Creating a Farming RPG in Unity - Part 10

Creating a Farming RPG (like Harvest Moon) in Unity — Part 10: Scene Transitions

This article is a part of the series:
Creating a Farming RPG (like Harvest Moon) in Unity

Ever wanted to create a game like Harvest Moon in Unity? Check out Part 10 of our guide here, where we go through how to set up scene transitions. You can also find Part 9 of our guide here, where we went through how to improve on our current Inventory system.

A link to a package containing the project files up to Part 10 of this tutorial series can also be found at the end of this article, exclusive to Patreon supporters only.

Video authored by Jonathan Teo, edited and subtitled by Hsin Yi.

1. Downloading new assets

We are going to start off with downloading some assets to put into our scene.

This tutorial is using POLYGON Farm – Low Poly 3D Art by Synty. Alternatively, we also recommend Low Poly Farm Pack and Low Poly Cartoon House Interiors.

Recommended asset packs for harvest moon
Our recommendations.

Once you have downloaded the assets, move them into the Imported Assets folder and create 3 main scenes:

  1. Player’s House
  2. Farm
  3. Town

Then set up these scenes with the imported assets by adding the models to your liking, or you can follow along with the time lapse of us setting up the scenes.

Shown: Time lapse of how the scene was crafted.

2. Keeping Essential GameObjects persistent

Before creating a script to for the transitions, we would first need to make some things persistent throughout all the scenes. This is to prevent losing data on anything important, such as the player’s inventory.

Create a new GameObject Essentials and parent the following GameObjects to it:

  1. Player
  2. Main Camera
  3. Directional Light
  4. Manager
  5. EventSystem
Parenting GameObjects to Essentials GameObject
The created Prefab.

Then, make them a prefab.

Essentials prefab
Prefabs in Essentials such as Player will still be affected by changes made to original Player prefab

Create a new folder under Scripts called Scene Transition and before creating a class called SceneTransitionManager in the folder. Make it a singleton class.

Creating scene transition folder

To make the object persistent throughout the scenes, use DontDestroyOnLoad(gameObject). However, this will create clones of the GameObject when swapping scene since the previous one was not destroyed. Thus, we also need to destroy them. To do that, add the following code to SceneTransitionManager:

