2D Games

Top  Previous  Next

This month we are going to wrap up the 2D Games workshop series by discussing about page scrolling games. These are games in which the player character can explore huge worlds, telling the engine to display new parts of these worlds as he crosses some threshold areas that are usually located near the edges of the screen.

 

I know that this might sound a bit confusing, so let's open and fire up the pagescrolling1.c demo:

 

aum85_workshop1

 

Move the player towards the left side of the screen; you will notice that it can't get out of the screen - a good thing, considering the fact that this is the first room in our world. Now move to the right side of the screen; you will see that the player is teleported to a different room (in fact, a different panel, but that's a story I'm saving for later).

 

aum85_workshop2

 

Keep moving towards the right side of the screen and you will end up seeing the last room from the demo:

 

aum85_workshop3

 

My "huge" world consists of only 3 rooms, but you can add as many as you want, of course. Back to the page scrolling discussion: as you can see, this demo allows the player to explore several rooms; as soon as he moves close to one of the screen boundaries, a new room is displayed. Let's see the code that does all these things:

 

PANEL* player_pan;

 

PANEL* page1_pan =

{        

       pos_x = 0;

       pos_y = 0;

       bmap = "page1.pcx";

       flags = SHOW;

}

 

PANEL* page2_pan =

{        

       pos_x = 0;

       pos_y = 0;

       bmap = "page2.pcx";

}

 

PANEL* page3_pan =

{        

       pos_x = 0;

       pos_y = 0;

       bmap = "page3.pcx";

}

 

We are defining a panel pointer that will be used for the player and 3 regular panels - they will be our "rooms". Since the rooms are supposed to be different for this type of games, we can use almost any images for them.

 

function main()

{

       fps_max = 60;

       video_mode = 7;

       video_depth = 32;

       video_screen = 1;

}

 

function player_startup()

{

       VECTOR player_speed;

       var horizontal_speed = 0;

       player_pan = pan_create("bmap = player.png; NULL;", 150);

       player_pan.pos_x = 380;

       player_pan.pos_y = 510;

       player_pan.flags |= VISIBLE;

       player_pan.center_x = player_pan.size_x * 0.5;

       player_pan.center_y = player_pan.size_y;

       while (1)

       {

               vec_set(player_speed.x, accelerate (horizontal_speed, 3 * (key_cur - key_cul), 0.3));

               player_pan.pos_x += player_speed.x;

               if (key_cul + key_cur)

               {

                       player_pan.angle += 0.2 * sin(40 * total_ticks);

               }

               else

               {

                       player_pan.angle = 0;

               }

               wait (1);

       }

}

 

The function that was used for the player is very similar with the one that was used in a previous workshop: it creates a panel and assigns it the "player_pan" pointer, places it at x = 380 and y = 510 pixels and makes it visible. Then, it sets the rotation point for the player panel at the center of its bitmap.

 

The "while (1)" loop moves the player horizontally with the speed given by 3 and the friction given by 0.3. The resulting, accelerated speed is added to player's pos_x value; in addition to that, player's angle is changed a bit when one of the left or right arrow cursor keys are pressed, in order to create a simple animation effect.

 

function pages_startup()

{

       while (1)

       {

               if ((player_pan.pos_x > 750) && (is(page1_pan, SHOW)))

               {

                       player_pan.pos_x = 0;

                       page1_pan.flags &= ~SHOW;

                       page2_pan.flags |= SHOW;

                       page3_pan.flags &= ~SHOW;

               }

               if ((player_pan.pos_x > 750) && (is(page2_pan, SHOW)))

               {

                       player_pan.pos_x = 5;

                       page1_pan.flags &= ~SHOW;

                       page2_pan.flags &= ~SHOW;

                       page3_pan.flags |= SHOW;

               }

               if ((player_pan.pos_x < 0) && (is(page2_pan, SHOW)))

               {

                       player_pan.pos_x = 750;

                       page1_pan.flags |= SHOW;

                       page2_pan.flags &= ~SHOW;

                       page3_pan.flags &= ~SHOW;

               }

               if ((player_pan.pos_x < 0) && (is(page3_pan, SHOW)))

               {

                       player_pan.pos_x = 750;

                       page1_pan.flags &= ~SHOW;

                       page2_pan.flags |= SHOW;

                       page3_pan.flags &= ~SHOW;

               }

               if ((player_pan.pos_x < 0) && (is(page1_pan, SHOW))) // trying to move outside the left level border?

               {

                       player_pan.pos_x = 0; // don't allow the player to do that!

               }

               if ((player_pan.pos_x > 750) && (is(page3_pan, SHOW))) // trying to move outside the right level border?

               {

                       player_pan.pos_x = 750; // don't allow the player to do that!

               }

               wait (1);

       }

}

 

The function above takes care of the page scrolling by monitoring player's panel pos_x value. If this value exceeds 750 pixels, which means that the player has come close to the right edge of the room, it moves the player to the left side of the screen, hides the current panel and makes a new panel visible. In a similar way, if the player tries to get out using the left side of the room (player's pos_x value is smaller than zero), the player is moved to the right side of the screen and a new panel is displayed. The last few lines don't allow the player to move outside the screen at the beginning (the left side of room1) and at the end (the right side of room3) of the level.

 

