Do you struggle with timing in your game? Ever feel like you might be repeating yourself? Expand your game-dev toolbox with timing patterns!
Sometimes as programmers we run into the same problems time and time again. Sometimes we run into other programmers having the same problems again and again on websites like StackOverflow. In order to deal with these repeatable problems, we come up with repeatable answers called “Patterns.”
Patterns were made famous by The Gang of Four, which Wikipedia tells me was either a group of Chinese communists, a band from the UK, or some software engineers. They created a bunch of general design patterns for software development. There are already a bajillion articles out there on these patterns, however, so I figured that in this post I’d introduce you to some very specific ones- namely timing patterns.
“Timing Patterns” may seem oddly specific, but I see people running into the problems addressed by these patterns on StackOverflow again and again and again.
You will learn:
- How to create reliable, consistent timers
- How to create unobtrusive cooldowns
- How to create flexible interpolations
Timing Patterns
What do first person shooters, massively multiplayer games, and simulators all have in common? They almost always feature mechanics which are functions of time. Granted, these functions are often very different (shooting bullets, ability cooldowns, resource gathering), but if you isolate the timing component they start to become very similar.
For example, I may want to give a weapon a rate of fire in a FPS, or I may want to give a player a super-ability which can only be used once a minute. They look very different, but both of these are examples of the Cooldown pattern which we’ll learn about later.
The interesting thing is from a coding perspective is that these patterns are examples of common functionality that “wraps” very uncommon functionality. This means when we instantiate a timing pattern, we’ll often pass in a lambda or anonymous class which gets invoked by the pattern at the appropriate time.
The Timer Pattern
Often in our games we want to ensure that something happens repeatedly at a fixed interval. Admittedly this is the simplest of the timing patterns, but it forms the base of the ones to come.
At first this sounds really easy to do, we simply check the time that has elapsed since our code last ran and once a certain amount of time has elapsed, we call it again:
I cheated and used Java’s own Timer class! Roughly every three seconds it prints “Hello World”- we could easily use it to regulate game loops, run animations, or schedule regularly occuring events (like spawning enemies). Unfortunately, like all easy things, this class introduces some complications:
First, it uses its own thread. This isn’t a bad thing per se, but it introduces another layer of complexity within our code (especially if your game requires a lot of these timers).Our code becomes harder to debug and we won’t always know the order things will happen in our code.
Second, it isn’t guaranteed to be accurate. This can be a problem for mechanics involving movement or physics since little imprecisions can add up over time.
We can address the first of these concerns by implementing our own version of the Timer:
Notice how we are discarding the “remainder” by reseting the timer to 0 when the interval is complete. Not only do these milliseconds add up over time, but we run the risk of actually skipping intervals if a resource intensive process (e.g. AI, rendering, loading resources) stops our Timer from being updated for any significant amount of time. Fortunately, we can make our timer a little smarter to account for this:
We’re pretty close to having our pattern, but there’s one final problem- our timer is based off of System.nanoTime()
which is using our computer’s clock to figure out the current time. It is often more helpful to determine how much time has elapsed outside of our timer and provide it a “virtual time” if you will:
- It allows us to pause the timer simply by not updating it- then when we resume it will pick up right where it left off.
- It allows us to pass the same difference in time to all of our timers, keeping them in sync
- It allows us to easily speed up or slow down time by manipulating the time elapsed before we tell the Timer class.
- It makes it a heckuva lot easier to unit test our code.
Fortunately, this isn’t hard. We can do it as follows:
Finally, we want to decouple our game logic from our timer class. We can do this by defining a Action
interface which represents the action we want to be executed by the timer for each interval:
With Java 8, we can pass in a lambda when we create the timer in our main loop:
Bam! We have a pattern!
The Cooldown Pattern
The cooldown pattern acts as a gate which stops an action from reoccurring until a certain amount of time has elapsed. This is a really helpful game design pattern because you can use it to balance out abilities and prevent spamming (particularly important in multiplayer games). Should some sort of ability have limited use, but you don’t want a resource mechanic? Throw on a cooldown! Need to limit a weapon’s rate of fire? Throw on a very short cooldown.
We can take our TimerPattern
concepts and tweak them a little bit to produce the pattern below:
Our Cooldown
class takes in an interval and an action that can be invoked. Once the action is invoked, the cooldown is set. Until the cooldown time elapses (by calling Cooldown#update()
), you can call Cooldown#execute()
as many times as you want and nothing will happen.
While this example illustrates the pattern fairly well, it’s easy to imagine some nice enhancements:
Sometimes we want an “alternative” action to execute if the cooldown is not complete. For example, if the cooldown is long we might want to print a message saying “X seconds remaining” or to play an audio clip (which itself may need to be wrapped up in a cooldown pattern).
Other times we may want to not ignore a user’s action, but queue it up so that when the cooldown expires, that action will be executed (potentially resetting the cooldown).
The Interpolation Pattern
What if we want to to transition between two values as a function of time? This may be as simple as adjusting an alpha value from 0% to 100% (i.e. fade in) or moving an element from one side of the screen to another. This behavior is more subtle than it sounds, so I’ve started with a unit test that probes the expected behavior for “Fade In”:
Notice that all the “game logic” is contained in the unit test. We specified that we wanted to adjust floating point values, we defined an algorithm for linear interpolation of these values, and the duration that we wanted the interpolation to take place over.
What the pattern provides us in this case is a framework to run this code- we have to do a lot of definition up front, but after all we care about is telling the Interpolation
timing pattern that time has passed and pulling values from it.
Support this up-front flexibility in Java requires a tricky combination of generics and lambdas (or anonymous classes, if you want to be old school):
Pro tip: This pattern/formula distinction that we’ve created between the Interpolation
class and the lambda we inject into it makes it much easier to do unit testing. You can write one set of unit tests for your pattern, and another set of unit tests which focus just on your formula.
Conclusion
In conclusion, Timing Patterns are repeatable answers to temporal problems created by time-driven mechanics. They allow us to create genuinely reusable boilerplate code that is isolated from our game-specific code.
I cannot stress enough how important this concept is in general- not just in games. If you understand how to identify common problems and the patterns that solve them, it becomes much easier to break larger problems down into manageable problems that you’ve already solved.
That being said, we only covered three game programming patterns, and it’s up to you to get out there and catch them all. If you’re interested in catching more from us, let me know in the comments section below, and subscribe to the mailing list to get notified of future posts!