SceneTransitionManager.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneTransitionManager : MonoBehaviour
{
    public static SceneTransitionManager Instance;

    private void Awake()
    {
        //If there is more than 1 instance, destroy GameObject
        if(Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            //Set the static instance to this instance
            Instance = this; 
        }

        //Make the gameobject persistent across scenes
        DontDestroyOnLoad(gameObject);

}

Put this script into the Essentials GameObject itself and not the Manager GameObject.

Adding script to Essentials GameObject

3. Scene Transition

Moving on to scene transitions, we would be going through how to teleport players from one scene to the next. This leads us to our next question:

How do we know which location to switch to when loading scene?

We solve this by representing all the scenes by using an enum. Call the enumLocation“, and name the elements in it exactly the same as the scene names. Then, create another variable to track the current location.

Location enum naming
Name the enums the same as the scene names

SceneTransitionManager.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneTransitionManager : MonoBehaviour
{
    public static SceneTransitionManager Instance;

    //The scenes the player can enter
    public enum Location { Farm, PlayerHome, Town}
    public Location currentLocation;


    private void Awake()
    {
        ...
    }
}

a. Switch Locations

To switch scenes, first add the code using UnityEngine.SceneManagement. Then, create a function to switch locations that takes in Location as a parameter. Remember to add the top line, otherwise your code will have an error. Add the following to SceneTransitionManager:

UnityEngine.SceneManagement is the system in Unity that handles all the scenes. It is most commonly used to get and load scenes. It also allows you to handle other scenes asynchronously.

SceneTransitionManager.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTransitionManager : MonoBehaviour
{
    public static SceneTransitionManager Instance;

    //The scenes the player can enter
    public enum Location { Farm, PlayerHome, Town}
    public Location currentLocation;


    private void Awake()
    {
        ...
    }

    //Switch the player to another scene
    public void SwitchLocation(Location locationToSwitch)
    {
        SceneManager.LoadScene(locationToSwitch.ToString());
    } 
}

Now that we have the code for switching scene, we also need a trigger to let the code know when to transfer the player to another scene. To do that, we are going to create a designated area that will switch scenes when entered.

First, create a new GameObject called Locations. This will be the parent GameObject for all entry and start points in the scene.

For each entry point, create a GameObject and rename it to “To [Location name]“. Then, add a box collider component to it and set it to Is Trigger. Place these GameObjects at the place where the player is going to be to switch the scenes. Remember to make these GameObjects children of the Locations GameObject.

The option Is Trigger in colliders allow any code that are related to triggers (OnTriggerEnter(), OnTriggerStay(), etc.) to run whenever another object with a collider enters the trigger zone.

Setting up entry point

Next, create a new class in Scene Transition folder and name it LocationEntryPoint.

In the script, use a Location variable to determine the scene to switch to. Thereafter, use OnTriggerEnter() to switch player over to the scene of choice if the entity entering the trigger object is the player.

LocationEntryPoint.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LocationEntryPoint : MonoBehaviour
{
    [SerializeField]
    SceneTransitionManager.Location locationToSwitch;

    private void OnTriggerEnter(Collider other)
    {
        //Check if the collider belongs to the player
        if(other.tag == "Player")
        {
            //Switch scenes to the location of the entry point
            SceneTransitionManager.Instance.SwitchLocation(locationToSwitch);
        }
    }


}

Add this script to all of the entry points and set the corresponding location that each entry point is supposed to transfer the player to.

Assigning switching location

Next, open the Player prefab and check if the tag is set to “Player”. If not, assign the player tag to it. Ensure the spelling of the tag is the same as the one in the script, including capitalisation of letters.

Checking player tag

Lastly, make sure the scenes are in the build settings and we now have a basic working scene transition.

Adding scenes to build setting
You can drag and drop scenes from the scene folder into the “Scenes in Build” if they are not in there

b. Start Points

Currently, the player is at the same position as in the previous scene after the scenes have been switched. We are going to fix this by keeping track of where the player should start in each scene.

Create another GameObject for the start location and rename it to “From [Location Name]“. Place this in a place where you think the player should start in each scene. Take note to not overlap this GameObject with the area that your entry point covers, else you will be continuously changing scenes.

Start and Entry point comparism

You might notice that some scenes have multiple points of entry. For instance, the Farm scene has the start points from the town and the player’s house. To keep track of them, we will utilize a struct. Create a struct called StartPoint. It should take in the Transform that we want the player to start from, as well as the previous Location that the player had came from.

StartPoint.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public struct StartPoint 
{
    //Location the player is entering from
    public SceneTransitionManager.Location enteringFrom;

    //The transform the player should start in
    public Transform playerStart;
}

Next, create a new singleton class called LocationManager. This script will manage the List of startPoints by have a function called GetPlayerStartingPosition(). This function will use the Player’s Location to retrieve the correct startingPoint from the list of startPoints. Add the following code into the script:

LocationManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LocationManager : MonoBehaviour
{
    public static LocationManager Instance { get; private set; }

    public List<StartPoint> startPoints;

    private void Awake()
    {
        //If there is more than one instance, destroy the extra
        if (Instance != null && Instance != this)
        {
            Destroy(this);
        }
        else
        {
            //Set the static instance to this instance
            Instance = this;
        }
    }
    
    //Find the player's start position based on where he's coming from
    public Transform GetPlayerStartingPosition(SceneTransitionManager.Location enteringFrom)
    {
        //Tries to find the matching startpoint based on the Location given
        StartPoint startingPoint = startPoints.Find(x => x.enteringFrom == enteringFrom);

        //Return the transform
        return startingPoint.playerStart; 
    }
}

How the GetPlayerStartingPosition() function works: It takes the list of StartPoints and goes through the array to find an entry that contains the location that we are trying to access. Since both the list and the item we are requesting are from the same enum, it will always find a match.

c. Connecting Start Points and switching location

With the StartPoints set up, we now are going to connect this with our SceneTransitionManager so that when we switch scenes, the starting points will switch.

To do this, we are making use of a callback: SceneManager.sceneLoaded += OnLocationLoad, where OnLocationLoad() is a new function. This is how the function works:

  1. This function will find our current location based on the scene name and check if it is the same as the old scene.
    1. If it is the same, it will use return to get out of the function so we do not set our position back to the starting position.
    2. Otherwise, find the correct starting point and set the player’s transform and location to it; before setting the current location to our new location.

SceneTransitionManager.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTransitionManager : MonoBehaviour
{
    public static SceneTransitionManager Instance;

    //The scenes the player can enter
    public enum Location { Farm, PlayerHome, Town}
    public Location currentLocation;

    //The player's transform
    Transform playerPoint;
    

    private void Awake()
    {
        //If there is more than 1 instance, destroy GameObject
        if(Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            //Set the static instance to this instance
            Instance = this; 
        }

        //Make the gameobject persistent across scenes
        DontDestroyOnLoad(gameObject);

        //OnLocationLoad will be called when the scene is loaded
        SceneManager.sceneLoaded += OnLocationLoad;

        //Find the player's transform
        playerPoint = FindObjectOfType<PlayerController>().transform;
    }

    //Switch the player to another scene
    public void SwitchLocation(Location locationToSwitch)
    {
        SceneManager.LoadScene(locationToSwitch.ToString());
    }

    //Called when a scene is loaded
    public void OnLocationLoad(Scene scene, LoadSceneMode mode)
    {
        //The location the player is coming from when the scene loads
        Location oldLocation = currentLocation;

        //Get the new location by converting the string of our current scene into a Location enum value
        Location newLocation = (Location) Enum.Parse(typeof(Location), scene.name);

        //If the player is not coming from any new place, stop executing the function
        if (currentLocation == newLocation) return; 

        //Find the start point
        Transform startPoint = LocationManager.Instance.GetPlayerStartingPosition(oldLocation);

        //Change the player's position to the start poin
        playerPoint.position = startPoint.position;
        playerPoint.rotation = startPoint.rotation;

        //Save the current location that we just switched to
        currentLocation = newLocation; 
    }
}

Although we have set the player’s position and rotation, we will still not teleport to the correct destination — reason being the CharacterController component works differently such that setting the Transform of a GameObject when it is enabled will not override the position that the CharacterController has set. To fix this, disable this component and change the player’s position before reenabling it in SceneTransitionManager:

SceneTransitionManager.cs

    //Called when a scene is loaded
    public void OnLocationLoad(Scene scene, LoadSceneMode mode)
    {
        //The location the player is coming from when the scene loads
        Location oldLocation = currentLocation;

        //Get the new location by converting the string of our current scene into a Location enum value
        Location newLocation = (Location) Enum.Parse(typeof(Location), scene.name);

        //If the player is not coming from any new place, stop executing the function
        if (currentLocation == newLocation) return; 

        //Find the start point
        Transform startPoint = LocationManager.Instance.GetPlayerStartingPosition(oldLocation);

        //Disable the player's CharacterController component
        CharacterController playerCharacter = playerPoint.GetComponent<CharacterController>();
        playerCharacter.enabled = false; 
        
        //Change the player's position to the start point
        playerPoint.position = startPoint.position;
        playerPoint.rotation = startPoint.rotation;

        //Re-enable player character controller so he can move
        playerCharacter.enabled = true; 

        //Save the current location that we just switched to
        currentLocation = newLocation; 
    }
}

