Ever wanted to create a game like Harvest Moon in Unity? Check out Part 8 of our guide here, where we go through how to make crops that can be harvested multiple times, along with a system that allows for them to die. You can also find Part 7 of our guide here, where we went through how to grow and harvest crops.
A link to a package containing the project files up to Part 8 of this tutorial series can also be found at the end of this article, exclusive to Patreon supporters only.
1. Regrowable Crops
In Part 7 of the series, we managed to set up the core grow and harvest system for single-harvest crops, which would make up the bulk of crops the player will work with. Once harvested, the crop is cleared from the land.
a. Additional properties for the SeedData
ScriptableObject
For regrowable crops to be a thing in the game, we need additional information in our SeedData
:
- Will the plant be able to regrow the crop after being harvested?
- If so, how long would it take?
Hence, add the following to SeedData
:
SeedData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Items/Seed")] public class SeedData : ItemData { //Time it takes before the seed matures into a crop public int daysToGrow; //The crop the seed will yield public ItemData cropToYield; //The seedling GameObject public GameObject seedling; [Header("Regrowable")] //Is the plant able to regrow the crop after being harvested? public bool regrowable; //Time taken before the plant yields another crop public int daysToRegrow; }
With this, we are able to create our first regrowable crops. Under Assets/Data/Tools/Seeds, create a new Seed (Create > Items > Seed) with the name ‘Tomato Seeds‘. Fill in the fields:
For this particular seed, it will take 9 days for the crop to mature, and it will subsequently take 3 days after harvesting it for it to be harvestable again. The player will harvest tomatoes from it, so Create > Item in the Assets/Data/Items and name it ‘Tomato’. Fill in the fields:
In the Tomato Seeds Seed ScriptableObject, set the Crop to Yield to the newly-created Tomato.
b. Setting up Prefabs for a regrowable crop
From Assets/Imported Asset/Cartoon_Farm_Crops/Prefabs/Standard, duplicate Tomato_Plant and Tomato_Fruit, rename them to ‘Tomato Seedling‘ and ‘Tomato‘ respectively. Afterwards, move these to Assets/Prefabs/Crops. On the Tomato prefab, set its Tag and Layer to Item, and set its rotation to face upright:
As it is the Item GameObject the player will be interacting with, add InteractableObject to the Tomato prefab and set the Item to the Tomato ItemData
we made from 1a.
Like what we did with the Cabbage seedling in Part 7, remove the Mesh Collider from the Tomato Seedling Prefab as the player has no business colliding with it.
Set the Tomato Seeds SeedData’s seedling to this prefab:
In single-harvest crops, the GameObject that spawns in the crop’s Harvestable state is the same Item GameObject the player receives (E.g. Cabbage seedlings grow into cabbages, the cabbage is harvested). This is not the case for regrowable crops. In the case of the tomato, the tomato seedling yields tomatoes on it, but only the tomato itself is harvested.
To this end, we need a separate prefab to represent this harvestable state. Duplicate the Tomato Seedling Prefab and name it ‘Tomato Harvestable‘. On it, add Tomato GameObjects like this:
The player needs to be able to interact with this object to harvest, so set the Tag and Layer to Item and give it back its Mesh Collider.
In CropBehaviour
, the script knows what GameObject to instantiate for the Harvestable state from the Seed’s cropToYield ItemData
. However, we currently have Tomato Seeds‘ cropToYield
set to Tomato. This means that it will instantiate the Tomato prefab instead of the Tomato Harvestable prefab.
To get around this, simply create a new Item in Assets/Data/Items and call it ‘Tomato Crop‘ and set its Game Model to the Tomato Harvestable prefab.
In this tutorial, we chose to just work with whatever we have set up. However, if you don’t like having so many Items to work with, you could alternatively add an additional GameObject variable in SeedData to store the prefab information instead. Just remember to change up the logic in CropBehaviour
as well.
In the Tomato Seeds SeedData
, change the Crop To Yield to the Tomato Crop ItemData
.
Article continues after the advertisement:
c. Creating the regrowing behaviour for the crop
By now, if you were to add the Tomato Seed to the Inventory and test it, the seed should grow into the tomato plant as intended.
The class InteractableObject
is used for the player to pick up the harvestable crop. However, we need the crop to do more than just the simple Pickup
function. It needs to also revert the crop’s state back to Seedling for it to start the regrowing process over.
Thus, create a new script that inherits from InteractableObject
called ‘RegrowableHarvestBehaviour‘:
RegrowableHarvestBehaviour.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RegrowableHarvestBehaviour :MonoBehaviourInteractableObject {void Start() { }public void Update() { }}
Add this script to the Tomato Harvestable prefab.
We will need to override the Pickup
function in InteractableObject
, so add the virtual
keyword to the function:
Learn more about the virtual
keyword in C# here.
InteractableObject.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class InteractableObject : MonoBehaviour { //The item information the GameObject is supposed to represent public ItemData item; public virtual void Pickup() { //Set the player's inventory to the item InventoryManager.Instance.equippedItem = item; //Update the changes in the scene InventoryManager.Instance.RenderHand(); //Destroy this instance so as to not have multiple copies Destroy(gameObject); } }
When the player harvests the crop from the regrowable plant, the following will happen:
- The yielded crop will be added to the player’s inventory
- The inventory changes will be rendered in the scene
- The crop state in the plant’s
CropBehaviour
will reset back to Seedling
For 3. to be done, RegrowableHarvestBehaviour
must be able to identify which CropBehaviour
it is supposed to work with. Add the following to RegrowableHarvestBehaviour
:
RegrowableHarvestBehaviour.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RegrowableHarvestBehaviour : InteractableObject
{
CropBehaviour parentCrop;
//Sets the parent crop
public void SetParent(CropBehaviour parentCrop)
{
this.parentCrop = parentCrop;
}
}
Call this function in the Plant()
function of CropBehaviour
:
CropBehaviour.cs
//Initialisation for the crop GameObject //Called when the player plants a seed public void Plant(SeedData seedToGrow) { //Save the seed information this.seedToGrow = seedToGrow; //Instantiate the seedling and harvestable GameObjects seedling = Instantiate(seedToGrow.seedling, transform); //Access the crop item data ItemData cropToYield = seedToGrow.cropToYield; //Instantiate the harvestable crop harvestable = Instantiate(cropToYield.gameModel, transform); //Convert Days To Grow into hours int hoursToGrow = GameTimestamp.DaysToHours(seedToGrow.daysToGrow); //Convert it to minutes maxGrowth = GameTimestamp.HoursToMinutes(hoursToGrow); //Check if it is regrowable if (seedToGrow.regrowable) { //Get the RegrowableHarvestBehaviour from the GameObject RegrowableHarvestBehaviour regrowableHarvest = harvestable.GetComponent<RegrowableHarvestBehaviour>(); //Initialise the harvestable regrowableHarvest.SetParent(this); } //Set the initial state to Seed SwitchState(CropState.Seed); }
Scroll down to the SwitchState()
method of the class. As of now, it will unparent the harvestable GameObject and destroy its own GameObject upon reaching the Harvestable state. This should not be the case for our regrowable crops. Hence, add an if statement to that block of code:
CropBehaviour.cs
//Function to handle the state changes void SwitchState(CropState stateToSwitch) { //Reset everything and set all GameObjects to inactive seed.SetActive(false); seedling.SetActive(false); harvestable.SetActive(false); switch (stateToSwitch) { case CropState.Seed: //Enable the Seed GameObject seed.SetActive(true); break; case CropState.Seedling: //Enable the Seedling GameObject seedling.SetActive(true); break; case CropState.Harvestable: //Enable the Harvestable GameObject harvestable.SetActive(true); //If the seed is not regrowable, detach the harvestable from this crop gameobject and destroy it. if (!seedToGrow.regrowable) { //Unparent it to the crop harvestable.transform.parent = null; Destroy(gameObject); } break; } //Set the current crop state to the state we're switching to cropState = stateToSwitch; }
With the planting and harvesting logic covered within the class, we now move on to the regrowing aspect. It needs to do the following:
- Push the growth value back by the time taken to regrow
- Reset the
CropState
back to Seedling
This can be done by adding a new function to CropBehaviour
:
CropBehaviour.cs
//Called when the player harvests a regrowable crop. Resets the state to seedling public void Regrow() { //Reset the growth //Get the regrowth time in hours int hoursToRegrow = GameTimestamp.DaysToHours(seedToGrow.daysToRegrow); growth = maxGrowth - GameTimestamp.HoursToMinutes(hoursToRegrow); //Switch the state back to seedling SwitchState(CropState.Seedling); }
We can now return to RegrowableHarvestBehaviour
and override the Pickup
method:
RegrowableHarvestBehaviour.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RegrowableHarvestBehaviour : InteractableObject { CropBehaviour parentCrop; //Sets the parent crop public void SetParent(CropBehaviour parentCrop) { this.parentCrop = parentCrop; } public override void Pickup() { //Set the player's inventory to the item InventoryManager.Instance.equippedItem = item; //Update the changes in the scene InventoryManager.Instance.RenderHand(); //Set the parent crop back to seedling to regrow it parentCrop.Regrow(); } }
It’s mostly identical with that of its parent, except instead of destroying itself afterwards, it calls the Regrow
function of the parented crop. So that it knows that the player will be picking a tomato up on harvest, go to the Tomato Harvestable prefab and set its item to Tomato.
This item variable was inherited from InteractableObject
.
We forgot to set the Game Model for the Tomato, so go back to the Tomato ItemData
and set it to the prefab we made:
With that, we are able to cultivate and harvest regrowable crops.
Article continues after the advertisement:
2. Plant Wilting and Death
As of now, whatever the player plants will be practically immortal. Let’s make it possible for crops to wilt and die.
a. Setting up the Wilted State
Duplicate the Dirt material from Assets/Imported Asset/Farmland/Dirt/Materials and rename it to ‘Wilted Plant‘.
Set its Albedo colour to a reddish-brown and lower its Smoothness to 0.
Enter the Crop prefab from Assets/Prefabs/Crops and drag in the Pumpkin_Plant prefab from Assets/Imported Asset/Cartoon_Farm_Crops/Prefabs/Standard.
Make the following adjustments:
- Rename it to ‘Wilted’
- Give it the Wilted Plant material.
- Set its scale to 0.5
- Position it so that it is in the middle of the Seed GameObject.
Add the wilted CropState
along with a reference to this GameObject in CropBehaviour
:
CropBehaviour.cs
//Information on what the crop will grow into SeedData seedToGrow; [Header("Stages of Life")] public GameObject seed; public GameObject wilted; private GameObject seedling; private GameObject harvestable; //The growth points of the crop int growth; //How many growth points it takes before it becomes harvestable int maxGrowth; public enum CropState { Seed, Seedling, Harvestable, Wilted } //The current stage in the crop's growth public CropState cropState;
Set the reference accordingly:
Create a case for it to switch to the Wilted state in CropBehaviour's
SwitchState
function:
CropBehaviour.cs
//Function to handle the state changes void SwitchState(CropState stateToSwitch) { //Reset everything and set all GameObjects to inactive seed.SetActive(false); seedling.SetActive(false); harvestable.SetActive(false); wilted.SetActive(false); switch (stateToSwitch) { case CropState.Seed: //Enable the Seed GameObject seed.SetActive(true); break; case CropState.Seedling: //Enable the Seedling GameObject seedling.SetActive(true); break; case CropState.Harvestable: //Enable the Harvestable GameObject harvestable.SetActive(true); //If the seed is not regrowable, detach the harvestable from this crop gameobject and destroy it. if (!seedToGrow.regrowable) { //Unparent it to the crop harvestable.transform.parent = null; Destroy(gameObject); } break; case CropState.Wilted: //Enable the wilted GameObject wilted.SetActive(true); break; } //Set the current crop state to the state we're switching to cropState = stateToSwitch; }
b. Plant health system
There are two main ways crops die in the game:
- If the player neglects to water the plants
- When the season changes and the crop is not suited to grow in that season
As we have not implemented the seasonal crops system yet, we will focus on the first way. This can be implemented in the same way we did the growth system:
- For every minute the
Land
is not watered, a function that handles the wilting process of the crop inCropBehaviour
is called. We will call thisWither()
. - The health of the plant will be tracked in
Wither()
. - When the health reaches 0, it will switch its state to Wilted.
For this tutorial, we will make it so that the crop dies after not being watered for 2 days (48 hours). Declare the following variables in CropBehaviour
:
CropBehaviour.cs
//Information on what the crop will grow into SeedData seedToGrow; [Header("Stages of Life")] public GameObject seed; public GameObject wilted; private GameObject seedling; private GameObject harvestable; //The growth points of the crop int growth; //How many growth points it takes before it becomes harvestable int maxGrowth; //The crop can stay alive for 48 hours without water before it dies int maxHealth = GameTimestamp.HoursToMinutes(48); int health; public enum CropState { Seed, Seedling, Harvestable, Wilted } //The current stage in the crop's growth public CropState cropState;
The wilting process should only begin after germination, that is from the point it becomes a seedling. Hence, we will initialise the health value at that stage. Add the following to the SwitchState()
function:
CropBehaviour.cs
//Function to handle the state changes void SwitchState(CropState stateToSwitch) { //Reset everything and set all GameObjects to inactive seed.SetActive(false); seedling.SetActive(false); harvestable.SetActive(false); wilted.SetActive(false); switch (stateToSwitch) { case CropState.Seed: //Enable the Seed GameObject seed.SetActive(true); break; case CropState.Seedling: //Enable the Seedling GameObject seedling.SetActive(true); //Give the seed health health = maxHealth; break; case CropState.Harvestable: //Enable the Harvestable GameObject harvestable.SetActive(true); //If the seed is not regrowable, detach the harvestable from this crop gameobject and destroy it. if (!seedToGrow.regrowable) { //Unparent it to the crop harvestable.transform.parent = null; Destroy(gameObject); } break; case CropState.Wilted: //Enable the wilted GameObject wilted.SetActive(true); break; } //Set the current crop state to the state we're switching to cropState = stateToSwitch; }
We also want the plant to heal up whenever the soil is watered, so add the following to the Grow()
function:
CropBehaviour.cs
//The crop will grow when watered public void Grow() { //Increase the growth point by 1 growth++; //Restore the health of the plant when it is watered if(health < maxHealth) { health++; } //The seed will sprout into a seedling when the growth is at 50% if(growth >= maxGrowth / 2 && cropState == CropState.Seed) { SwitchState(CropState.Seedling); } //Grow from seedling to harvestable if(growth >= maxGrowth && cropState == CropState.Seedling) { SwitchState(CropState.Harvestable); } }
Add the Wither()
function to the class:
CropBehaviour.cs
//The crop will progressively wither when the soil is dry public void Wither() { health--; //If the health is below 0 and the crop has germinated, kill it if(health <= 0 && cropState != CropState.Seed) { SwitchState(CropState.Wilted); } }
Finally, we call Wither()
from Land
under the ClockUpdate()
function:
Land.cs
public void ClockUpdate(GameTimestamp timestamp) { //Checked if 24 hours has passed since last watered if(landStatus == LandStatus.Watered) { //Hours since the land was watered int hoursElapsed = GameTimestamp.CompareTimestamps(timeWatered, timestamp); Debug.Log(hoursElapsed + " hours since this was watered"); //Grow the planted crop, if any if(cropPlanted != null) { cropPlanted.Grow(); } if(hoursElapsed > 24) { //Dry up (Switch back to farmland) SwitchLandStatus(LandStatus.Farmland); } } //Handle the wilting of the plant when the land is not watered if(landStatus != LandStatus.Watered && cropPlanted != null) { //If the crop has already germinated, start the withering if (cropPlanted.cropState != CropBehaviour.CropState.Seed) { cropPlanted.Wither(); } } }
Testing the game, you should observe the following:
- Seeds that are consistently watered yield crops
- Seeds that germinate into seedlings but are then neglected wilt
- Seeds that are not watered at all remain unchanged
Article continues after the advertisement:
c. Getting rid of unwanted and wilted crops
After the crop wilts, its remains are going to be stuck there until the player clears it. Hence, we need to make a tool for the player that does just that. Let’s make the shovel the tool for players to clear plant-based obstacles.
Add it as a ToolType
enum value in EquipmentData
:
EquipmentData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName = "Items/Equipment")] public class EquipmentData : ItemData { public enum ToolType { Hoe, WateringCan, Axe, Pickaxe, Shovel } public ToolType toolType; }
Under Assets/Data/Tools, create an asset for the Shovel:
Add the shovel to the player’s inventory:
Giving the shovel its functionality is simple with the interaction and tool systems we have made in the previous parts. Simply add a casein the toolType
switch for it to destroy planted crops on the Interact
function in Land
:
Land.cs
//When the player presses the interact button while selecting this land public void Interact() { //Check the player's tool slot ItemData toolSlot = InventoryManager.Instance.equippedTool; //If there's nothing equipped, return if (toolSlot == null) { return; } //Try casting the itemdata in the toolslot as EquipmentData EquipmentData equipmentTool = toolSlot as EquipmentData; //Check if it is of type EquipmentData if(equipmentTool != null) { //Get the tool type EquipmentData.ToolType toolType = equipmentTool.toolType; switch (toolType) { case EquipmentData.ToolType.Hoe: SwitchLandStatus(LandStatus.Farmland); break; case EquipmentData.ToolType.WateringCan: SwitchLandStatus(LandStatus.Watered); break; case EquipmentData.ToolType.Shovel: //Remove the crop from the land if(cropPlanted != null) { Destroy(cropPlanted.gameObject); } break; } //We don't need to check for seeds if we have already confirmed the tool to be an equipment return; } //Try casting the itemdata in the toolslot as SeedData SeedData seedTool = toolSlot as SeedData; ///Conditions for the player to be able to plant a seed ///1: He is holding a tool of type SeedData ///2: The Land State must be either watered or farmland ///3. There isn't already a crop that has been planted if(seedTool != null && landStatus != LandStatus.Soil && cropPlanted == null) { //Instantiate the crop object parented to the land GameObject cropObject = Instantiate(cropPrefab, transform); //Move the crop object to the top of the land gameobject cropObject.transform.position = new Vector3(transform.position.x, 0, transform.position.z); //Access the CropBehaviour of the crop we're going to plant cropPlanted = cropObject.GetComponent<CropBehaviour>(); //Plant it with the seed's information cropPlanted.Plant(seedTool); } }
That is all you need to get the shovel up and running:
Conclusion
With that, crops can be regrown, wilt, and removed. If you are a Patreon supporter, you can download the project files for what we’ve done so far. To use the files, you will have to unzip the file (7-Zip can help you do that), and open the folder with Assets and ProjectSettings as a project using Unity.
Here is the final code for all the scripts we have worked with today:
SeedData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Items/Seed")] public class SeedData : ItemData { //Time it takes before the seed matures into a crop public int daysToGrow; //The crop the seed will yield public ItemData cropToYield; //The seedling GameObject public GameObject seedling; [Header("Regrowable")] //Is the plant able to regrow the crop after being harvested? public bool regrowable; //Time taken before the plant yields another crop public int daysToRegrow; }
RegrowableHarvestBehaviour.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RegrowableHarvestBehaviour : InteractableObject { CropBehaviour parentCrop; //Sets the parent crop public void SetParent(CropBehaviour parentCrop) { this.parentCrop = parentCrop; } public override void Pickup() { //Set the player's inventory to the item InventoryManager.Instance.equippedItem = item; //Update the changes in the scene InventoryManager.Instance.RenderHand(); //Set the parent crop back to seedling to regrow it parentCrop.Regrow(); } }
InteractableObject.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class InteractableObject : MonoBehaviour { //The item information the GameObject is supposed to represent public ItemData item; public virtual void Pickup() { //Set the player's inventory to the item InventoryManager.Instance.equippedItem = item; //Update the changes in the scene InventoryManager.Instance.RenderHand(); //Destroy this instance so as to not have multiple copies Destroy(gameObject); } }
CropBehaviour.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CropBehaviour : MonoBehaviour { //Information on what the crop will grow into SeedData seedToGrow; [Header("Stages of Life")] public GameObject seed; public GameObject wilted; private GameObject seedling; private GameObject harvestable; //The growth points of the crop int growth; //How many growth points it takes before it becomes harvestable int maxGrowth; //The crop can stay alive for 48 hours without water before it dies int maxHealth = GameTimestamp.HoursToMinutes(48); int health; public enum CropState { Seed, Seedling, Harvestable, Wilted } //The current stage in the crop's growth public CropState cropState; //Initialisation for the crop GameObject //Called when the player plants a seed public void Plant(SeedData seedToGrow) { //Save the seed information this.seedToGrow = seedToGrow; //Instantiate the seedling and harvestable GameObjects seedling = Instantiate(seedToGrow.seedling, transform); //Access the crop item data ItemData cropToYield = seedToGrow.cropToYield; //Instantiate the harvestable crop harvestable = Instantiate(cropToYield.gameModel, transform); //Convert Days To Grow into hours int hoursToGrow = GameTimestamp.DaysToHours(seedToGrow.daysToGrow); //Convert it to minutes maxGrowth = GameTimestamp.HoursToMinutes(hoursToGrow); //Check if it is regrowable if (seedToGrow.regrowable) { //Get the RegrowableHarvestBehaviour from the GameObject RegrowableHarvestBehaviour regrowableHarvest = harvestable.GetComponent<RegrowableHarvestBehaviour>(); //Initialise the harvestable regrowableHarvest.SetParent(this); } //Set the initial state to Seed SwitchState(CropState.Seed); } //The crop will grow when watered public void Grow() { //Increase the growth point by 1 growth++; //Restore the health of the plant when it is watered if(health < maxHealth) { health++; } //The seed will sprout into a seedling when the growth is at 50% if(growth >= maxGrowth / 2 && cropState == CropState.Seed) { SwitchState(CropState.Seedling); } //Grow from seedling to harvestable if(growth >= maxGrowth && cropState == CropState.Seedling) { SwitchState(CropState.Harvestable); } } //The crop will progressively wither when the soil is dry public void Wither() { health--; //If the health is below 0 and the crop has germinated, kill it if(health <= 0 && cropState != CropState.Seed) { SwitchState(CropState.Wilted); } } //Function to handle the state changes void SwitchState(CropState stateToSwitch) { //Reset everything and set all GameObjects to inactive seed.SetActive(false); seedling.SetActive(false); harvestable.SetActive(false); wilted.SetActive(false); switch (stateToSwitch) { case CropState.Seed: //Enable the Seed GameObject seed.SetActive(true); break; case CropState.Seedling: //Enable the Seedling GameObject seedling.SetActive(true); //Give the seed health health = maxHealth; break; case CropState.Harvestable: //Enable the Harvestable GameObject harvestable.SetActive(true); //If the seed is not regrowable, detach the harvestable from this crop gameobject and destroy it. if (!seedToGrow.regrowable) { //Unparent it to the crop harvestable.transform.parent = null; Destroy(gameObject); } break; case CropState.Wilted: //Enable the wilted GameObject wilted.SetActive(true); break; } //Set the current crop state to the state we're switching to cropState = stateToSwitch; } //Called when the player harvests a regrowable crop. Resets the state to seedling public void Regrow() { //Reset the growth //Get the regrowth time in hours int hoursToRegrow = GameTimestamp.DaysToHours(seedToGrow.daysToRegrow); growth = maxGrowth - GameTimestamp.HoursToMinutes(hoursToRegrow); //Switch the state back to seedling SwitchState(CropState.Seedling); } }
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour, ITimeTracker { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; //The selection gameobject to enable when the player is selecting the land public GameObject select; //Cache the time the land was watered GameTimestamp timeWatered; [Header("Crops")] //The crop prefab to instantiate public GameObject cropPrefab; //The crop currently planted on the land CropBehaviour cropPlanted = null; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); //Set the land to soil by default SwitchLandStatus(LandStatus.Soil); //Deselect the land by default Select(false); //Add this to TimeManager's Listener list TimeManager.Instance.RegisterTracker(this); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; //Cache the time it was watered timeWatered = TimeManager.Instance.GetGameTimestamp(); break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } public void Select(bool toggle) { select.SetActive(toggle); } //When the player presses the interact button while selecting this land public void Interact() { //Check the player's tool slot ItemData toolSlot = InventoryManager.Instance.equippedTool; //If there's nothing equipped, return if (toolSlot == null) { return; } //Try casting the itemdata in the toolslot as EquipmentData EquipmentData equipmentTool = toolSlot as EquipmentData; //Check if it is of type EquipmentData if(equipmentTool != null) { //Get the tool type EquipmentData.ToolType toolType = equipmentTool.toolType; switch (toolType) { case EquipmentData.ToolType.Hoe: SwitchLandStatus(LandStatus.Farmland); break; case EquipmentData.ToolType.WateringCan: SwitchLandStatus(LandStatus.Watered); break; case EquipmentData.ToolType.Shovel: //Remove the crop from the land if(cropPlanted != null) { Destroy(cropPlanted.gameObject); } break; } //We don't need to check for seeds if we have already confirmed the tool to be an equipment return; } //Try casting the itemdata in the toolslot as SeedData SeedData seedTool = toolSlot as SeedData; ///Conditions for the player to be able to plant a seed ///1: He is holding a tool of type SeedData ///2: The Land State must be either watered or farmland ///3. There isn't already a crop that has been planted if(seedTool != null && landStatus != LandStatus.Soil && cropPlanted == null) { //Instantiate the crop object parented to the land GameObject cropObject = Instantiate(cropPrefab, transform); //Move the crop object to the top of the land gameobject cropObject.transform.position = new Vector3(transform.position.x, 0, transform.position.z); //Access the CropBehaviour of the crop we're going to plant cropPlanted = cropObject.GetComponent<CropBehaviour>(); //Plant it with the seed's information cropPlanted.Plant(seedTool); } } public void ClockUpdate(GameTimestamp timestamp) { //Checked if 24 hours has passed since last watered if(landStatus == LandStatus.Watered) { //Hours since the land was watered int hoursElapsed = GameTimestamp.CompareTimestamps(timeWatered, timestamp); Debug.Log(hoursElapsed + " hours since this was watered"); //Grow the planted crop, if any if(cropPlanted != null) { cropPlanted.Grow(); } if(hoursElapsed > 24) { //Dry up (Switch back to farmland) SwitchLandStatus(LandStatus.Farmland); } } //Handle the wilting of the plant when the land is not watered if(landStatus != LandStatus.Watered && cropPlanted != null) { //If the crop has already germinated, start the withering if (cropPlanted.cropState != CropBehaviour.CropState.Seed) { cropPlanted.Wither(); } } } }
EquipmentData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName = "Items/Equipment")] public class EquipmentData : ItemData { public enum ToolType { Hoe, WateringCan, Axe, Pickaxe, Shovel } public ToolType toolType; }
Article continues after the advertisement:
Really thankful for this series thus far! I’m curious why something is happening though with my regrowables:
-when I harvest the crop and don’t move I can deposit infinite amounts of crop into my inventory(though the scene item disappears from handpoint as expected). Doesn’t happen with destroyable crops nor the regrowables if I move after harvesting. I tried adding an equipped item = null after calling the renderHand() but that just makes the item stick to my hand as expected since it doesn’t know there’s something there to be removed. Curious if anyone else has this happen (we’re usually moving as we pick things up so I almost didn’t notice)?
Hi Sabrina, sorry for missing out on your comments here. We addressed a couple of bug fixes in a later part: https://blog.terresquall.com/2022/12/creating-a-farming-rpg-in-unity-part-17/#bug-fixes. Do let me know if this fixes your issues. Otherwise, I will get the series developer to respond to you.
Hi Sabrina, looks like this bug is caused by the
RegrowableHarvestBehaviour
object still responding to player input despite being disabled. To fix this, add this to the top of thePickup()
inRegrowableHarvestBehaviour
:if (!isActiveAndEnabled) return;
adding if (!isActiveAndEnabled) return;
This solve my issue with the crops reappear in the hand every time i click right mouse button/Fire2. Thanks.
the other issue with harvesting small items is, the interaction or raycast need to be exactly hit the items any clue to handle this?
You can make the collider for these items larger so they can be easily detected.