Linux Format

Coding a platformer

Armchair arcade warrior Calvin Robinson guides us through creating a side-scrolling platform game in Python. Are you ready, Player One?

-

Armchair arcade warrior Calvin Robinson guides us through creating a side-scrolling platform game in Python. Are you ready, Player One?

Side-scrolling video games are usually action based, with a side-view perspectiv­e on a player character traversing a two-dimensiona­l world. As the character moves left or right, the game world moves with them. Timeless classics include Sonic The Hedgehog and Super Mario Bros. More contempora­ry takes would include Broforce, Super Meat Boy, Braid, Rayman Legends, Limbo… all fantastic titles!

There’s a debate to be had about the first sidescroll­ing arcade game, released sometime in the 1970s, but the first game to enable the world to extend beyond a single screen was 1981’s Defender, a side-scrolling shooter. Before this worlds were restricted to a single screen, showing only what was on the monitor. A game called Jump Bug came out that same year, which was the first side-scrolling platform shooter, offering players the ability to jump on platforms, with levels that scrolled now only horizontal­ly but also vertically.

For our game, we’re going to dial things down a step and work with 2D shapes to begin with. This project is all about getting the game world, player character and relevant physics programmed. Let’s start by loading up Python IDLE or your favourite text-based editor and setting up the basic parameters for our game world: import pygame

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

RED = (255, 0, 0)

GREEN = (0, 255, 0)

BLUE = (0, 0, 255)

SCREEN_WIDTH = 1024 SCREEN_HEIGHT = 768

Pygame is a module that offers a handy toolset. It takes care of shapes and physics, so that we can focus on building our game rather than putting a lot of time and effort into re-inventing the wheel.

We’re initialisi­ng several variables here: these colours will be used for objects in the game environmen­t, Feel free to change the RGB values as you see fit. As for resolution, we’re running quite low-res on 1,024x768 by default but you could change this to high definition with 1,280x720 for 720p or 1,920x1,080 for 1080p.

Now that we have the basics setup, we can start coding. We’re going to take an object-oriented approach, with classes and objects.

By setting up a class for levels first, we can then implement a child class for each subsequent level later. class Level(): def __init__(self, player): self.platform_list = pygame.sprite.group() self.enemy_list = pygame.sprite.group() self.player = player self.world_shift = 0 def update(self): self.platform_list.update() self.enemy_list.update() def draw(self, screen): screen.fill(blue) self.platform_list.draw(screen) self.enemy_list.draw(screen) def shift_world(self, shift_x): self.world_shift += shift_x for platform in self.platform_list:

platform.rect.x += shift_x for enemy in self.enemy_list:

enemy.rect.x += shift_x

There are a number of methods here. Draw spawns our ‘sprites’ (be them 2D vector graphics) on the screen while Update keeps them updated. That may seem self-explanator­y, and that’s precisely why we use good naming convention­s.

Since our game is a side-scrolling platformer, our player character will mostly be moving from left to right, across the screen. There are a few ways we could implement this, but it’d look quite odd in this instance if we let the player move all the way from the left of the screen to the right – unless our levels were really that small. To give a larger sense of scale, we’re going to enable our player character to move freely to a point, then keep them in the centre of the screen and switch to moving the game world behind them, instead of the player themselves.

In effect, it’ll look like the player character is walking indefinite­ly, but it’ll be the surroundin­gs that are moving. This is a relatively easy optical illusion that video games – and films for that matter – have been using for some time. Think of a car scene, where the actors are sat in a static car but the viewer observes scenery moving past the car windows, as if the car is actually moving.

Always on the move

For this visual effect to work, we’ll need to give our player character freedom of movement left and right until they reach the centre of the screen. Then we’ll start shifting the world or environmen­t around them, from right-to-left. World_shift is the variable responsibl­e for how much movement we allow here. Level_limit is the limit or size of each level, the amount of shifting that takes place before we stop moving the world and enable the character to move further right again, before walking into the next level. The shift_world function takes care of this, by shifting all sprites as well as the background, along the X (horizontal) axis.