We have completed allowing the player to switch locations. Remember to create all the start points and entry points in all the scenes for everything to work properly!

4. Transition UI feedback

Next, we are going to smooth out the transitions by creating a fade in and fade out effect as UI feedback.

Under the canvas in Essentials, create 2 UI Images: FadeIn and FadeOut. Make these images colour black.

Creating fade UI images

Create a new folder under Animations and name it “UI”. Create animations for both UI and name them “fade in” and “fade out” respectively.

For “fade out” animation, set the image’s alpha to 0 at 0:00 seconds and at the half second mark (0:50), set the alpha to 1. Do the opposite for the “fade in” animation to achieve the correct effect [alpha is 1 at 0:00 and alpha is 0 at 0:50]. Ensure that loop time is disabled for these 2 animations so that the animation would not replay upon completion.

Fade animations without loop time
The animation controllers will be automatically created for you when you create the animations
Creating fade animation timeline

Thereafter, create a new class called ParentedAnimationEvent with a function called NotifyAncestors() to use SendMessageUpwards(). This will allow the animations to send messages to their parent GameObject.

The default AnimationEvent will send message to the GameObject itself, but we want to send it upwards.

ParentedAnimationEvent.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParentedAnimationEvent : MonoBehaviour
{
    //Sends the message upwards
   public void NotifyAncestors(string message)
   {
        SendMessageUpwards(message);
   }
}

