![]() |
|||||||||||||
Tutorial |
|||||||||||||
Platform game basicsSince I started to implement J-Rio one year ago, I thought about writing a tutorial chapter about the basics of platform games, but until today I never had the time to do it. But as many mails I got in the last year included questions about techniques of J-rio and platform games in general, I think that such a chapter could be very usefull to some of you. Also as I won't give the sourcecode of J-rio away, people that asked me for the sourcecode have with this chapter the chance to take a look behind the scenes of my J-rio implementation because the example applet we will implement in this chapter is some kind of a lite version of J-rio. Well, ok, I hope that I can help you a little bit with this chapter and before we start you should take one more look at the chapters about the level editor and about scrolling because I'll use the techniques I introduced in those chapters in our platform game but I won't talk about them in detail anymore. You should also download the sourcecode of the example applet now and work the code through while you read this tutorial. This chapter won't give you the details to every line of code but I'll focus on three major problems. First, we'll take a closer look at the class design I have chosen to implement the game, second, we'll talk about the methods and attributes of the player class where I'll focus on things that are related to the movement of the player and last but not least we'll talk about the structure and function of the class level . The class design of our platform gameThe more complex a game (or any other program) gets, the more important becomes a good class design to allow the programmer to add new functions to the game easily. In our case (or in my case when I implemented J-rio) we want to have the possiblity to add new enemies and level elements to our already released game without any major changes of the code and it should also be easy to write new levels for the game. I think that I managed those problems pretty well with the J-rio class design and that's why I'll use it in our case too, even though there might be simpler but maybe not so powerful solutions (this does not mean that I found the only correct solution and there might not be better ones than mine!). For example it is really easy to add new levels to J-rio, just take a look at the J-rio level editor . The class MainFirst of all the Main class implements the applet itself, which means the init() , start() , stop() , destroy() and paint(Graphics g) - methods and includes the main game thread within the method run() of the interface Runnable . It also holds methods to handle player events (keyDown...) and two attributes: an instance of the class player and one instance of a child class of the class level (in our case only the class LevelOne). In our run() - loop we manage the complete game which means to call methods to draw player and level, to scroll the level and the player if necessary, to test for collisions between player and level elements and so on. Please note that the class main does not implement one of those methods on its own but calls methods in the classes Player and Level. The class PlayerThis class implements behaviour and attributes of the player object. This means it holds information about the position of the player in the game, stores the player images used to animate the player and is also responsible to control the movement of the player (which is explained in detail later). The class LevelElementA game like J-rio consists of many different kinds of level elements that have different attributes, behaviour and cause different interactions with the player. There are for example those "questionmark stones" that have at least two different states, which are "hit" and "not yet hit", moving platforms and the most simple level element, the ground element, that has no special behaviour at all. And for this chapter we will concentrate on this very simple element implemented in the class Ground . But despite those differences between the different kinds of level elements they have quite a bit of common behaviour, which is implemented in the class LevelElement , which is the basic class for every level element in our Platform game. This basic bahaviour contains the position of the level element, a unique integer identifier, a boolean variable "inSight" that is used for scrolling and painting the level element (see chapter about scrolling for details), as well as an Image - object to store the level element GIF. Every level element in J-rio extends the class LevelElement and the internal data structure of the class Level contains only instances of the child classes of the class LevelElement that are identified by their unique integer id. The class GroundThis class represents exactly such a child class of the class LevelElement. But this kind of LevelElement has no special behaviour so that it only calls the super constructor in its own constructor. The class LevelThe abstract class Level is maybe the most important and complex class of the game. The class holds a method to translate the string representation of the level (given in the class LevelOne) to the internal data representation of the level which is a two dimensional array of LevelElement - instances. It also holds methods used for collision control between player and level elements. What the internal representation looks like, how it is created and how the collision control works will be explained later in this chapter. The class LevelOneThis class contains level specific definitions especially the definition of the level itself consisting of 25 strings representing 25 rows of the level. The color of the background can also be manipulated using the initializeColorArray() method of the class LevelOne. Die Klasse C_JumpTo make changes of the game size, levelelement size... easier all constants used in the game are written down in a special class, in our case in the class C_jump. How the classes work togetherAs I already said the class Main holds an instance of the Player - object, as well as an instance of a child class of the class Level . Main is also responsible for managing the complete game. The class Player is used to store player specific attributes and to move, draw, animate and scroll the player correctly, whereas the class Level is used to hold the internal representation of the level, to draw and to scroll the level as well as to test for collisions between the player and level elements. A level is implemented by a child class of the class Level , in our case by the class LevelOne . When we construct a instance of the class LevelOne, the string representation of the level is translated to the two dimensional array of LevelElement - objects, stored in the parent class of LevelOne Level . Every level element is a child class of LevelElement , in our platform game there is only the class Ground . Details of the class PlayerThe animation and movement control of the player is very important for our game. But to control the movement of the player in a platform game is not that simple as it might seem, because we have to take care that our player has to stop walking or jumping if he hits a wall, he has to fall down of any platform if the end is reached, has to stop falling if he lands on a platform and so on. That's why I'm going to explain my solotion to those problems within the next part of this chapter. The four movement flags and their controlOur player object as four, partly independent movement flags:
The walking_left and walking_right as well as the falling and jumping flags may not be true at the same time. But falling / jumping and walking_left / walking_right are completely independent from each other so they can be true together because our player must have the chance to jump to the left or to fall to the right side. The movement flags are controlled from two different sides: the keyboard input of the human player and the collision control of the level class, we'll discuss later this chapter. But now we'll take a look at the set - methods for our boolean flags and the playerMove() - method that does the movement work for us. The set - methods for the walking_left and walking_right values are really simple, here is the code.
public void playerWalkLeft(boolean value) {
// Method sets the value of the walking_right flag public void playerWalkRight(boolean value) {
In case of the flag jumping it's getting a little bit more complicated because we have to take care of some more things, when we set the value. First of all the player can only jump, if he's not already falling which means that the value of falling is false and if the player is not already jumping so the value of another flag jump_lock has to be false too. One more thing is, that we will use a counter jump_counter to contoll the length of the jump. This counter must be reset to 0 when the player starts a new jump which means that the values of the flags are: jumping = false, jump_lock = false and value is true. You'll find some details on the jump_counter later this chapter. Here comes the code:
public void playerJump(boolean value) {
if(!jumping && !jump_lock && value) {
// the player can only jump, if he's not already falling if(falling) {
else {
The last one of the four flags is the falling flag. With this flag, it is important that if falling is set to false which means the player lands on a platform, we have to end a jump (jump_lock and jumping must be set to false). One more thing is, that in some cases the player does not stop moving right on the surface of the platform but a little bit more down so that it seem the player would be standing right in the platform. So we have to correct errors that happen here. We also have to take care that the player can't jump and fall at the same time but this is guarantied somewhere else (in the method playerMove()).
public void playerFall(boolean value) {
if(!value) {
if(jump_lock) {
jumping = false; // we have to correct the player position so that the player always, // stands on the surface of a platform. In this case, the lower // position of the player modulo the Height of a level element // is equal 0. If this is not the case, the player is moved up // until this is the case and the player stands on the surface! while(y_pos_down%C_Jump.level_element_height != 0) {
y_pos_up--; // set value of falling falling = value; Movement and animation of the playerNow as we know about the four movement flags and how they are set we will take a look at the method that actually moves the player according to the values of the flags. This happens in the playerMove() - method but before we take a look at the sourcecode of the method I have to say something about two variables I'll use in the method:
public void playerMove() {
if(walking_left) {
x_pos_left -= walk_x_speed; x_pos_right -= walk_x_speed; // change the game position (important for scrolling) game_x_position -= walk_x_speed; // change the player image after 15 steps if (step_counter%15 == 0) {
picture_counter ++; // reset counter if value is 2 (there are only 2 pictures) if(picture_counter == 2) {
// reset step counter step_counter = 1; // else increase value of the step counter else {
// tell if player looks left (only important for animation) look_left = true; // walking_right flag is true else if(walking_right) {
// Value of jumping is true if(jumping) {
// if he's already jumping jump_lock = true; // Make sure that the value of falling is false becaues the collision // control sets the falling value to true even if the player is jumping falling = false; // jump_counter still smaller than 30 if(jump_counter < 30) {
y_pos_up -= jump_y_speed; y_pos_down -= jump_y_speed; jump_counter ++; // if the value of the jump_counter is smaller than 30 but greater than // 40 jump by the speed of 1 else if (jump_counter < 40) {
y_pos_down -= jump_y_speed2; jump_counter++; // if the value of the jump_counter is greater than 40, player can't // jump further so set the value of jumping to false else {
// if player is falling move playe down if(falling) {
y_pos_down += fall_y_speed; I hope that I could show you now the most important and new parts of the class player, scrolling and painting of the player should be no problem anymore. But I think you should really take a look at the sourcecode of the class to understand everything because this class is not so simple. Well, ok, lets start with a even more complex class, the class Level Structure and function of the class LevelIn this last part of the tutorial we'll talk about the class Level and the related classes LevelOne and LevelElement . First of all we'll take a look at the "definition language" of a platform game level and we'll see how this definition language is translated to the internal data representation of the level. Those are mainly ideas I already introduced in the chapter about the level editor, if you have any problems please read this chapter first. Afterwards I'll show you some details of the collision control between level elements and the player. I won't talk about things like scrolling and painting because those things should be clear now (at least I hope so). Definition language and internal data structure of a levelOne major goal of our class design was to allow ourselfs to write new levels for our game easily. That's the reason why we define our levels with a certain "definition language" which can easily be used by humans and then we translate this level definition into a new data structure which can be used by the computer. In our design language a level consists of 25 rows represented as 25 strings. The length of those strings is variable but they all need to be of the same length. Every level element we want to use in our level is implemented as a child class of LevelElement, has a unique character identifier and at every position where this character appears in our level definition strings this level element will be generated in the internal data structure of the level. The parser in the class Level (method initializeLevel()) has to be capable to translate those string definitions into the internal data structure of our level. This internal data structure consists of a two dimensional array with null pointers at places where no level element exists in our level and instances of child classes of the class LevelElement where a level element exists in the level. This idea is explained in more detail in the chapter aboue the level editor . Ok, before we get to the collision control here comes the sourcecode: The level definition can be found in the class LevelOne . This class is used to define all the level specific things like the background color, the level itself and so on where the real functionality of the level is implemented in the inherited methods from the parent class Level. Here comes a level in the level definition language:
/**
As you can see here it is really simple to define new levels (see the level editor version of J-rio too). But how is this simple definition translated to the internal computer readable data representation? This is implemented in the method initializeLevel(String [] definitions) of the class Level. This method parses the level definition strings and generates a two dimensional array of level elements out of them. This array is mainly used for the collision control. Afterwards all pointers to levelelements are also stored in a one dimensional array to make scrolling and painting of the level more efficient. // Method to parse the level definition strings and generate 2D and 1D arrays.public void initializeLevel(String [] definitions) {
collision_array = new LevelElement [C_Jump.number_of_level_lines] [definitions[0].length()]; // Initialize some level information: level length and left and right border level_length = definitions[0].length() * C_Jump.level_element_width; left_level_border = 0; right_level_border = C_Jump.applet_width; // Counter to count the number of level elements in the level, important // for the initialisation of the 1D array int elements_counter = 0; // For all level definition strings do: for(int i=0; i<definitions.length; i++) {
char [] definition_line = definitions[i].toCharArray(); // for all elements in the char array do: for(int j=0; j<definition_line.length; j++) {
if(definition_line[j] == ':') {
// Generate a Ground element else if(definition_line[j] == 'g') {
// is translated to concrete pixel position Ground element = new Ground(j*C_Jump.level_element_width,
// Store element in collision array collision_array[i][j] = element; // increase element counter elements_counter ++; // Copy levelelement pointers to 1D array // Code's missing because nothing special happens Test for collisions between player and level elementsLet's get to the last and maybe most comprehensive topic of this chapter. All the things we did and talked about before are important for the collision control of the game because the collision control has quite a lot to to with the movement control and the internal data representation of the game. public void testForPlayerCollisions(Player player) {
int player_game_pos = player.getGameXPosition(); int player_down_pos = player.getYPosDown(); int player_up_pos = player.getYPosUp(); int player_left = player_game_pos - (C_Jump.player_image_width/2); int player_right = player_game_pos + (C_Jump.player_image_width/2); // Test for collisions down LevelElement down_element = testCollisionDown(player_game_pos, player_down_pos); // If there is a element below the player, the player does not fall if(down_element != null) {
// if null is returned the player is falling down else {
// Test for collisions up LevelElement upper_element = testCollisionUp(player_game_pos, player_up_pos); // stop jumping if there is a element if(upper_element != null) {
// Test for collisions at the left side of the player LevelElement left_element = testCollisionLeft(player_left, player_down_pos); // stop movement of the player to the left if(left_element != null) {
// Test for collisions at the right side of the player LevelElement right_element = testCollisionRight(player_right, player_down_pos); // stop movement of the player to the right if(right_element != null) {
// Method tests for a given player position if there is a level element, this element is returned to the calling class public LevelElement testCollisionDown(int game_pos, int player_y_down) {
int col = game_pos / C_Jump.level_element_width; int row = player_y_down / C_Jump.level_element_height; try {
if(collision_array[row][col] != null) {
else {
catch (ArrayIndexOutOfBoundsException ex) {
That's it!Well, here I'd like to finish this chapter and I hope that I could help you a little bit. I know that this chapter is not that simple as some other chapters of this tutorial (it was neither easy for me to write, nor easy for you to understand, I think), but as the topic is not so simple this is not such a big surprise. Also I'd really like to hear what was useful and what was not with this chapter because maybe I should explain some things in more detail or so. Please write me a mail if you used this chapter and tell me what was useful and what was too fuzzy (a word my professor is always using so I wanted to use it too once in my life ;-)! Well then, as always you can download the sourcecode of this chapter and you can take a look at the example applet we've just implemented but you can play J-Rio as well, because it is "almost" the same. Good luck with the implementation of your own platform games! Sourcecode downloadTake a look at the applet |
|||||||||||||
|
|
|||||||||||||