Contents:

Using Scriptable Objects

Scriptable Objects are a brilliant way to store data for future reference, and we will be using this to have data for UI elements to derive from.

On the UI front, the plants have a Name and Description which we can utilise TextMeshPro fields and [TextArea] fields to show in the inspector. I’ve also moved the Prefab, Sun Cost and Spawn Recharge Time fields from the original Plant class into Plant Data as it is required to be used by UI interactions rather than the plant itself.

[CreateAssetMenu(fileName = "PlantData", menuName = "PvZRemake/Plant/New Plant", order = 1)]
internal class PlantData : ScriptableObject
{
    [Header("Identity")]
    public string plantName;
    [TextArea(5, 5)]
    public string plantDescription;

    [Header("Visuals")]
    public GameObject plantPrefab;

    [Header("Parameters")]
    public int sunCost;

    public enum PlantDesignation { Day, Night, Water_Day, Water_Night, Roof };
    public PlantDesignation plantDesignation;

    public enum SpawnRechargeTime { Fast, Slow, Very_Slow };
    public SpawnRechargeTime spawnRechargeTime;

    public bool isUpgrade;
}

07_scriptableobj.PNG

Adding an Interactive Plant Placer

Utilising the basic placing element we created in the Playable Area, we can create a system that needs to check the Sun Cost, and if the plant has Recharged to prevent spam clicking.

Checking the Sun Cost is a simple comparison, but the Spawn Recharge Time will require a bit more logic. We can set up a Coroutine to allow the Recharge Time to increment every second and do a check against the Recharge Time of the Plant UI selected; we can also apply this to the icon’s Image component to create a filling icon every second to show a visual Recharge timer. We also add an isInitial state as Plants which are not defined as a Fast recharge speed are not immediately able to be placed.

IEnumerator RechargePlantUI()
{
		while (isRecharging)
		{
				//Wait for seconds.
				yield return new WaitForSeconds(1f);       
				if (rechargeTime < maxRechargeTime)
        {
            rechargeTime++;
            GetComponent<Image>().fillAmount = rechargeTime / maxRechargeTime;
        }
        else isRecharging = false;
    }

    while (!isRecharging)
    {
        GetComponent<Image>().fillAmount = 1f;
        
        if (isInitial)
        {
            isInitial = false;
            SetRechargeTime();
        }
            
        yield return null;
    }
}

Now, we can add a new method call into the Clicking check inside of PlaceObjOnGrid along the lines of uiController.currentlyHighlightedUIMember.PlaceAndBeginRecharging(); to store the currently highlighted icon for a future tooltip.

public void PlaceAndBeginRecharging()
{
		uiController.DecreaseSunAmount(selectedPlant.sunCost);
		uiController.currentlyHighlightedUIMember = null; 
		SetRechargeTime(); //Uses the spawnRechargeTime Enum to set the recharge time.
    isRecharging = true;
    GetComponent<Image>().fillAmount = 0;
    rechargeTime = 0;
    StartCoroutine("RechargePlantUI");
}

07_uiAndSunFirstPass.gif

Adding a Plant Tooltip

For the Tooltip, we can utilise the OnPointerEnter and OnPointerExit methods to detect when we are hovering over the icons in the UI. Upon this, we can get the tooltip to populate its Text fields with the Name and Description fields we made in the Plant Data Scriptable Object.

//Detect if the Cursor starts to pass over the GameObject.
public void OnPointerEnter(PointerEventData pointerEventData)
{
		//Output to console the GameObject's name and the following message.
		Debug.Log("Cursor Entering " + name + " GameObject");   
		uiController.currentlyHighlightedUIMember = this;
		uiController.tooltip.SetActive(true);
    tooltipController.plantName.text = selectedPlant.plantName;
    tooltipController.plantDescription.text = selectedPlant.plantDescription;
    tooltipController.sunCost.text = "Sun Cost: " + selectedPlant.sunCost.ToString();
}

//Detect when Cursor leaves the GameObject.
public void OnPointerExit(PointerEventData pointerEventData)
{
    //Output the following message with the GameObject's name.
    Debug.Log("Cursor Exiting " + name + " GameObject");

    uiController.tooltip.SetActive(false);
}

07_uiTooltip.gif

Now we have a dynamic Tooltip that updates based on the icon selected, which also allows a bit of optimisation due to only one Tooltip existing instead of turning multiple on and off.

We’ve also changed the Raycast Target element to be disabled on the ToolTip and moved the ToolTip layer above the buttons so the UI looks refined and isn’t blocking either element.