Post tutorial Report RSS About time-based events in Space Riot - #Devlog 1

Welcome to the first Devlog for Space Riot! In this article I will explain how time-based variation in terms of the gameplay is implemented in Space Riot. A level is completed if you reach the specified goals of the level which can be: collect items, kill enemies and do this within the predefined time period. However, to challenge the player more ideas are needed!

Posted by on - Intermediate Client Side Coding

Intro

This devlog deals with time-based variation in Space Riot. As for Space Riot - the variation of the game itself not only lies in the three different game modes but also deeply in the amount of different enemies that maneuver around in different formations or paths.
There exist numerous enemies that can be grouped into static ones like mines or non-static ones like enemy spaceships. But when and how should they be generated in a shooter like Space Riot? It's always hard for a developer to invent things that brings something fresh and more fun to the game and mechanisms to implement a more random and unpredictable feeling. And at the same time to reduce the amount of time that is needed to adjust these properties for the game designer.
With this article I will give you some insights on how it was achieved for Space Riot or how you can use this concept for similar games. The presented pseudo code should only be considered as concepts and don't always follow good programming practices of OOP. You probably will have your own architecture. So please keep this in mind when adopting those concepts. For this game I concentrate on generating enemies along the game. How can a more flexible architecture for time-based events achieved that is both easy to extend and to adjust for the developer and at the same time fun for the actual player? So lets begin.

Starting point

First, we have to define some game mechanics. As mentioned before, we have some static and non-static enemies. A goal might be: the longer the game is played the harder it should become. A simple approach would be that every x seconds an enemy is created on the screen:
//update method
if timeNow > lastTime + x
	lastTime = timeNow //update lastTime 
	//create enemy
This would create enemies after every x time unit. That may work for simple games, but would get boring after some time. This behaviour is predictable. Okay, the next idea would be to create enemies after 1 second, then 3 seconds, and then again after 5 seconds and increase the power:
//update method
if timeNow > lastTime + x(i)
	lastTime = timeNow //update lastTime 
	i = i + 1 % length
	//create fighter and increase power
Something like this. The time unit x is now a dynamic array that holds all the time units in a sequence. This is more interesting because less predictable for the player. We start with a value and iterate through the x values. If we reach the end of the sequence the modulo operator % helps us to get to the beginning of x, but this way may lead to a major flaw when programming a game. This hard-coded way of solving this problem isn't flexible at all. It's hard to understand and to maintain for a game designer as well. New features cannot be implemented on the fly by the developer. Let me explain why.
By this we only defined a periodic behaviour of creating enemies. If you want, for instance, a slowly increasing number of enemies appearing on the screen and then suddenly stop the process of creating enemies - you have to add another code block or add an if statement like this:
//update method
if timeNow > lastTime + x(i)
	lastTime = timeNow //update lastTime
	
	if(repeat == true) {
		i = (i + 1) % x<sub>n</sub>
	} else {
		i = i + 1
	}
	
	if( i < x<sub>n</sub>) 
		//create fighter
