Advertisement
  1. Code
  2. Corona SDK

Build an Endless Runner Game From Scratch: Sprite Interaction

Scroll to top
Read Time: 11 min
This post is part of a series called Corona SDK: Build an Endless Runner Game From Scratch.
Build an Endless Runner Game From Scratch: Using Sprites
Build an Endless Runner Game from Scratch: Adding Events

Welcome to the fourth tutorial in our series on building a running-game from scratch with the Corona SDK. In this section, we are going to be adding gravity, collision detection, and the ability to jump to the game sprite. Let's go!

Hopefully the tutorials so far have been helpful and easy to follow. As always, if you have any questions be sure to leave a comment! Last time we went over how to create nice sprites from spritesheets. Today, we are going to be taking what we learned in the last tutorial and getting that sprite monster into our actual game. Once he is in, we will learn how to control him and make our game interactive. The way I am going to do this is to take the source code that we had from the background motion tutorial and add our monster animation stuff in first. If you download the files for the tutorial, you will notice that there are 2 folders, called "old" and "new". Old contains all the files from the background motion tutorial that you need to get started working on this tutorial. New contains all the code and everything you will have once this tutorial has been completed. So, go ahead and download the files and open the main.lua file from the old folder. I am going to break up everything we do into three sections. The first is going to cover organization of our game, to which we will make a few changes. The second will cover taking what we learned in the using sprites tutorial and implementing it here. We will go over that section fairly quickly as the details of what is happening have already been covered. The third section will be giving our little guy gravity, collision detection, and the ability to jump!

Up until now we put our images in the code in the order that we want them to appear on the screen. The earlier they are called, the further back they will appear in the layers on the screen. This works, but there is a better way to do this. It will not always be realistic to put every single image in the exact order you want them to appear, and you may at some point want to change the order to which objects appear on the screen. So, open up the main.lua file from the old folder and we'll start making some changes.

The first thing we are going to do is add the following line to the top of the page right below the display.setStatusBar line.

1
2
local sprite = require("sprite")

Next, add the following two lines  right beneath where we created the display group blocks:

1
2
local player = display.newGroup()
3
local screen = display.newGroup()

The display group player is going to be the display group that holds our hero sprite and the screen group will be a display group that holds everything else. Let's put some more code in and then I will finish explaining.

Insert the following code beneath the for loop where we instantiate our ground blocks:

1
2
--create our sprite sheet
3
local spriteSheet = sprite.newSpriteSheet("monsterSpriteSheet.png", 100, 100)
4
local monsterSet = sprite.newSpriteSet(spriteSheet, 1, 7)
5
sprite.add(monsterSet, "running", 1, 6, 600, 0)
6
sprite.add(monsterSet, "jumping", 7, 7, 1, 1)
7
--set the different variables we will use for our monster sprite
8
--also sets and starts the first animation for the monster
9
local monster = sprite.newSprite(monsterSet)
10
monster:prepare("running")
11
monster:play()
12
monster.x = 110
13
monster.y = 200
14
--these are 2 variables that will control the falling and jumping of the monster
15
monster.gravity = -6
16
monster.accel = 0
17
--rectangle used for our collision detection
18
--it will always be in front of the monster sprite
19
--that way we know if the monster hit into anything
20
local collisionRect = display.newRect(monster.x + 36, monster.y, 1, 70)
21
collisionRect.strokeWidth = 1
22
collisionRect:setFillColor(140, 140, 140)
23
collisionRect:setStrokeColor(180, 180, 180)
24
collisionRect.alpha = 0
25
--used to put everything on the screen into the screen group
26
--this will let us change the order in which sprites appear on
27
--the screen if we want. The earlier it is put into the group the
28
--further back it will go
29
screen:insert(backbackground)
30
screen:insert(backgroundfar)
31
screen:insert(backgroundnear1)
32
screen:insert(backgroundnear2)
33
screen:insert(blocks)
34
screen:insert(monster)
35
screen:insert(collisionRect)

Now, let's  go over that huge block of code.

The first two sections we are going to skip over. Those sections just create our monster sprite from our sprite sheet. If you have any questions about what is going on there do a quick review of the last tutorial where we went went over creating sprites from sprite sheets. In the next section, I have created a basic rectangle shape called collisionRect, this is how we are going to do our collision detection so our monster can interact with the world. Essentially, what is happening is we are creating an invisible square that goes in front of our monster. If you make the rectangle visible (i.e. just change the alpha to 100) you will see that it sits right in front of the monster and is floating a little above the ground.

Figure 1Figure 1Figure 1

