Since the success of Stardew Valley, farming sims are more popular than ever before – and it doesn’t take much development experience to make them.
In this Unity tutorial, we’re going to cover how to set up the most essential component of farming-based games: planting crops. This tutorial will be based around a grid-based, top-down 2D project, but many of these concepts can be retrofitted as needed for 3D as well.
While some basic Unity experience is required, including knowledge of how to set up top-down 2D player characters, these explanations will be very beginner-friendly!
If you’re ready to start learning farming mechanics, let’s dive into Unity!
Project Files
While you’ll need to set up your own Unity project, we have included a full copy of the code files used in this tutorial, as well as sprite assets used.
BUILD GAMES FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.
Setting up Crop Data
To start our tutorial off, we’re first going to set up our crop data and our game manager that will control the entire aspect of how crops are planted. We will be setting up our data based around a top-down, 2D game with grid-based, interactable tiles.
Data Setup
To start, let’s create a new C# script called “CropData”. This script is going to contain all the information specific to the crop we grow.
This script isn’t going to be attached to any GameObject. Instead, we want to create an asset for each of the different types of crop. To do this, simply replace MonoBehaviour with ScriptableObject and use the CreateAssetMenu property to create a menu item for it.
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName = "Crop Data", menuName = "New Crop Data")] public class CropData : ScriptableObject { }
This will allow us to right-click on the Project window to create a new Crop Data asset.
We’re going to create one for “Wheat”.
Crops will display different sprites depending on the number of days it has grown for. Let’s declare the following variables inside the script to track these sprites, as well as a few other details related to the crop (such as sell price):
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName = "Crop Data", menuName = "New Crop Data")] public class CropData : ScriptableObject { public int daysToGrow; public Sprite[] growProgressSprites; public Sprite readyToHarvestSprite; public int purchasePrice; public int sellPrice; }
Now we can save the script and go back to the Editor to start filling out the crop data for Wheat. You can fill whatever data you want, but we used:
- Days to Grow: 6
- Grow Progress Sprites & Ready To Harvest Sprite: With the course files, we includes our wheat crop sprite which we sliced from a single sprite.
- Purchase Price: 20
- Sell Price: 35
GameManager
To manage the crops, we will create a new C# script called “GameManager“. We need to know primarily what the current day is, how much money we have, and what crop we want to plant.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using TMPro; public class GameManager : MonoBehaviour { public int curDay; public int money; public CropData selectedCropToPlant; }
We can set up a Singleton for this script so that we can easily access this script from other scripts as well.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using TMPro; public class GameManager : MonoBehaviour { public int curDay; public int money; public CropData selectedCropToPlant; // Singleton public static GameManager instance; void Awake () { // Initialize the singleton. if(instance != null && instance != this) { Destroy(gameObject); } else { instance = this; } } }
Now we want to implement the following functionalities:
- Whenever a new day takes over, we will trigger an event to notify the crops to grow.
- We can purchase, plant, and harvest crops
Let’s create some dummy functions first:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using TMPro; public class GameManager : MonoBehaviour { public int curDay; public int money; public int cropInventory; public CropData selectedCropToPlant; public event UnityAction onNewDay; // Singleton public static GameManager instance; void Awake () { // Initialize the singleton. if(instance != null && instance != this) { Destroy(gameObject); } else { instance = this; } } // Called when a crop has been planted. // Listening to the Crop.onPlantCrop event. public void OnPlantCrop (CropData cop) { } // Called when a crop has been harvested. // Listening to the Crop.onCropHarvest event. public void OnHarvestCrop (CropData crop) { } // Called when we want to purchase a crop. public void PurchaseCrop (CropData crop) { } // Do we have enough crops to plant? public bool CanPlantCrop () { } // Called when the buy crop button is pressed. public void OnBuyCropButton (CropData crop) { } }
Crop Script
Next, let’s set up the ability to actually grow our crops.
Crop Variables
Let’s create a new C# script called “Crop“. This is going to spawn in the crops once we put the seeds down.
First of all, we need to declare the variables and make sure to set up UnityEngine.Events so we can automatically trigger events when we plant or harvest a crop:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Crop : MonoBehaviour { private CropData curCrop; private int plantDay; private int daysSinceLastWatered; public SpriteRenderer sr; public static event UnityAction<CropData> onPlantCrop; public static event UnityAction<CropData> onHarvestCrop; }
Harvesting & Watering
We can then keep track of the number of days that the crop has been planted (i.e. Crop Progress), by subtracting plantDay from GameManager.curday.
Let’s return a boolean to check if the crop is old enough for harvesting.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Crop : MonoBehaviour { private CropData curCrop; private int plantDay; private int daysSinceLastWatered; public SpriteRenderer sr; public static event UnityAction<CropData> onPlantCrop; public static event UnityAction<CropData> onHarvestCrop; // Returns the number of days that the crop has been planted for. int CropProgress () { return GameManager.instance.curDay - plantDay; } // Can we currently harvest the crop? public bool CanHarvest () { return CropProgress() >= curCrop.daysToGrow; } }
Now, when we harvest the crop, we’re going to invoke the onHarvestCrop event and destroy the crop gameObject. At the same time, we’ll also add our water function, which simply changes daysSinceLastWatered to 0.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Crop : MonoBehaviour { private CropData curCrop; private int plantDay; private int daysSinceLastWatered; public SpriteRenderer sr; public static event UnityAction<CropData> onPlantCrop; public static event UnityAction<CropData> onHarvestCrop; // Called when the crop has been watered. public void Water () { daysSinceLastWatered = 0; } // Called when we want to harvest the crop. public void Harvest () { if(CanHarvest()) { onHarvestCrop?.Invoke(curCrop); Destroy(gameObject); } } // Returns the number of days that the crop has been planted for. int CropProgress () { return GameManager.instance.curDay - plantDay; } // Can we currently harvest the crop? public bool CanHarvest () { return CropProgress() >= curCrop.daysToGrow; } }
Note that the question mark next to the unity event (onHarvestCrop?.Invoke) allows us to invoke it only if we have functions listening to the event. This effectively prevents errors from popping up.
When the crop has progressed, we can check if the crop is ready to be harvested. If it is, then we should change the sprite of the crop.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Crop : MonoBehaviour { private CropData curCrop; private int plantDay; private int daysSinceLastWatered; public SpriteRenderer sr; public static event UnityAction<CropData> onPlantCrop; public static event UnityAction<CropData> onHarvestCrop; // Called when the crop has progressed. void UpdateCropSprite () { int cropProg = CropProgress(); if(cropProg < curCrop.daysToGrow) { sr.sprite = curCrop.growProgressSprites[cropProg]; } else { sr.sprite = curCrop.readyToHarvestSprite; } } // Called when the crop has been watered. public void Water () { daysSinceLastWatered = 0; } // Called when we want to harvest the crop. public void Harvest () { if(CanHarvest()) { onHarvestCrop?.Invoke(curCrop); Destroy(gameObject); } } // Returns the number of days that the crop has been planted for. int CropProgress () { return GameManager.instance.curDay - plantDay; } // Can we currently harvest the crop? public bool CanHarvest () { return CropProgress() >= curCrop.daysToGrow; } }
New Day
When a new day takes over, we will increment the daySinceLastWatered, and destroy the crop if it has been more than 3 days.
If it hasn’t, then we can call UpdateCropSprite.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Crop : MonoBehaviour { private CropData curCrop; private int plantDay; private int daysSinceLastWatered; public SpriteRenderer sr; public static event UnityAction<CropData> onPlantCrop; public static event UnityAction<CropData> onHarvestCrop; // Called when a new day ticks over. public void NewDayCheck () { daysSinceLastWatered++; if(daysSinceLastWatered > 3) { Destroy(gameObject); } UpdateCropSprite(); } // Called when the crop has progressed. void UpdateCropSprite () { int cropProg = CropProgress(); if(cropProg < curCrop.daysToGrow) { sr.sprite = curCrop.growProgressSprites[cropProg]; } else { sr.sprite = curCrop.readyToHarvestSprite; } } // Called when the crop has been watered. public void Water () { daysSinceLastWatered = 0; } // Called when we want to harvest the crop. public void Harvest () { if(CanHarvest()) { onHarvestCrop?.Invoke(curCrop); Destroy(gameObject); } } // Returns the number of days that the crop has been planted for. int CropProgress () { return GameManager.instance.curDay - plantDay; } // Can we currently harvest the crop? public bool CanHarvest () { return CropProgress() >= curCrop.daysToGrow; } }
Initial Values
Finally, we will set up the initial values for the variables when we first plant the crop (i.e. curCrop, plantDay, daysSinceLastWatered).
Once they’re set, we can update the crop sprite and invoke the onPlantCrop event.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Crop : MonoBehaviour { private CropData curCrop; private int plantDay; private int daysSinceLastWatered; public SpriteRenderer sr; public static event UnityAction<CropData> onPlantCrop; public static event UnityAction<CropData> onHarvestCrop; // Called when the crop has been planted for the first time. public void Plant (CropData crop) { curCrop = crop; plantDay = GameManager.instance.curDay; daysSinceLastWatered = 1; UpdateCropSprite(); onPlantCrop?.Invoke(crop); } // Called when a new day ticks over. public void NewDayCheck () { daysSinceLastWatered++; if(daysSinceLastWatered > 3) { Destroy(gameObject); } UpdateCropSprite(); } // Called when the crop has progressed. void UpdateCropSprite () { int cropProg = CropProgress(); if(cropProg < curCrop.daysToGrow) { sr.sprite = curCrop.growProgressSprites[cropProg]; } else { sr.sprite = curCrop.readyToHarvestSprite; } } // Called when the crop has been watered. public void Water () { daysSinceLastWatered = 0; } // Called when we want to harvest the crop. public void Harvest () { if(CanHarvest()) { onHarvestCrop?.Invoke(curCrop); Destroy(gameObject); } } // Returns the number of days that the crop has been planted for. int CropProgress () { return GameManager.instance.curDay - plantDay; } // Can we currently harvest the crop? public bool CanHarvest () { return CropProgress() >= curCrop.daysToGrow; } }
Event Listeners
We now need to register listeners for the events we made. Right now the events are invoked, but there are no functions listening to them.
Let’s open up the GameManager script, and subscribe to the events inside OnEnable.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using TMPro; public class GameManager : MonoBehaviour { public int curDay; public int money; public int cropInventory; public CropData selectedCropToPlant; public event UnityAction onNewDay; // Singleton public static GameManager instance; void OnEnable () { Crop.onPlantCrop += OnPlantCrop; Crop.onHarvestCrop += OnHarvestCrop; } void OnDisable () { Crop.onPlantCrop -= OnPlantCrop; Crop.onHarvestCrop -= OnHarvestCrop; } ... }
Here we can define what to execute when onPlantCrop and onHarvestCrop are invoked.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using TMPro; public class GameManager : MonoBehaviour { public int curDay; public int money; public int cropInventory; public CropData selectedCropToPlant; public event UnityAction onNewDay; // Singleton public static GameManager instance; void OnEnable () { Crop.onPlantCrop += OnPlantCrop; Crop.onHarvestCrop += OnHarvestCrop; } void OnDisable () { Crop.onPlantCrop -= OnPlantCrop; Crop.onHarvestCrop -= OnHarvestCrop; } // Called when a crop has been planted. // Listening to the Crop.onPlantCrop event. public void OnPlantCrop (CropData cop) { cropInventory--; } // Called when a crop has been harvested. // Listening to the Crop.onCropHarvest event. public void OnHarvestCrop (CropData crop) { money += crop.sellPrice; } }
Planting Crops
Last but not least, we need to be able to plant crops, so we will set up an Interact function that can be called by the player whenever they interact with the tiles we can plant on.
Tile Interaction
On any script that is controlling your field tiles, add in the Interact function (which will ultimately be invoked by your player character in whatever manner you choose):
// Called when the player interacts with the tile. public void Interact () { if(!tilled) { Till(); } else if(!HasCrop() && GameManager.instance.CanPlantCrop()) { PlantNewCrop(GameManager.instance.selectedCropToPlant); } else if(HasCrop() && curCrop.CanHarvest()) { curCrop.Harvest(); } else { Water(); } }
After this, we’ll create a new function called PlantNewCrop, which takes a parameter of CropData (which is the crop we want to plant).
Planting should only occur if the tile is tilled.
// Called when we interact with a tilled tile and we have crops to plant. void PlantNewCrop (CropData crop) { if(!tilled) return; curCrop = Instantiate(cropPrefab, transform).GetComponent<Crop>(); curCrop.Plant(crop); GameManager.instance.onNewDay += OnNewDay; }
Note that we’re subscribing to the GameManager’s OnNewDay event here rather than OnEnable, because not every tile needs to listen to the event. We want to subscribe to it once we have a crop planted.
Next, we’ll create a function for Till and Water, where we will switch the tile’s sprite.
// Called when we interact with a grass tile. void Till () { tilled = true; sr.sprite = tilledSprite; } // Called when we interact with a crop tile. void Water () { sr.sprite = wateredTilledSprite; if(HasCrop()) { curCrop.Water(); } }
If we no longer have any crop on this tile, then we just want to reset the tile and unsubscribe from the OnNewDay event.
If we do have a crop planted and a new day occurred, then we need to trigger the crop’s NewDayCheck:
// Called every time a new day occurs. // Only called if the tile contains a crop. void OnNewDay () { if(curCrop == null) { tilled = false; sr.sprite = grassSprite; GameManager.instance.onNewDay -= OnNewDay; } else if(curCrop != null) { sr.sprite = tilledSprite; curCrop.NewDayCheck(); } }
Finishing Up
Let’s save this script and go to the Editor to test it out.
Make sure that the Crop script has the sprite renderer assigned:
Save the Crop GameObject as a prefab and assign sprites to your tile object. We’ve included an example of our FieldTile script so you can see how we did it, but your results will vary.
Finally, we can set up the GameManager with the public variables filled in.
We can now till the ground, water the ground, and plant crops!
Conclusion
And that covers the basics of setting up farming mechanics for farming sim games! Congratulations for making it through!
As mentioned, farming is very popular, and it can add variety to tried and true genres. However, keep in mind our way is not the only way. Depending on your own game project, you may need to adapt these systems. However, we’ve designed these systems to be expandable and fit in with most top-down, 2D games.
From here, though, you can try out tons of other features using the same fundamentals. Perhaps you want to have livestock, which you can also set up as Scriptable Objects and utilize the Unity events system to also have an invokable Interact function. Or, maybe you want to create a farming sim on an alien world where the crops can be poisonous! The choice is yours!
Regardless, we hope you’ve expanded your horizons with farming, and we wish you the best of luck with your future projects!
Want to learn more about farming sims? Try our complete Construct a Micro-Farming RPG course.