The if statement can lead to performance issues when this update method is called many times (but this shouldn't be an issue for this method - but think about it if you have a lot of code blocks like this). So you make two code blocks and check before:
//update method
if(repeat == true) {
	if timeNow > lastTime + x(i)
		lastTime = timeNow //update lastTime 
		i = (i + 1) % x<sub>n</sub>
		//create fighter
} else {
	if i < x<sub>n</sub> && timeNow > lastTime + x(i)
		lastTime = timeNow //update lastTime 
		i = i + 1
		//create fighter
}
And what if you want to create not just space fighters, but instead mine fields? Or both? You would have to replicate this code construct for every game entity you want to create or insert even more if statements - and that isn't a good code practice. The code basis will explode and error prone. Code maintenance will be very hard.

Time-based Events

Before, we can begin with the real work we need to define game events that we'll use later. A time-based event is a specific topic that is triggered by time. We are free to define those events. In the course of this article I'll define the following events:
  • MAKE_ENEMIES_STRONGER
  • CREATE_FIGHTER
  • CREATE_MINE

events


Of course, those events can also be used for other actions like collecting items or shooting enemy spaceships. Now we want to define when an event should be triggered.

Event Timeline Functions

A better approach is to define a function that will tell us, at which point in time the enemies should be created. I call those functions Event Timeline Functions. With that we'll have all the freedom later. The first idea I mentioned can be formulated via a periodic function. Let's say we want to create enemies in constant time intervals. For that we can use a sawtooth wave:
function sawtooth(t, x)
	return (float) (((t + x - 1) % (x * 1.0)) / x)
By definition, it will only output values between 0 and 1 (the function was scaled for that by dividing by the maximum). We'll interpret that as the magnitude of the function or the action potential when the event can be fired over time. This is depicted in the figure below. Over the time the value of the function increases:
event timeline function

With that we can do even more. Now we know when a peak is reached. This peak can be understand as an event and we know when it is happing in our timeline of the game. We can do anything at this point in time. We are not bound to just create space fighters for instance. The code would now look like this:
result = sawtooth(t, x)
if (result > 0.9) {
	return CREATE_FIGHTER
} else {
	return NONE
}
But we need some little improvement. Because when running on a system you never know how fast the engine will update the game and how fast the underlying hardware can handle those things. In this case there is a high chance that the function will fire more than once. That will lead to unwanted game behaviour. So we add a flag and if the function had reached the potential to fire the event we're also checking the flag. If the function value decreases, we can set the flag to false and wait for the next potential:
result = sawtooth(t, x)
if (result > 0.9 && !fired) {
	fired = true;
	return CREATE_FIGHTER
} else if (result < 0.9) {
	fired = false
}
return NONE
This is just an example. Depending on your architecture, it may be also wise to just return the value of the timeline function and via a callback handler to give other classes the option to decide what to do or keep track of the events that were fired. Also the event should be an argument of the function and not a fixed value like here. I'll explain this detail in the next section!

The Game Event Handler

Okay, now we have that, but what can we do with it? We have events and an event timeline function. The next step is to implement a game event handler which takes care of all the event timelines and notifies us when an event happened. Not so much detail on the implementation just the idea. I implemented a thread and a message queue that operates in the background. This is a strong improvement. With that in mind, we can implement the publish-subscribe-pattern.
//inside the gameEventHandler class (a background thread with a message queue)
for every event timeline function: eventTimeLineFnc
	event = eventTimeLineFnc.activation(currentTime)
	notifyEvent(event)
The method notifyEvent(event) will insert the event in a message queue and that event will be processed later. The queue will poll it and checking who is interested in this event. Checking for time-based events are crucial operations. So it will only evaluate the function for the given time and just put it in the queue to handle those events later. Any game entity or class can subscribe to an event. If that specific event is triggered then an enemy will be created. For instance the class EnemySpawner will subscribe to events that are interesting to this class - in this case the event CREATE_FIGHTER and CREATE_MINE:
gameEventHandler.addSubscriber(enemySpawner, CREATE_FIGHTER)
gameEventHandler.addSubscriber(enemySpawner, CREATE_MINE)
The class enemySpawner has to implement a specific interface that the gameEventHandler knows and can call when this event is fired. When the event is fired the callback method of the classes is called for those classes that subscribed to those events. Now the class gets the event back as an argument of the method and can finally create the appropriate enemy:
//insde the enemySpawner class
event_callback(event) {
	//do something with the event
}
Everything is well encapsulated and the responsibility is where it should be. The class has everything what is needed to create an enemy and gets the information from the game event handler which only knows "when" to create "what".You are not obliged to only send the event. Sometimes additional data is needed for a spawner class to create a specific breed of an enemy.

Even if the game has to do heavy-load operations the background thread will operate independently and fire those events just in time. The game loop can then add game entities to the scene and they will get updated and rendered in the next cycle.
This architecture lets us easily define new behaviour without changing the code basis! Isn't that neat?

Summary

So what did I do? Basically, I implemented an asynchronous message queue that operates in the background and doesn't block our game logic and regularly checks for time-based events. If an event occurs, then it will notify its subscribers. According to the solid principle of OOP the appropriate object will create the enemy and not the update method itself or the event handler. Everything is well encapsulated.
For a game designer it means: He/she only defines the function and the parameters - this change will instantly be available in the game.

if you have any questions please do not hesitate to comment.
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: