Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How to make crops mature over time like in Stardew Valley?

Discussion in '2D' started by Sentokoo, Oct 15, 2020.

  1. Sentokoo

    Sentokoo

    Joined:
    Oct 6, 2020
    Posts:
    5
    What's the easiest way to make crops that mature after a set amount of time in Unity / C#? I want to plant a seed and then have it transform into a tiny plant, then a full-grown plant which can be harvested.

    I've tried to make one prefab instantiate another and then destroy itself, but that doesn't seem to be working. Plus I'm having trouble assigning position and rotation that way.

    I'm guessing there is probably an easier way to just transform/swap/replace one prefab for another, in the same location, once a set amount of time has passed??

    I've looked through tons of Youtube tutorials with the keywords Unity + stardew, farming, or anything similar. All I've found is devlogs showing off crop growth but no tutorials or code displayed. (I suspect some of them are just using extremely slow animations -- but then I'm not sure how the game would know when a plant is ready to harvest or not.)
     
  2. Derekloffin

    Derekloffin

    Joined:
    Mar 14, 2018
    Posts:
    322
    what problem are you having with the prefab creating another prefab then destroying itself?

    Me personally, I'd probably just have a script control which sprite is displayed for the crop, but if you're doing complex stuff like animation that may not be a great way to go.
     
  3. adehm

    adehm

    Joined:
    May 3, 2017
    Posts:
    369
    Easiest way is to just update the sprite and using a variable that indicates growth stage.


    First I create a script to handle drawing the plants.

    Code (CSharp):
    1.  
    2. public class PlantAnimator
    3. {
    4.     //lets declare memory for three sprites
    5.    Sprite[] tomato = new Sprite[3];
    6.    //called when project loads or when first gameplay scene loads
    7.    public void LoadGraphics()
    8.    {
    9.       //lets load graphics for a three level tomato
    10.       for(int i = 0; i < 3; i++)
    11.       {
    12.          //put graphics into your project's Assets\Resources folder
    13.          //in my example I put folders \Graphics\Plants\Tomato as well
    14.          //"Tomato0" "Tomato1" "Tomato2"
    15.          Resources.Load<Sprite>("Graphics/Plants/Tomato" + i);
    16.       }
    17.    }
    18.    //a function to call when you want to update your sprite
    19.    public void UpdateTomatoGraphic(SpriteRenderer sr, int plantStage)
    20.    {
    21.       sr.sprite = tomato[plantStage];
    22.    }
    23. }

    Then whenever I want to update a tomato plant I would call our UpdateTomatoGraphic function.

    Code (CSharp):
    1. plantAnimator.UpdateTomatoGraphic(currentTomatoSpriteRenderer, currentTomatoStage);
     
    Last edited: Oct 15, 2020
  4. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,009
    You can use flip-book (animation sheets).
     
  5. Cornysam

    Cornysam

    Joined:
    Feb 8, 2018
    Posts:
    1,343
    Here is a video I used to learn about using real world time for events. This one is for how much time has passed since last playing the game but the same logic can be applied to your crops. After X seconds, whether the apps is running or closed, the sprite can update. Using adehm's logic above is probably best to update the sprite at each stage so you arent instantiating when all you need to do is change the sprite in the sprite renderer component.

     
  6. adehm

    adehm

    Joined:
    May 3, 2017
    Posts:
    369
    A simple way of counting time would be to use a float for a timer. Let's imagine each plant has its own script for simplicity but I prefer arrays to hold all of the plants.

    If counting time.

    Code (CSharp):
    1. float timer = 0;
    2. float duration = 1; //1 second
    3. void Update()
    4. {
    5.    timer+=Time.deltaTime;
    6.    if(timer>=duration) //1 second has passed
    7.    {
    8.       timer-=duration;
    9.    }
    10. }

    But as in your game you would be counting days since planted right? So would be even simplier because every day just talk to your plants and add the day.
    Code (CSharp):
    1. int daysSincePlanted = 0;
    2. public void UpdateDay()
    3. {
    4.     daysSincePlanted++;
    5. }
    6.  
     
    Last edited: Oct 15, 2020
  7. Sentokoo

    Sentokoo

    Joined:
    Oct 6, 2020
    Posts:
    5
    I'm not sure how to get the second prefab to know where to instantiate. The problem is that the first prefab doesn't exist when the scene starts, until it's placed by the player. So I'm not sure how to refer to an object position/rotation that isn't in the scene yet.

    Here is how the initial seed gets planted, which works fine:

    Code (CSharp):
    1.  
    2. public Transform placePoint;
    3. //placePoint is an empty GameObject on the player that indicates where a seed should be planted
    4. public GameObject seedPrefab;
    5.  
    6. void Update()
    7.     {
    8.         if (Input.GetButtonDown("Fire1"))
    9.         {
    10.             Plant();
    11.         }
    12.         }
    13.  
    14.         void Plant (){
    15.             //planting logic
    16.             Instantiate(seedPrefab, placePoint.position, placePoint.rotation);
    17.         }
    18.  
    The problem is turning the seed into a plant in another script:

    Code (CSharp):
    1.  
    2. public GameObject seed;
    3. public GameObject seedPrefab;
    4.  public GameObject plantPrefab;
    5.  
    6.     void Update()
    7.     {
    8.         Invoke("Grow", 2);
    9.     }
    10.  
    11.     void Grow()
    12.     {Instantiate(plantPrefab,seedPrefab.position, seedPrefab.rotation)
    13.      Destroy(gameObject);
    14.     }
    15.  
    16.  
    I get an error on the .position and .rotation saying "GameObject does not contain a definition for "position" and no accessible extention method "position" accepting a first argument of type "GameObject" could be found (are you missing a using directive or an assembly reference?)"

    If I change "public GameObject seedPrefab" to "public Transform seedPrefab" the error goes away, but the plant isn't spawning where the seed was.

    I think I might've had an if statement in the update method yesterday, but I forget what it was now. Now I realize it's just invoking a random plant at a random position in the scene without regard for where the seed is, or if there is one.

    I might need an if statement for the Invoke command, which then starts the Grow method if a seed exists? But then I worry how I'm going to differentiate between different seeds if there are multiple planted on the screen at a time.

    Maybe I need to figure out how to use a sprite renderer command and not instantiating or using prefabs at all. Sorry I'm so new at this that I dont even know where to start.
     
    Last edited: Oct 15, 2020
  8. Derekloffin

    Derekloffin

    Joined:
    Mar 14, 2018
    Posts:
    322
    To get a game object's position, you have to first refer to the transform element.

    So transform.position, and transform.rotation. So just copy that along to the new prefab.

    You tend to use transform quite a bit as it is a link between subordinate scripts and the main game object they are attached too.
     
  9. Cornysam

    Cornysam

    Joined:
    Feb 8, 2018
    Posts:
    1,343
    I think it will be much easier to work with. The main reason is think about it this way, The Seed and Flower are technically the same thing, they just look different and have small behavior differences. So lets say you have 4 phases: Seed>Sprout>Baby Plant>Fully Grown. All you need are 4 sprites and when each phase occurs, you change the sprite (see link below). Now, if you want to change behavior, you can add and remove scripts from that gameobject at any phase as well.

    Another benefit is this. If you have 4 phases and continue with your Prefab instantiation, then you will still need 4 sprites AND 4 separate gameobjects to keep track of. If you just mess with the sprites, you have 4 sprites and 1 gameobject prefab. You go from 8 objects to keep track of to 5. That can get really messy if you have 10 plants or more. I hope this makes sense.

    https://gamedevbeginner.com/how-to-change-a-sprite-from-a-script-in-unity-with-examples/