I've been thinking about how Zelda: Majora's Mask manages all its character events during the day and thinking about the best way to implement this. Initially I thought of each character entity polling the timer, or the timer sending out events to subscribers of it, but both of these seem wasteful. What are some approaches to solving this?
3 Answers
You're overthinking this. Polling the in-game time each frame is nothing compared to the other things your CPU does each frame. You would need millions upon millions of entities to see the a performance impact (think about it, your CPU runs on Gigaherz speed. That means billions of operations each second). Just check each frame if the time is what the entity expects and then act on it:
if getGameTime() == timeOfDay("12:00"):
...
You can also have a master "Schedule" that acts as a priority queue. This automatically sorts the next item to occur so that it's the first item in the collection (often more cheaply than sorting the whole collection on every addition).
Each thing that needs to happen can register its next occurrence in the schedule along with a payload of what to do at that time - like setting a trigger/flag variable or firing an event or calling a callback method.
Each frame, your scheduler checks the timestamp of the next item in the queue against the current time. If it's still in the future, it leaves it alone. Otherwise, it pops it out of the queue and executes its payload. It keeps popping scheduled items out of the queue until it hits an item that still belongs in the future, and at that point returns back to the main game loop until next frame's schedule update.
This reduces the workload from being proportional to the number of events to being proportional to the number of events that occur/schedule themselves on a single frame.
If your schedule is unchanging, you can instead just make one sorted list, and keep track of your current index in the list, advancing the index (and wrapping around for the next day) rather than popping items from the priority queue.
If you want to retain separation of concerns in an object-oriented system, and want a high amount of flexibility in how objects react to time, this would be a good use case for the Observer Pattern and the Reactive Extensions for your chosen programming language. (Although the pattern can be implemented without the extensions, it does handle some of the plumbing.)
The advantage of the observer pattern for your use case is that you can have one object that is a "clock" that all other objects can observe. The clock only ticks; it generates observable events at a set cadence (which can and should be independent of frame rate, as all game logic should be in most situations). Other objects that care about time can observe these ticks, which could be a payload with any amount of information (current in-game time, for example). This allows you to reuse the functionality among multiple classes and have one central "clock".
From there, you can wire up your observer classes. The Clock Tower can be wired up to the clock to display the time, and each other object that needs to react to time-based events can observe the central clock and react accordingly. This keeps behaviors associated with their owning classes and allows for the clock to be swapped out for any other compatible Observable (which eases testing, as you can make clocks for specific tests and your classes won't know the difference). This will ensure that every object knows exactly what time it is according to the central clock and stays in-sync.
how Zelda: Majora's Mask manages all its character events during the day
? Some of us have never played the game, so I'm not sure if you mean how their appearance changes, or their position/behaviour etc. \$\endgroup\$