In Main we’ll need to do some basic mathematic­s. When the player character reaches boundaries prescribed on the left or right (currently 120 and 500 pixels, respective­ly), the world will shift accordingl­y.

if player.rect.right >= 500: diff = player.rect.right - 500 player.rect.right = 500 current_level.shift_world(-diff) if player.rect.left <= 120: diff = 120 - player.rect.left player.rect.left = 120 current_level.shift_world(diff)

To ensure our player can move, we have to set events to occur when the player character reaches the end of a level. This will be based on the previous boundary maths and the level limit variable we set earlier.

We move the player to the next level by preventing them from walking into the walls and/or out of the screen environmen­t with the following code:

current_position = player.rect.x + current_level. world_shift if current_position < current_level.level_limit: player.rect.x = 120 if current_level_no < len(level_list)-1: current_level_no += 1 current_level = level_list[current_level_no] player.level = current_level

We’re changing the player’s position, checking the level number in the list of levels and then changing to the next one in the array. This will mean our player has some freedom of movement, until they reach the centre of the screen and the whole process begins again, with the world shifting until we reach the level limit.

Now that we have our parent level class setup, we can start creating child classes for each independen­t level. Green Hill Zone might look as follows: class levelgreen­hill(level): def __init__(self, player): Level.__init__(self, player) self.level_limit = -1000 level = [[200, 50, 500, 500],

[200, 50, 800, 400],

[200, 50, 1000, 500],

[200, 50, 1100, 300], [200, 50, 1500, 500],

[200, 50, 1800, 400]] for platform in level: block = Platform(platform[0], platform[1]) block.rect.x = platform[2] block.rect.y = platform[3] block.player = self.player self.platform_list.add(block)

Since we’re drawing on the parent class, Level, we don’t need to duplicate all of those functions, as we’ll inherit all of the methods from within. We do, however, need to set a relevant level limit, which is essentiall­y the size of the current level in pixels. We also have an array here that contains the settings for our platforms; the numbers within represent: width, height, x and y coordinate­s. For example [200, 50, 1400, 400] would be 200x50 in size, at screen coordinate­s 1,400:400.

You’ll also notice we’ve included a For Loop to create the platforms row by row. We’re using simple rectangle blocks for this 2D implementa­tion of a side-scroller, but again, graphical sprites could easily replace our vector graphics for a prettier end product.

For the purposes of this tutorial we’re only building two levels, but using these examples you could build scores of levels for your game, and if you really wanted to take things further you could procedural­ly generate them so that every time the game is played the levels will be completely different. This will introduce a whole new level of challenge, because part of the charm of side-scrolling platformer­s has always been memorising the difficult parts of a level.

Our second level is called Marble Zone. Fans of the original Sonic the Hedgehog will have worked out that Spring Yard would have been next. The format remains the same, with some difference in the number of platforms and their location coordinate­s. class levelmarbl­e(level): def __init__(self, player): Level.__init__(self, player) self.level_limit = -1000 level = [[200, 50, 400, 400],

[200, 50, 800, 500],

[200, 50, 900, 400],

[200, 50, 1200, 400],

[200, 50, 1400, 400],

[200, 50, 1900, 500]] for platform in level: block = Platform(platform[0], platform[1]) block.rect.x = platform[2] block.rect.y = platform[3] block.player = self.player self.platform_list.add(block)

For the most part, that’s all the code we need to get our levels functionin­g. Once we’ve finished designing

levels we simply add them to a list, so the game knows the order to load them, and which comes next. This will go in our main class later on, but the code is simple: level_list = [] level_list.append(levelgreen­hill(player)) level_list.append(levelmarbl­e(player))

Character design time

We have a game world, with functionin­g levels that shift and spawn platforms. It makes sense that the next thing we code is our player character. Player class will hook into Pygame’s class Player(pygame.sprite.sprite) .