Add this script to both FadeIn and FadeOut GameObject. Now, under their animations, at the half second mark, create an animation event and call NotifyAncestor().

Under the Parameter, send OnFadeInComplete and OnFadeOutComplete for the FadeIn and FadeOut animations respectively.

Create fade animation event
Right click on the space grey space shown above to create an animation event

In UIManager, create 2 new GameObject variables fadeIn and fadeOut for the UI images. Disable the fadeIn GameObject under OnFadeInComplete so it doesn’t interfere with the other UI elements.

UIManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; 

public class UIManager : MonoBehaviour, ITimeTracker
{
    public static UIManager Instance { get; private set; }
    [Header("Status Bar")]
    //Tool equip slot on the status bar
    public Image toolEquipSlot;
    //Tool Quantity text on the status bar 
    public Text toolQuantityText; 
    //Time UI
    public Text timeText;
    public Text dateText; 


    [Header("Inventory System")]
    //The inventory panel
    public GameObject inventoryPanel;

    //The tool equip slot UI on the Inventory panel
    public HandInventorySlot toolHandSlot; 

    //The tool slot UIs
    public InventorySlot[] toolSlots;

    //The item equip slot UI on the Inventory panel
    public HandInventorySlot itemHandSlot;

    //The item slot UIs
    public InventorySlot[] itemSlots;

    //Item info box
    public Text itemNameText;
    public Text itemDescriptionText;

    [Header("Screen Transitions")]
    public GameObject fadeIn;
    public GameObject fadeOut; 


    private void Awake()
    {
        ...
    }

    private void Start()
    {
        ...
    }
    #region Fadein Fadeout Transitions

    public void FadeOutScreen()
    {
        fadeOut.SetActive(true);
    }

    public void FadeInScreen()
    {
        fadeIn.SetActive(true); 
    }

    public void OnFadeInComplete()
    {
        //Disable Fade in Screen when animation is completed
        fadeIn.SetActive(false); 
    }



    #endregion
    //Iterate through the slot UI elements and assign it its reference slot index
    public void AssignSlotIndexes()
    {
        ...
    }

    //Render the inventory screen to reflect the Player's Inventory. 
    public void RenderInventory()
    {
        ...
    }

    //Iterate through a slot in a section and display them in the UI
    void RenderInventoryPanel(ItemSlotData[] slots, InventorySlot[] uiSlots)
    {
        ...
    }

    public void ToggleInventoryPanel()
    {
        ...
    }

    //Display Item info on the Item infobox
    public void DisplayItemInfo(ItemData data)
    {
        ...
    }

    //Callback to handle the UI for time
    public void ClockUpdate(GameTimestamp timestamp)
    {
        ...
    }
}

In the Unity window, under the Essentials GameObject in your scene, disable FadeOut. At the same time, assign both FadeIn and FadeOut to their respective variable under UIManager.

Assigning fade variables
Setting up Scene transitions.