That's a good piece of code for page scrolling worlds if the player is supposed to explore various rooms. But how do we create a smooth scrolling page snippet, just like the one that is used in... let's say, Mario-like games? Well, I knew that you were going to ask this question, so fire up pagescrolling2.c and run it.

 

aum85_workshop4

 

It looks like the first demo, but try and move the player towards the right side of the screen; you will notice that as it approaches the right border of the screen, the entire background starts to scroll towards left.

 

aum85_workshop5

 

Keep going to the right and you'll see the 3rd room as well:

 

aum85_workshop6

 

It is clear that the bitmaps that are used for these smooth, continuous scrolling "rooms" need to match perfectly; otherwise, you will see those ugly looking borders where the panels meet. Move the player towards the left side of the screen; the scrolling will work in the opposite direction as well. Take a look at the figure displayed in the top right corner; it shows the offset value that's applied to all these scrolling panels (backgrounds).

 

Time to examine the code for the pagescrolling2.c file:

 

var offset_x = 0;

 

VECTOR player_speed;

 

PANEL* player_pan;

 

PANEL* page1_pan =

{        

       pos_x = 0;

       pos_y = 0;

       bmap = "page1.pcx";

       flags = SHOW;

}

 

PANEL* page2_pan =

{        

       pos_x = 0;

       pos_y = 0;

       bmap = "page2.pcx";

       flags = SHOW;

}

 

PANEL* page3_pan =

{        

       pos_x = 0;

       pos_y = 0;

       bmap = "page3.pcx";

       flags = SHOW;

}

 

Nothing new so far; we are defining a variable, a vector, a panel pointer and the panels that will be used for our virtual, smooth scrolling rooms.

 

function main()

{

       fps_max = 60;

       video_mode = 7; // 800x600 pixels

       video_screen = 1; // start in full screen mode

}

 

function player_startup()

{

       var horizontal_speed = 0;

       player_pan = pan_create("bmap = player.png; NULL;", 150);

       player_pan.pos_x = 380;

       player_pan.pos_y = 510;

       player_pan.flags |= VISIBLE;

       player_pan.center_x = player_pan.size_x * 0.5;

       player_pan.center_y = player_pan.size_y;

       while (1)

       {

               vec_set(player_speed.x, accelerate (horizontal_speed, 6 * (key_cur - key_cul), 0.5));

               if ((player_pan.pos_x > 100) && (player_pan.pos_x < 650))

               {

                               player_pan.pos_x += player_speed.x;

                               player_pan.pos_x = clamp(player_pan.pos_x, 101, 649);

               }

               if ((player_pan.pos_x == 101) || (player_pan.pos_x == 649))

               {

                       if (key_cul + key_cur)

                       {

                               offset_x -= player_speed.x;

                       }

               }

               if (key_cul + key_cur)

               {

                       player_pan.angle += 0.2 * sin(40 * total_ticks);

               }

               else

               {

                       player_pan.angle = 0;

               }

               player_pan.pos_x = clamp(player_pan.pos_x, 0, 750); // don't allow the player to get out of the screen borders

               offset_x = clamp(offset_x, -1600, 0);

               wait (1);

       }

}

 

Player's startup function creates the panel that will be used for the player and sets it up just like in the previous demo; the "while (1)" loop works in a different way, though. If player's pos_x is greater than 100 pixels and smaller than 650 pixels (the player is away from the screen borders), it can move freely and its pos_x value will be limited to the 101...649 pixels interval. If pos_x is equal to 101 (the player has come close to the left side of the screen) or pos_x is equal to 649 (the player has come close to the right side of the screen) and one of the left / right cursor keys is pressed, the player won't move at all - the background will be moved instead, by changing the offset_x value!

 

This means that as the player panel approaches one of the screen borders, it will stop moving. Nevertheless, since the background is now moving in the opposite direction, the player will think that his / her character continues to move; the animation runs at all times, provided that at least one of the left / right cursor keys is kept pressed. This way, the player character will move freely while it is away from the screen borders and will make the background scroll, revealing new areas of the level as he approaches the screen borders.

 

The last two lines of code inside the "while (1)" loop don't allow the player to get out of the screen borders and don't allow the panels to scroll too much (-1600... 0 is the proper range for offset_x - you'll see why in a second).

 

function panels_startup()

{

       while (1)

       {

               page1_pan.pos_x = offset_x;

               page2_pan.pos_x = page1_pan.pos_x + 800;

               page3_pan.pos_x = page2_pan.pos_x + 800;

               wait (1);

       }

}

 

The last function that needs our attention is a tiny one; it keeps all the panels glued to each other by adding 800 pixels to the previous panels (the bitmaps used for the panels have 800x600 pixels). This means that when offset_x changes (remember the digit in the upper right corner of the screen?) all the panels are moved at the same time, creating a big, smooth page scrolling, seamless world. Well, it's going to be a seamless world only if your panels blend together nicely - it's your job to make sure of that! The "clone brush" tool from your painting application might be your best friend here ;)

 

That's all for today! Don't forget to check out the "Plug and play" section of the magazine, which shows a nice, fully working 2D game.