It would be lovely to use a fancy graphic for our player character, and there’s nothing stopping you from loading a sprite animation into the game, especially with the help of Pygame, but we’re going to stick with singlecolo­ured vector graphics for the sake of ease. Start by setting the shape and colour of our vertical rectangle, plus basic parameters like height and width, and the speed vector. All of this belongs in the initiation method:

def __init__(self): super().__init__() width = 50 height = 50 self.image = pygame.surface([width, height]) self.image.fill(red) self.rect = self.image.get_rect() self.change_x = 0 self.change_y = 0 self.level = None

That’s our player character created. Next, we’ll need to introduce an Update method so that we can move our player around the screen. We’ll also calculate gravity with calv_grav to provide some physics elements to the game. All that’s remaining is to move our player character left or right through the level by altering rect.x and adding any changes to change_x , with positive changes reflecting on a movement to the right, and negative resulting in movements to the left.

def update(self): self.calc_grav() self.rect.x += self.change_x block_hit_list = pygame.sprite.spritecoll­ide(self, self.level.platform_list, False) for block in block_hit_list: if self.change_x > 0: self.rect.right = block.rect.left elif self.change_x < 0: self.rect.left = block.rect.right

The way our collision detection works is by checking each individual block in the block_hit_list , and if we attempt to move our player character into another block the action is prevented by setting our player’s left value to that of the platform’s right coordinate­s, meaning we can’t move inside any other platform or box – we can, at the most, touch the sides.

We’ll need to do the same thing for vertical movement, too, ensuring that when we’re travelling up and down we don’t jump into another box: self.rect.y += self.change_y block_hit_list = pygame.sprite.spritecoll­ide(self, self.level.platform_list, False) for block in block_hit_list: if self.change_y > 0:

self.rect.bottom = block.rect.top elif self.change_y < 0:

self.rect.top = block.rect.bottom self.change_y = 0

Now let’s apply some physics to our game. Start off by calculatin­g gravity to introduce falling – this will avoid our player character floating on the spot if there’s no platform beneath them. Accelerati­on should become incrementa­lly faster with each change of the Y-axis. Then, once we hit the floor at the bottom of the screen, our player character’s Y-axis will stop changing on 0, therefore landing on the ground. We could always remove that If statement and allow the player character to fall through the world, if we wanted to. We’d then need to program a death/respawn for our player. def calc_grav(self): if self.change_y == 0:

self.change_y = 1 else:

self.change_y += .35 if self.rect.y >= SCREEN_HEIGHT - self.rect.height and self.change_y >= 0: self.change_y = 0 self.rect.y = SCREEN_HEIGHT - self.rect.height With falling physics in place, as well as our collision detection, we’re relatively sure our player character isn’t about to disappear through the ceiling or floor. It’s now time to work on our jump method. Our jump method is based on physics too. Basically here we’re checking to see if our player character is hitting a platform before we enable them to jump upwards on the Y-axis: def jump(self): self.rect.y += 2 platform_hit_list = pygame.sprite.spritecoll­ide(self, self.level.platform_list, False) self.rect.y -= 2 if len(platform_hit_list) > 0 or self.rect.bottom >=

SCREEN_HEIGHT:

self.change_y = -10

We can’t jump or move at all unless we set functions to handle that process, and we have a few options as far as movement implementa­tion goes. We could monitor key presses and move our player character on the down press or the release. We’re going with key up – we’ll handle the actual presses later on – but for now we’ll set movement to five pixels per button press. Setting a higher or lower number will make our character move further or shorter distances, respective­ly: def go_left(self):

self.change_x = -5 def go_right(self):

self.change_x = 5 def stop(self):

self.change_x = 0

That’s our world, our levels and our player characters sorted. The only thing left to create is the platforms themselves. There’s not a lot to it, other than drawing a new rectangle based on the height and width that we defined earlier in our levels, and setting the RGB colour that we assigned at the start of our program:

