Basic Game States and Creating a Day/Night Cycle
What is a state? A state is a variable that modifies one or multiple behaviors or actions. There are many different ways we use states, in creating enemy behaviors we use behavior states such as attacking, fleeing, patrolling, or chasing. If you were flipping a switch we could have an off state or an on state. In a basic state machine, we can only have one state true at a time. In a real life example, pretend you haven’t eaten all day. You would be in a hungry state, after going out to lunch and eating you would be in a full state. Being hungry affects your behaviors and actions. Being full affects your behaviors and actions differently. You cant be hungry and full at the same time. Obviously this is a very simplified example but it helps paint a picture of how states work. So how would you use this in a game? On a global scale, an example that comes to mind is the second generation on Pokémon games, Gold and Silver. This was the first time the developer, GameFreak, implemented a day and night cycle from the game. The way this worked was depending on what time it was in game, it would trigger one of two states: day and night. During the day you would find specific daytime monsters, the lighting would be bright and the music would play the standard day time music. When the in game time passed 6pm, night state would trigger, and the the nighttime monster would appear, the lighting would darken and the music would change to a night theme. Through one state machine, all these different behaviors are reacting to the state change. Lets look at how we could implement a day and night cycle using states.
Lets start with setting up our state machine. In our example we are going to create a few extra states so we can look at an example with four states. We will be using Morning, Day, Evening and Night. Start by creating a new class, I called mine TimeOfDayState. We will create a public enum called TimeOfDay, and enter in our states in the body. and a public TimeOfDay called currentTimeOfDay.
Now we can have our scripts react to this enum. I created a second class called EnvironmentManager that controls the environment, to quickly show how we can implement this state machine we just created.
you can see below, now based on the time of day it is, we have different logic that fires off. This is the most basic implementation of how a state machine works with beginner friendly code. If you want to see how to fully implement a game clock and use the state machine more efficiently I will be continuing below. I suggest you read a bit up on events and delegates and singleton pattern before going ahead. I will hopefully have some posts on those topics in the near future.
Intermediate lesson: Finishing the Time of Day State Machine.
Lets get this whole system working, in an optimized and efficient manner. I'm going to go from the start as we are going to implement things just a bit differently. We will be using a singleton pattern in our state machine for easy and efficient use. We will also change the protection levels of some variables so that it adheres better to professional programming standards.
Below is a diagram of how I want the program to look, and we will build the systems off of it:
First we need an easy way to reference this state machine, since we will only ever have one of these classes in the scene we will use a singleton pattern.
We will change the currentTimeOfDay variable to private so that it can not be modified by outside classes. We will then serialize the field so we can see it in the inspector.
We now need a way to set the time of day so we will create a a public method called ChangeTime, with TimeOfDay as a parameter called time. In the body we will set currentTimeOfDay to time. It should look like this:
We now need a way to get what _currentTimeOfDay is in other scripts, now that it is set to private. We will simply make a method that returns TimeOfDay called: GetCurrentTimeOfDay(). In the body, it will return _currentTimeOfDay.
Now outside scripts can get and set the state. Lets take a look at what we have so far. then we can create a time manager, that keeps track of game time.
Now lets come up with a way to calculate our game time. I will be using a simple couroutine called GameTimer to add 1(or 1 hour) to a float called gameTime every 10seconds. The day will range from 0 being midnight to 23 being 11pm. After 23, it will reset back to 0.
Now lets implement state changes based on time. We will say morning is from 5–10, day is from 11–15, Afternoon is from 16–19, and night is from 20–4.We will then check the value and tell the instance of our TimeOfDayState to change states. A switch statement will be the most efficient.
Now that our game timer is working we need to implement and event system so that anything that need to react to the state change can. We will go back to our TimeOfDayState for that.
We will now add a new delegate and event to TimeOfDayState. We will pass in a parameter of TimeOfDay called timeOfDay on the delegate. We will then implement it within the ChangeTime Function .
Now we just have any receiver we want subscribe to the event and we have a fully built in game time system. We will use the EnvironmentManager we created earlier, and subscribe to the event within there. Don't forget to unsubscribe on disable.
If you are getting an error its because we need to add the parameter of TimeOfDay to the OnTimeChange function from earlier. It should now look like the following.
Lastly lets make a switch statement to change behavior based on the event being called.
We now have a system that tracks the time of day, changes states based on that time, and sends out messages to other game objects on state changes. This is just one of many ways we can use states to our advantage!