The reason we do this is that it will give us a collision system that is easy to manage. Because we are doing an endless running game we are mainly concerned with what goes on right in front of the monster (normally what comes behind him won't kill him). Also, we lift it a little off the ground so that box never collides with the ground below the monster, only things in front of it. The monster itself will handle the collisions with the ground, we just need something to handle the collisions with external objects that could hit him in the front. This will make even more sense in the next couple tutorials as we add things for out monster to run into.

The section after that is where we insert everything into the screen. So, the screen is just a display group -there is nothing magical about it. However, by doing this, it gives us one huge advantage over how we put things on the screen before, and that is how we have control over how things are ordered. Now, regardless of when we created the sprites they will now appear in the order we put them into the display group screen. So, if we decided that the monster should go behind the the backgroundnear objects, we would simply need to insert them into screen group after we have inserted the monster.

Figure 2Figure 2Figure 2

Next, modify your update() function to look like this one:

1
2
local function update( event )
3
    updateBackgrounds()
4
    updateSpeed()
5
    updateMonster()
6
    updateBlocks()
7
    checkCollisions()
8
end

This will call the remainder of the functions that we need to run to make sure everything is well updated. One thing to note while working with your update function is that order does matter. For our little running game the order isn't that important as it is called thirty times a second, so anything that is updated will be caught extremely quickly.  Also there is no crucial data that the functions ca ruin for each other. However, there will be times when you need to be careful about what order you put things in. This is especially true when you have multiple functions that update the same variables in different ways. Usually though this is really just a matter of using common sense though and you will be able to logically step through which things should be handled first.

Here are the rest of the functions that will do the work that we just called from the update function. Put them beneath the update function. Be sure to read the comments as I will use them to describe what is going on.

1
2
function checkCollisions()
3
     wasOnGround = onGround
4
     --checks to see if the collisionRect has collided with anything. This is why it is lifted off of the ground
5
     --a little bit, if it hits the ground that means we have run into a wall. We check this by cycling through
6
     --all of the ground pieces in the blocks group and comparing their x and y coordinates to that of the collisionRect
7
     for a = 1, blocks.numChildren, 1 do
8
          if(collisionRect.y - 10 > blocks[a].y - 170 and blocks[a].x - 40 < collisionRect.x and blocks[a].x + 40 > collisionRect.x) then
9
               speed = 0
10
          end
11
     end
12
     --this is where we check to see if the monster is on the ground or in the air, if he is in the air then he can't jump(sorry no double
13
     --jumping for our little monster, however if you did want him to be able to double jump like Mario then you would just need
14
     --to make a small adjustment here, by adding a second variable called something like hasJumped. Set it to false normally, and turn it to
15
     --true once the double jump has been made. That way he is limited to 2 hops per jump.
16
     --Again we cycle through the blocks group and compare the x and y values of each.
17
     for a = 1, blocks.numChildren, 1 do
18
          if(monster.y >= blocks[a].y - 170 and blocks[a].x < monster.x + 60 and blocks[a].x > monster.x - 60) then
19
               monster.y = blocks[a].y - 171
20
               onGround = true
21
               break
22
          else
23
               onGround = false
24
          end
25
     end
26
end
27
function updateMonster()
28
     --if our monster is jumping then switch to the jumping animation
29
     --if not keep playing the running animation
30
     if(onGround) then
31
          --if we are alread on the ground we don't need to prepare anything new
32
          if(wasOnGround) then
33
          else
34
               monster:prepare("running")
35
               monster:play()
36
          end
37
     else
38
          monster:prepare("jumping")
39
          monster:play()
40
     end
41
42
     if(monster.accel > 0) then
43
          monster.accel = monster.accel - 1
44
     end
45
46
     --update the monsters position accel is used for our jump and
47
     --gravity keeps the monster coming down. You can play with those 2 variables
48
     --to make lots of interesting combinations of gameplay like 'low gravity' situations
49
     monster.y = monster.y - monster.accel
50
     monster.y = monster.y - monster.gravity
51
     --update the collisionRect to stay in front of the monster
52
     collisionRect.y = monster.y
53
end
54
--this is the function that handles the jump events. If the screen is touched on the left side
55
--then make the monster jump
56
function touched( event )
57
     if(event.phase == "began") then
58
          if(event.x < 241) then
59
               if(onGround) then
60
                    monster.accel = monster.accel + 20
61
               end
62
          end
63
     end
64
end

Notice that the touched function is never called. At the bottom of the code right below where you use the timer that calls the update function, put this code:

1
2
Runtime:addEventListener("touch", touched, -1)

Let's review a couple things from the code we just put in. The touched function passes in an event. Each "event" has properties of its own. When we say event.phase == "began" we are telling Corona that we want to be notified as soon as the user touches the screen. On the contrary if we had said "ended" instead of began we would be telling Corona not to notify us until the user had lifted his finger from the screen. Also stored in the event is the coordinates of the touch. This is why using began and ended is important. Do you want the location of when the user first touches the screen, or when he lets go? In most game situations you want to know as soon as the user touches the screen, but not always. For a full reference of the touch events you can go here(https://developer.anscamobile.com/reference/index/events/touch).

So, when you run this you will notice that you only jump if you touch the left side of the screen. The reason why we do that is because we want to reserve the right side of the screen for other things, like shooting fireballs. That does not fall under the scope of this project, but we will get there soon enough! With all of that in there we should now be good to go.

Figure 3Figure 3Figure 3

With everything in its place you should now have a monster that can run and jump on the ground! Slowly, our little tutorial is starting to feel like a game! As always, if you have any questions let me know in the comments section below and thanks for following along!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.