To play the animation before changing scenes, we are going to utilise a coroutine to delay the scene change. In the coroutine, disable the CharacterController to prevent the player from moving during the feedback and transition. Then, name a boolean screenFadedOut. This keeps track of whether the animation is playing and will turn to true once it is completed. This is when the scene will change. Add the following code to SceneTransitionManager to accomplish this:

SceneTransitionManager.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTransitionManager : MonoBehaviour
{
    public static SceneTransitionManager Instance;

    //The scenes the player can enter
    public enum Location { Farm, PlayerHome, Town}
    public Location currentLocation;

    //The player's transform
    Transform playerPoint;
    
    //Check if the screen has finished fading out
    bool screenFadedOut; 

    private void Awake()
    {
        ...
    }

    //Switch the player to another scene
    public void SwitchLocation(Location locationToSwitch)
    {
        //Call a fadeout
        UIManager.Instance.FadeOutScreen();
        screenFadedOut = false;
        SceneManager.LoadScene(locationToSwitch.ToString());
        StartCoroutine(ChangeScene(locationToSwitch)); 
    }

    IEnumerator ChangeScene(Location locationToSwitch)
    {
        //Disable the player's CharacterController component
        CharacterController playerCharacter = playerPoint.GetComponent<CharacterController>();
        playerCharacter.enabled = false;
        //Wait for the scene to finish fading out before loading the next scene
        while (!screenFadedOut)
        {
            yield return new WaitForSeconds(0.1f); 
        }
    }

    //Called when the screen has faded out
    public void OnFadeOutComplete()
    {
        screenFadedOut = true;
        
    }

    
    //Called when a scene is loaded
    public void OnLocationLoad(Scene scene, LoadSceneMode mode)
    {
        ...
    }
}

Thereafter, in UIManager, create a new function ResetFadeDefaults() to restore the state of which fadeIn and fadeOut GameObjects should be active at the start of a scene (fadeIn should be enabled so the scene can starts with a fade in effect).

UIManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; 

public class UIManager : MonoBehaviour, ITimeTracker
{
    public static UIManager Instance { get; private set; }
    [Header("Status Bar")]
    //Tool equip slot on the status bar
    public Image toolEquipSlot;
    //Tool Quantity text on the status bar 
    public Text toolQuantityText; 
    //Time UI
    public Text timeText;
    public Text dateText; 


    [Header("Inventory System")]
    //The inventory panel
    public GameObject inventoryPanel;

    //The tool equip slot UI on the Inventory panel
    public HandInventorySlot toolHandSlot; 

    //The tool slot UIs
    public InventorySlot[] toolSlots;

    //The item equip slot UI on the Inventory panel
    public HandInventorySlot itemHandSlot;

    //The item slot UIs
    public InventorySlot[] itemSlots;

    //Item info box
    public Text itemNameText;
    public Text itemDescriptionText;

    [Header("Screen Transitions")]
    public GameObject fadeIn;
    public GameObject fadeOut;


    private void Awake()
    {
        ...
    }

    private void Start()
    {
        ...
    }
    #region Fadein Fadeout Transitions

    public void FadeOutScreen()
    {
        fadeOut.SetActive(true);
    }

    public void FadeInScreen()
    {
        fadeIn.SetActive(true); 
    }

    public void OnFadeInComplete()
    {
        //Disable Fade in Screen when animation is completed
        fadeIn.SetActive(false); 
    }

    //Reset the fadein fadeout screens to their default positions
    public void ResetFadeDefaults()
    {
        fadeOut.SetActive(false);
        fadeIn.SetActive(true);
    }



    #endregion
    //Iterate through the slot UI elements and assign it its reference slot index
    public void AssignSlotIndexes()
    {
        ...
    }

    //Render the inventory screen to reflect the Player's Inventory. 
    public void RenderInventory()
    {
        ...
    }

    //Iterate through a slot in a section and display them in the UI
    void RenderInventoryPanel(ItemSlotData[] slots, InventorySlot[] uiSlots)
    {
        ...
    }

    public void ToggleInventoryPanel()
    {
        ...
    }

    //Display Item info on the Item infobox
    public void DisplayItemInfo(ItemData data)
    {
        ...
    }

    //Callback to handle the UI for time
    public void ClockUpdate(GameTimestamp timestamp)
    {
        ...
    }
}

Lastly, go back to SceneTransitionManager and add a reset for screenFadedOut and call ResetFadeDefaults() so that the variables are reset correctly before transferring the Player to the next scene.

SceneTransitionManager.cs

    IEnumerator ChangeScene(Location locationToSwitch)
    {
        //Disable the player's CharacterController component
        CharacterController playerCharacter = playerPoint.GetComponent<CharacterController>();
        playerCharacter.enabled = false;
        //Wait for the scene to finish fading out before loading the next scene
        while (!screenFadedOut)
        {
            yield return new WaitForSeconds(0.1f); 
        }

        //Reset the boolean
        screenFadedOut = false;
        UIManager.Instance.ResetFadeDefaults();
        SceneManager.LoadScene(locationToSwitch.ToString());
        
    }

Conclusion

Today, we have covered how to change scenes with a fading transition effect.

If you are a Patreon supporter, you can download the project files for what we have 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:

SceneTransitionManager.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTransitionManager : MonoBehaviour
{
    public static SceneTransitionManager Instance;

    //The scenes the player can enter
    public enum Location { Farm, PlayerHome, Town}
    public Location currentLocation;

    //The player's transform
    Transform playerPoint;
    
    //Check if the screen has finished fading out
    bool screenFadedOut; 

    private void Awake()
    {
        //If there is more than 1 instance, destroy GameObject
        if(Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            //Set the static instance to this instance
            Instance = this; 
        }

        //Make the gameobject persistent across scenes
        DontDestroyOnLoad(gameObject);

        //OnLocationLoad will be called when the scene is loaded
        SceneManager.sceneLoaded += OnLocationLoad;

        //Find the player's transform
        playerPoint = FindObjectOfType<PlayerController>().transform;
    }

    //Switch the player to another scene
    public void SwitchLocation(Location locationToSwitch)
    {
        //Call a fadeout
        UIManager.Instance.FadeOutScreen();
        screenFadedOut = false;
        StartCoroutine(ChangeScene(locationToSwitch)); 
    }

    IEnumerator ChangeScene(Location locationToSwitch)
    {
        //Disable the player's CharacterController component
        CharacterController playerCharacter = playerPoint.GetComponent<CharacterController>();
        playerCharacter.enabled = false;
        //Wait for the scene to finish fading out before loading the next scene
        while (!screenFadedOut)
        {
            yield return new WaitForSeconds(0.1f); 
        }

        //Reset the boolean
        screenFadedOut = false;
        UIManager.Instance.ResetFadeDefaults();
        SceneManager.LoadScene(locationToSwitch.ToString());
        
    }

    //Called when the screen has faded out
    public void OnFadeOutComplete()
    {
        screenFadedOut = true;
        
    }

    
    //Called when a scene is loaded
    public void OnLocationLoad(Scene scene, LoadSceneMode mode)
    {
        //The location the player is coming from when the scene loads
        Location oldLocation = currentLocation;

        //Get the new location by converting the string of our current scene into a Location enum value
        Location newLocation = (Location) Enum.Parse(typeof(Location), scene.name);

        //If the player is not coming from any new place, stop executing the function
        if (currentLocation == newLocation) return; 

        //Find the start point
        Transform startPoint = LocationManager.Instance.GetPlayerStartingPosition(oldLocation);

        //Disable the player's CharacterController component
        CharacterController playerCharacter = playerPoint.GetComponent<CharacterController>();
        playerCharacter.enabled = false; 

        //Change the player's position to the start point
        playerPoint.position = startPoint.position;
        playerPoint.rotation = startPoint.rotation;

        //Re-enable player character controller so he can move
        playerCharacter.enabled = true;

        //Save the current location that we just switched to
        currentLocation = newLocation; 
    }
}

LocationEntryPoint.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LocationEntryPoint : MonoBehaviour
{
    [SerializeField]
    SceneTransitionManager.Location locationToSwitch;

    private void OnTriggerEnter(Collider other)
    {
        //Check if the collider belongs to the player
        if(other.tag == "Player")
        {
            //Switch scenes to the location of the entry point
            SceneTransitionManager.Instance.SwitchLocation(locationToSwitch);
        }
    }


}

LocationManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LocationManager : MonoBehaviour
{
    public static LocationManager Instance { get; private set; }

    public List<StartPoint> startPoints;

    private void Awake()
    {
        //If there is more than one instance, destroy the extra
        if (Instance != null && Instance != this)
        {
            Destroy(this);
        }
        else
        {
            //Set the static instance to this instance
            Instance = this;
        }
    }
    
    //Find the player's start position based on where he's coming from
    public Transform GetPlayerStartingPosition(SceneTransitionManager.Location enteringFrom)
    {
        //Tries to find the matching startpoint based on the Location given
        StartPoint startingPoint = startPoints.Find(x => x.enteringFrom == enteringFrom);

        //Return the transform
        return startingPoint.playerStart; 
    }
}

StartPoint.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public struct StartPoint 
{
    //Location the player is entering from
    public SceneTransitionManager.Location enteringFrom;

    //The transform the player should start in
    public Transform playerStart;
}

ParentedAnimationEvent.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParentedAnimationEvent : MonoBehaviour
{
    //Sends the message upwards
   public void NotifyAncestors(string message)
   {
        SendMessageUpwards(message);
   }
}

UIManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; 

public class UIManager : MonoBehaviour, ITimeTracker
{
    public static UIManager Instance { get; private set; }
    [Header("Status Bar")]
    //Tool equip slot on the status bar
    public Image toolEquipSlot;
    //Tool Quantity text on the status bar 
    public Text toolQuantityText; 
    //Time UI
    public Text timeText;
    public Text dateText; 


    [Header("Inventory System")]
    //The inventory panel
    public GameObject inventoryPanel;

    //The tool equip slot UI on the Inventory panel
    public HandInventorySlot toolHandSlot; 

    //The tool slot UIs
    public InventorySlot[] toolSlots;

    //The item equip slot UI on the Inventory panel
    public HandInventorySlot itemHandSlot;

    //The item slot UIs
    public InventorySlot[] itemSlots;

    //Item info box
    public Text itemNameText;
    public Text itemDescriptionText;

    [Header("Screen Transitions")]
    public GameObject fadeIn;
    public GameObject fadeOut; 


    private void Awake()
    {
        //If there is more than one instance, destroy the extra
        if (Instance != null && Instance != this)
        {
            Destroy(this);
        }
        else
        {
            //Set the static instance to this instance
            Instance = this;
        }
    }

    private void Start()
    {
        RenderInventory();
        AssignSlotIndexes();

        //Add UIManager to the list of objects TimeManager will notify when the time updates
        TimeManager.Instance.RegisterTracker(this); 
    }
    #region Fadein Fadeout Transitions

    public void FadeOutScreen()
    {
        fadeOut.SetActive(true);
    }

    public void FadeInScreen()
    {
        fadeIn.SetActive(true); 
    }

    public void OnFadeInComplete()
    {
        //Disable Fade in Screen when animation is completed
        fadeIn.SetActive(false); 
    }

    //Reset the fadein fadeout screens to their default positions
    public void ResetFadeDefaults()
    {
        fadeOut.SetActive(false);
        fadeIn.SetActive(true);
    }



    #endregion
    //Iterate through the slot UI elements and assign it its reference slot index
    public void AssignSlotIndexes()
    {
        for (int i =0; i<toolSlots.Length; i++)
        {
            toolSlots[i].AssignIndex(i);
            itemSlots[i].AssignIndex(i);
        }
    }