class Platform(pygame.sprite.sprite): def __init__(self, width, height): super().__init__() self.image = pygame.surface([width, height]) self.image.fill(green) self.rect = self.image.get_rect()

That’s all of our methods created for this game: Player , Level , Platform and the independen­t levels. In our main function we can now set up the screen and spawn our player object:

def main(): pygame.init() size = [SCREEN_WIDTH, SCREEN_HEIGHT] screen = pygame.display.set_mode(size) pygame.display.set_caption(“side-scrolling

Platformer”) player = Player()

Then include the levels with the level_list code we produced earlier, followed by the code to change levels: current_level_no = 0 current_level = level_list[current_level_no] active_sprite_list = pygame.sprite.group() player.level = current_level player.rect.x = 340 player.rect.y = SCREEN_HEIGHT - player.rect.height active_sprite_list.add(player)

Add two essential commands here. done = False

sets the game on a permanent loop until we quit, and clock = pygame.time.clock() ensure a smooth running game. Changing the clock speed will drasticall­y alter the speed of the gameplay. Gamers from the 90s will remember cheat programs to alter the clock speed of games to speed them up or slow them down for an advantage; all they were doing is altering this variable.

Our last major step is to set up our main game loop. Everything that follows is included in this loop: while not done:

for event in pygame.event.get():

We set up our movement keys earlier on, so now we implement the code for that and assign the buttons we’d like to use to move our character: if event.type == pygame.quit:

done = True if event.type == PYGAME.KEYDOWN: if event.key == pygame.k_left:

player.go_left() if event.key == pygame.k_right:

player.go_right() if event.key == pygame.k_up:

player.jump() if event.type == pygame.keyup:

if event.key == pygame.k_left and player. change_x < 0: player.stop() if event.key == pygame.k_right and player. change_x > 0:

player.stop()

Two more important commands to add following this are active_sprite_list.update() and current_level. update() , which will update/refresh the sprites and the level, respective­ly. Having these lines as part of the main loop ensure gameplay continuati­on – the level and sprite changes are constantly being redrawn.

We’ll need to include some important movement code in the main loop, to shift the world left/right: if player.rect.right >= 500: diff = player.rect.right - 500 player.rect.right = 500 current_level.shift_world(-diff) if player.rect.left <= 120: diff = 120 - player.rect.left player.rect.left = 120 current_level.shift_world(diff) current_position = player.rect.x + current_level. world_shift if current_position < current_level.level_limit: player.rect.x = 120 if current_level_no < len(level_list)-1: current_level_no += 1 current_level = level_list[current_level_no] player.level = current_level

Follow that with a couple of lines to draw the screen and player with current_level.draw(screen) and active_sprite_list.draw(screen) . Set the framerate to 60 frames per second with clock.tick(60) . And finally, once we’re happy we’ve drawn everything we can close the loop with pygame.display.flip() . It’s best practice to assign a quit comment; a simple pygame.quit() will do. Our last bit of code before running and testing our game should be to initialise our main function, in order to get the main loop running and start the gameplay: if __name__ == “__main__”: main()

Save, run, play and have fun!

 ??  ?? Here’s the main while loop that keeps our gameplay ticking.
Here’s the main while loop that keeps our gameplay ticking.
 ??  ?? Our orange player character jumping from one green platform to the next, with the aid of our physics code.
Our orange player character jumping from one green platform to the next, with the aid of our physics code.
 ??  ?? A demonstrat­ion of collision detection – our red player character is stopped by a green platform.
A demonstrat­ion of collision detection – our red player character is stopped by a green platform.
 ??  ?? A mid-air jump, as our red player character prepares to land on a platform, Mario-style. Use your imaginatio­n!
A mid-air jump, as our red player character prepares to land on a platform, Mario-style. Use your imaginatio­n!
 ??  ?? Our game loaded and running. 2D vector graphics in a Pygame environmen­t.
Our game loaded and running. 2D vector graphics in a Pygame environmen­t.

Newspapers in English

Newspapers from Australia