Build an Endless Runner Game from Scratch: Adding Events
Welcome to the fifth installment of the Endless Runner series! Up to this point we should have a little monster running on a never-ending scrolling platform. We should have three levels of parallax scrolling, interaction between our monster sprite and the ground, as well as the ability to make him jump via touch interaction. So, today's step will give users a bit of a challenge: rather than just running on flat ground, there will be level changes and pits they will have to jump over in order to keep playing.
We will accomplish this fun challenge by adding an 'event system.' This should not be confused with the built-in events that the Corona SDK uses. For our purposes, the word 'event' is simply an easy way to explain what is going on. This tutorial will use the same format as the last one. In the download file you will find two folders: 'old' and 'new.' If you haven't been following the other tutorials and just want to jump straight into this one, you can simply open the old folder and it will have all of the files that you need to get started. The new folder will contain the finished project, so if you want to see what everything looks like at the end, feel free to take a peak.
The Event System
You may recognize the word 'event' from the previous tutorials. For example, look at the function touched:
function touched( event ) if(event.phase == "began") then if(event.x < 241) then if(onGround) then monster.accel = monster.accel + 20 end end end end
In that function we used the word 'event' to simply describe what was happening. We could very well have used the following code:
function touched( kapow ) if(kapow.phase == "began") then if(kapow.x < 241) then if(onGround) then monster.accel = monster.accel + 20 end end end end
'Event' or 'kapow' are simply variables that we use. 'Event' just happens to be more descriptive than 'kapow.' So, when we say 'event' we are not talking about a variable that describes something that is happening (as shown in the previous example), but we are using the word as a way to describe a system that controls the flow of the game. You may be wondering what things this "event system" can control. Well, how much fun is our demo right now? Is it something that you would want to try to sell? Even more importantly, is it something that someone would want to play? The answer is a painful no, because running and jumping with no obstacles is boring.
In order to make our game more interesting we need to add features. Things like the ground-changing levels, pits, obstacles, bad guys, etc., will make the game much more interesting. However, in a game like Endless Runner we can't have the same obstacles appearing at the same time in each game. We need to have everything randomized. That's what makes these games so addictive. So, this is where our 'event system' is really going to shine. In this tutorial we are going to discuss how to create two different events: first, the ground-change levels up and down, and second, creating a pit. These events are going to be randomly generated at random intervals. There are several ways you can go about doing this. For this example, the most logical way is to check if we need to start a new event every time we move one of our ground pieces back to the right side of the screen. Let's go ahead and look at what we need in order to do this.
Go ahead and open up the main.lua file in the old folder and we will start making some changes. The first thing you need to do is add some variables that we will use throughout the program. Add these lines at the top of the file right below the require sprite line:
--these 2 variables will be the checks that control our event system. local inEvent = 0 local eventRun = 0
Next, go to the updateSpeed function and change the speed. We should choose a slow, gradual, increase in speed so that the game actually progresses like you would expect. Change the line to this:
--this will drastically slow down how fast the tiles accelerate. --adjust this number to get the gamespeed closer to what works for your game. speed = speed + .0005
The next thing that we are going to do is to add our first event. Our first event is going to make each of the blocks change to a newly-adjusted height every time one is moved to the opposite side of the screen. This will of course be done randomly so that our game is always new and exciting. To get our event system started, first change the function updatesBlocks() to look like this:
function updateBlocks() for a = 1, blocks.numChildren, 1 do if(a > 1) then newX = (blocks[a - 1]).x + 79 else newX = (blocks).x + 79 - speed end if((blocks[a]).x < -40) then (blocks[a]).x, (blocks[a]).y = newX, groundLevel checkEvent() else (blocks[a]):translate(speed * -1, 0) end end end
The first line would simply set the y position of the block to be the same that it was before making a flat running surface that we have seen so far. The second changes the y position to be whatever the groundLevel is. The variable ground level will be randomly changed in small intervals in the following functions. Go ahead and add these functions anywhere below the update function.
function checkEvent() --first check to see if we are already in an event, we only want 1 event going on at a time if(eventRun > 0) then --if we are in an event decrease eventRun. eventRun is a variable that tells us how --much longer the event is going to take place. Everytime we check we need to decrement --it. Then if at this point eventRun is 0 then the event has ended so we set inEvent back --to 0. eventRun = eventRun - 1 if(eventRun == 0) then inEvent = 0 end end --if we are in an event then do nothing if(inEvent > 0 and eventRun > 0) then --Do nothing else --if we are not in an event check to see if we are going to start a new event. To do this --we generate a random number between 1 and 100. We then check to see if our 'check' is --going to start an event. We are using 100 here in the example because it is easy to determine --the likelihood that an event will fire(We could just as easilt chosen 10 or 1000). --For example, if we decide that an event is going to --start everytime check is over 80 then we know that everytime a block is reset there is a 20% --chance that an event will start. So one in every five blocks should start a new event. This --is where you will have to fit the needs of your game. check = math.random(100) --this first event is going to cause the elevation of the ground to change. For this game we --only want the elevation to change 1 block at a time so we don't get long runs of changing --elevation that is impossible to pass so we set eventRun to 1. if(check > 80 and check < 99) then --since we are in an event we need to decide what we want to do. By making inEvent another --random number we can now randomly choose which direction we want the elevation to change. inEvent = math.random(10) eventRun = 1 end end --if we are in an event call runEvent to figure out if anything special needs to be done if(inEvent > 0) then runEvent() end end --this function is pretty simple it just checks to see what event should be happening, then --updates the appropriate items. Notice that we check to make sure the ground is within a --certain range, we don't want the ground to spawn above or below whats visible on the screen. function runEvent() if(inEvent < 6) then groundLevel = groundLevel + 40 end if(inEvent > 5 and inEvent < 11) then groundLevel = groundLevel - 40 end if(groundLevel < groundMax) then groundLevel = groundMax end if(groundLevel > groundMin) then groundLevel = groundMin end end
The first function checkEvent will be called every time we reset the location of the block. So, go ahead and call checkEvent() right after the line we edited in updateBlocks(). Every time checkEvent() is called we check to see whether or not we need to start an event. It will also check to see if an event is already running. This will help us control our behaviors so we don't get too many crazy events spawning at one time. Run this and you should now have a game that is a lot more fun to play!
Notice that if you run into a wall, you don't die; you simply stop. This is sufficient for now. Just remember that when you die, or any other time you want to restart the simulator, you simply need to press control or command R and the level will instantly restart.
You can now see how we are creating events to control the flow of the game. Now we are going to add a second event, but this time we are not going to use runEvent to make our changes. We are going to add a pit that the player will have to jump over in order to keep playing. This can be added fairly quickly, so let's get to work!
The first thing to do here is add another check in the function checkEvent(). Add this section right below the other check inside the same if/else statement:
if(check > 98) then inEvent = 11 eventRun = 2 end
Next, go to the updateBlocks() function and change the section that looks like this:
if((blocks[a]).x < -40) then (blocks[a]).x, (blocks[a]).y = newX, groundLevel checkEvent() else (blocks[a]):translate(speed * -1, 0) end
Make that look like this:
if((blocks[a]).x < -40) then if(inEvent == 11) then (blocks[a]).x, (blocks[a]).y = newX, 600 else (blocks[a]).x, (blocks[a]).y = newX, groundLevel end checkEvent() else (blocks[a]):translate(speed * -1, 0) end
After running that, you should have a second event in the game that can randomly spawn the pit!
Notice, however, that instead of defining the action that will happen inside of the runEvent function, we do it directly inside of the updateBlock() function. This is why event systems such as this one are helpful. It allows us to easily define events and update the game in the areas of our code that make the most sense to us. Hopefully this gives you an idea of what you can do with events. Have some fun playing around with them and make your game full of interesting events. In the next tutorial, we will add two more events to keep the game challenging: bad monsters and destructible obstacles! So tune in next time, and if you have any questions, please let me know in the comments.