Basic Game Tutorial 3: The Character
Alright! Here we go.
It's time to bring this game project to life with a character. In this tutorial, we'll be putting our character on screen and making it interact with the level. We want it to walk on the platforms and fall off the edges!
Setup
Before we get our character on screen, we need to do some setup. First, we must load a the character tilesheet:
1. background = loadImage( "Kenney/backgrounds", false )
2. tilesheet = loadImage( "Kenney/extraPlatformPack", false )
3. chrSheet = loadImage( "Kenney/characters", false )
We're using the name chrSheet
for the variable which stores the image we need.
Now let's create some player position variables:
5. playerX = 0
6. playerY = 0
Nice and simple. We'll begin with our character being drawn at the very top left of the screen.
Let's use these variables in a drawSheet()
function to actually get our player character appearing on screen.
We'll be adding this line just before the update()
function in our main game loop on line 46:
46. drawSheet( chrSheet, 96, playerX, playerY, scale )
Run your program and we should see our character sitting comfortably in the top left of the screen.
Okay! We're done. Enjoy your new game.
Just kidding. There's lots more to do.
First of all let's address a slight problem. Our character tile is a different size to the level building tiles. Because of this, just like our tSize
variable, we'll need a player size variable to make things easier for us later.
Add the following line to your code, just beneath the tSize
variable in the main loop:
33. pSize = tileSize( chrSheet, 96 ) * scale
Just like with the tSize
variable, we take the tile size of the character tile and multiply it by the scale
variable.
Alright, let's move on!
If our game is going to be playable, we need our character to fall until he lands safely on the ground. When he's standing on a platform, he should not fall.
Achieving this is actually quite tricky. However, once complete, we will be able to freely create new parts to the level and it will work just fine!
Gravity and Velocity
You might have noticed that on Earth, when we jump into the air, we unfortunately come back down again. This is because of a rather inconvenient thing called gravity.
When we are in the air, gravity pulls us towards the centre of the Earth. Thankfully, there is some nice solid ground in the way to stop us going too far.
Velocity is the speed of an object in a direction.
Let's say we drop a stone from the top of a building. The force of gravity is making the stone move faster and faster towards the ground. This is called increasing in velocity.
When the stone reaches the ground, its velocity becomes 0. It has stopped. However, gravity has not changed.
We can simulate the effects of gravity and velocity in our game code and achieve some awesome things.
Let's create the variables we'll use for these effects:
8. gravity = 1
9. velocity = 0
Done! Now let's use these variables to affect the player. We need to add these next two lines just before the drawSheet()
function used to draw the player. To make sure you've got it right, we'll show the end of the main loop too:
50. velocity += gravity
51. playerY += velocity
52.
53. drawSheet( chrSheet, 96, playerX, playerY, scale )
54.
55. update()
56. repeat
Run the program to see our character plummet straight past the screen! Excellent!
Okay... So we have gravity. Now let's work on actually making a working floor. Before we explain how this works, we need to delete a line of code. Line 51 to be precise. Take a look at how lines 50 onward should look:
50. velocity += gravity
51.
52. drawSheet( chrSheet, 96, playerX, playerY, scale )
53.
54. update()
55. repeat
We do not always want our character's y position to be affected by the velocity
variable all the time, only when there is no ground beneath them.
What exactly do we mean by ground anyway?
Going from Pixel Co-ordinates to Array Co-ordinates
What we need to do is quite complicated, so strap your focusing hats on.
We need to map our level
array on to the screen so that it fits correctly in the tiles.
We must check the tile beneath the player to see if it is empty in the level
array. As long as we have an empty tile beneath us, our y. position should be affected by velocity.
Let's return to our picture for a minute to illustrate what we mean:
See our cute character on the left side of the level? In the picture, he is above a tile indexed by the number 1. We want him not to fall unless he is above a -1 tile.
But how do we go from pixel co-ordinates to the co-ordinates of our array?
With maths! Hurray...
In all seriousness, this is an incredibly useful technique to learn - with it, you'll be able to create any 2D game with much more confidence.
Figuring out the surrounding tiles is something we'll want to do quite a bit in our project. Not just for falling, but for moving left and right and to interact with items too.
It's the perfect time to write our very own function to to do just that!
A function which does this needs to be given an x and y position to calculate from. We will pass these to the function as variables.
Remember, we write custom functions at the very end of our program:
57. function collision( x, y )
58. tileX = int( x / tSize )
59. tileY = int( y / tSize ) - levelOffset
60.
61. result = true
62.
63. if tileY < 0 or tileY >= len( level ) or tileX < 0 or tileX >= len( level[0] ) then
64. result = false
65. else
66. if level[tileY][tileX] < 0 then
67. result = false
68. endif
69. endif
70. return result
Now that's a scary bit of code right there! This function receives an x and a y position of the screen, converts them into array co-ordinates and finally tells us whether the tile at our x and y position is something to collide with (> 0
) or not (< 0
).
Remember, the tiles in our level
array are empty if they are a -1. It tells us this with a single true
or false
variable called result
. We can use this function to check if a tile is not a collision tile with a statement like:
if !collision( playerX, playerY ) then
(Remember, !
means not)
Here's how the function works:
First, we receive an x and y position. We then create two local variables called tileX
and tileY
which will be our array co-ordinates.
58. tileX = int( x / tSize )
59. tileY = int( y / tSize ) - levelOffset
We take the x and y positions passed to the function and divide them by the tSize
variable to give us the tile coordinates.
We must use the int()
function when we do this because we're looking for a whole number. If our result was a decimal, it would not work properly when used as an index into the level array.
For example, imagine our character is at the screen co-ordinates (450, 560).
We take 450 and divide it by the tSize
variable. When undocked, the tSize
variable is 60
. 450 divided by 60 is 7.5
. With the int()
function, this becomes the number 7
. So, our tileX
variable now holds a 7!
Doing the same with the y position gives us a result of 9. We subtract the levelOffset
variable and this gives us a 3. So tileY
now stores a 3!
This operation tells us any grid position from any pair of screen co-ordinates. Now let's put those tileX
and tileY
variables to work.
The next part is a little tricky. First, we create the variable which will tell us whether the tile we are checking is solid (true
) or empty (false
). The variable is called result
and at first it simply stores true
.
61. result = true
Next, we check if the tile in question is actually in the range of our level array. We must do this because if our character falls down a pit, or walks somewhere on screen where there is no tile data in the level array, we will get an out of bounds error. This is because the system is trying to check for a place in the level array which does not exist.
To solve this, we use an if statement which checks whether the tileX
or tileY
variables are in the correct range. If they are not, we simply make our result
variable false
to indicate that we will not collide with the tile in question.
63. if tileY < 0 or tileY >= len( level ) or tileX < 0 or tileX >= len( level[0] ) then
64. result = false
65. else
66. if level[tileY][tileX] < 0 then
67. result = false
68. endif
69. endif
70. return result
If it is in range, we check if that position in the array is solid or empty, then make the result
variable true
or false
accordingly.
Using our Collision Function in the Program
Time to put this function to good use. We want to apply the gravity effect to our player only if the tile underneath them is empty.
Let's write this into our code. We're looking at lines 52 to 57 below:
52. if !collision( playerY + pSize / 2, playerY + pSize + velocity ) then
53. playerY += velocity
54. else
55. playerY = int( ( playerY + velocity + pSize.y ) / tSize ) * tSize - pSize.y
56. velocity = 0
57. endif
Here, we are using our collision()
function to check if the tile below the player is not solid. We use an exclamation mark (!
) before the function call to check if the result is not true
.
Let's take a quick look at the arguments for the function call. We want to pass the player's x and y positions to the function, but we need a couple of other things.
First, we must add pSize / 2
to the x position, because we want to check the middle of the tile, not the corner. We also add tSize
to the y position to check the bottom of the tile rather than the top. Check out the graphics below to see what we mean by this.
The image below shows the origin point of the tile in yellow. This point is ( playerX, playerY )
.
If we add pSize.x / 2
to the x position, we are describing this point (shown in yellow):
Finally, we add pSize.y
to the y position:
That gives us the bottom of the character's feet, but we need to check where they are going to be, not where they currently are. For this reason, we add the velocity
variable to the y position. We are checking where the character would be if velocity was added to their position.
Back to the if statement:
52. if !collision( playerX + pSize.x / 2, playerY + pSize.y + velocity ) then
53. playerY += velocity
54. else
55. playerY = int( ( playerY + velocity + pSize.y ) / tSize ) * tSize - pSize.y
56. velocity = 0
57. endif
So, if the collision()
function returns a false
, we know that the tile in question is empty, and we can apply velocity to the character's y position.
We use an else
on line 54 to give an instruction if the tile in question is not empty.
On line 55 we set the y position of the player to be exactly where we want it to be and set velocity
to 0.
Run the program to see the character fall perfectly on to the platform!
If you've made it this far, you deserve a huge congratulations!
Believe it or not, that's all of the collision code complete. This will allow us to create new parts to our level and they will work perfectly.
End Result
Let's double check we're at the same point in the project. Below is a list of exactly how the program should look. If yours is all up to scratch and works without errors then we'll see you in the next tutorial where we'll be making our player move and jump! Exciting!
1. background = loadImage( "Kenney/backgrounds", false )
2. tilesheet = loadImage( "Kenney/superPlatformPack", false )
3. chrSheet = loadImage( "Kenney/characters", false )
4.
5. playerX = 0
6. playerY = 0
7.
8. gravity = 1
9. velocity = 0
10.
11. screenX = 0
12. screenY = 0
13.
14. tiles = [ 121, 138, 128, 129, 130 ]
15.
16. level = [
17. [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
18. [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
19. [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
20. [ 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
22. [ 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
22. [ 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
23. ]
24.
25. levelHeight = 12
26. levelOffset = levelHeight - len( level )
27. tSize = 0
28.
29. loop
30. clear()
31.
32. screenW = gwidth()
33. screenH = gheight()
34. scale = screenH / ( tileSize( tilesheet, 121 ).y * levelHeight )
35. tSize = scale * tileSize( tilesheet, 121 ).y
36. pSize = tileSize( chrSheet, 96 ) * scale
37.
38. drawImage( background, -screenX, -screenY, screenH / imageSize( background ).y )
39.
40. for row = 0 to len( level ) loop
41. for col = 0 to len( level[0] ) loop
42. if level[row][col] >= 0 then
43. x = col * tSize
44. y = ( row + levelOffset ) * tSize
45. drawSheet( tilesheet, tiles[level[row][col]], x, y, scale )
46. endif
47. repeat
48. repeat
49.
50. velocity += gravity
51.
52. if !collision( playerX + pSize.x / 2, playerY + pSize.y + velocity ) then
53. playerY += velocity
54. else
55. playerY = int( ( playerY + velocity + pSize.y ) / tSize ) * tSize - pSize.y
56. velocity = 0
57. endif
58.
59. drawSheet( chrSheet, 96, playerX, playerY, scale )
60.
61. update()
62. repeat
63.
64. function collision( x, y )
65. tileX = int( x / tSize )
66. tileY = int( y / tSize ) - levelOffset
67.
68. result = true
69.
70. if tileY < 0 or tileY >= len( level ) or tileX < 0 or tileX >= len( level[0] ) then
71. result = false
72. else
73. if level[tileY][tileX] < 0 then
74. result = false
75. endif
76. endif
77. return result
Functions and Keywords used in this tutorial
clear(), drawImage(), drawSheet(), else, endIf, for, function, gHeight(), gWidth(), if, int(), len(), loadImage(), loop, repeat, return, tileSize(), then, to, update()