    //Render the inventory screen to reflect the Player's Inventory. 
    public void RenderInventory()
    {
        //Get the respective slots to process
        ItemSlotData[] inventoryToolSlots = InventoryManager.Instance.GetInventorySlots(InventorySlot.InventoryType.Tool);
        ItemSlotData[] inventoryItemSlots = InventoryManager.Instance.GetInventorySlots(InventorySlot.InventoryType.Item);

        //Render the Tool section
        RenderInventoryPanel(inventoryToolSlots, toolSlots);

        //Render the Item section
        RenderInventoryPanel(inventoryItemSlots, itemSlots);

        //Render the equipped slots
        toolHandSlot.Display(InventoryManager.Instance.GetEquippedSlot(InventorySlot.InventoryType.Tool));
        itemHandSlot.Display(InventoryManager.Instance.GetEquippedSlot(InventorySlot.InventoryType.Item));

        //Get Tool Equip from InventoryManager
        ItemData equippedTool = InventoryManager.Instance.GetEquippedSlotItem(InventorySlot.InventoryType.Tool);

        //Text should be empty by default
        toolQuantityText.text = "";
        //Check if there is an item to display
        if (equippedTool != null)
        {
            //Switch the thumbnail over
            toolEquipSlot.sprite = equippedTool.thumbnail;

            toolEquipSlot.gameObject.SetActive(true);

            //Get quantity 
            int quantity = InventoryManager.Instance.GetEquippedSlot(InventorySlot.InventoryType.Tool).quantity;
            if (quantity > 1)
            {
                toolQuantityText.text = quantity.ToString();
            }
            return;
        }

        toolEquipSlot.gameObject.SetActive(false);
    }

    //Iterate through a slot in a section and display them in the UI
    void RenderInventoryPanel(ItemSlotData[] slots, InventorySlot[] uiSlots)
    {
        for (int i = 0; i < uiSlots.Length; i++)
        {
            //Display them accordingly
            uiSlots[i].Display(slots[i]);
        }
    }

    public void ToggleInventoryPanel()
    {
        //If the panel is hidden, show it and vice versa
        inventoryPanel.SetActive(!inventoryPanel.activeSelf);

        RenderInventory();
    }

    //Display Item info on the Item infobox
    public void DisplayItemInfo(ItemData data)
    {
        //If data is null, reset
        if(data == null)
        {
            itemNameText.text = "";
            itemDescriptionText.text = "";

            return;
        }

        itemNameText.text = data.name;
        itemDescriptionText.text = data.description; 
    }

    //Callback to handle the UI for time
    public void ClockUpdate(GameTimestamp timestamp)
    {
        //Handle the time
        //Get the hours and minutes
        int hours = timestamp.hour;
        int minutes = timestamp.minute; 

        //AM or PM
        string prefix = "AM ";
        
        //Convert hours to 12 hour clock
        if (hours > 12)
        {
            //Time becomes PM 
            prefix = "PM ";
            hours = hours - 12;
            Debug.Log(hours);
        }

        //Format it for the time text display
        timeText.text = prefix + hours + ":" + minutes.ToString("00");

        //Handle the Date
        int day = timestamp.day;
        string season = timestamp.season.ToString();
        string dayOfTheWeek = timestamp.GetDayOfTheWeek().ToString();

        //Format it for the date text display
        dateText.text = season + " " + day + " (" + dayOfTheWeek +")";

    }
}

There are 5 comments:

  1. i got this warning When I enter the player’s house and then leave the house and return to the farm:
    There can be only one active Event System.
    i’m usign Unity 2022.3.8f1c1.
    What may be the cause?

    1. Sorry, this is my problem. The assets I introduced have additional EventSystem. I encountered another problem. When I switched scenes for the first time, the screen became very dark, and everything in the scene became much darker. I don’t know what’s going on

Leave a Reply

Your email address will not be published. Required fields are marked *

Note: You can use Markdown to format your comments.

This site uses Akismet to reduce spam. Learn how your comment data is processed.