Code snippets

Top  Previous  Next

Pong

 

It all started with Pong! Ok, there were some text based adventures before Pong, but this one the first (as I like to call it) "real game". I wanted to recreate it because I'm a nostalgic guy (sniff) but also to show you how to use 2D graphics, collision detection in 2D and many more things!

 

Let's start with some panel definitions and with the picture that shows them all in action:

 

aum26_shot6

 

panel main_pan // the main panel

{

    bmap = main_pcx;

    layer = 10;

    pos_x = 0;

    pos_y = 0;

    digits = 330, 40, 2, digital_font, 1, player_score;

    digits = 395, 40, 2, digital_font, 1, computer_score;

    flags = refresh, d3d, visible;

}

 

panel player_pan

{

    bmap = player_pcx;

    layer = 11;

    pos_x = 60;

    pos_y = 250;

    flags = overlay, refresh, d3d, visible;

}

 

panel computer_pan

{

    bmap = computer_pcx;

    layer = 11;

    pos_x = 715;

    pos_y = 250;

    flags = overlay, refresh, d3d, visible;

}

 

panel ball_pan

{

    bmap = ball_pcx;

    layer = 11;

    pos_x = 504;

    pos_y = 344;

    flags = overlay, refresh, d3d, visible;

}

 

This time I have used a big main function; the good news is that function main is the only function in this game and we will read it line by line:

 

function main()

{

    fps_max = 80; // limit the frame rate to keep the game playable on faster computers

    level_load (dummy_wmb);

    wait (2);

    speed_x = 3 - 6 * (sys_seconds % 2);

    speed_y = 3 - random(6);

 

We limit the frame rate to 80 fps because the game should be played at the same speed on a P5 at 5GHz. We load a dummy level, we wait for the level to be loaded and then we generate 2 random values:

1) The direction of the ball at game start (towards the player or the computer) = -3 or 3, depending on sys_seconds (odd or even).

2) The vertical speed of the ball = -3...3.

 

    while ((computer_score != 15) && (player_score != 15))

    {

         if (key_cuu == 1) {player_pan.pos_y -= 8;}

         if (key_cud == 1) {player_pan.pos_y += 8;}

         player_pan.pos_y = min(470, player_pan.pos_y);

         player_pan.pos_y = max(35, player_pan.pos_y);

 

The game continues to run as long as the score (for the player or for the computer) is below 15. If we press the cursor up (cuu) arrow key, player_pan is moved upwards because its y gets smaller and smaller (until it reaches 35). If we press the cursor down arrow key, player_pan is moved downwards (its y can increase up to 470).

 

         ball_pan.pos_x += speed_x;

         ball_pan.pos_y += speed_y;

 

The ball will change its position because of speed_x and speed_y. Here's a picture that shows the movement of the ball:

 

aum26_shot7

 

You know that the ball changes its x and y position at the same time, so its movement looks something like this:

 

aum26_shot8

 

         if (ball_pan.pos_y > 555)

         {

              speed_y = -3 - random(3);

              snd_play (beep1_wav, 50, 0);

         }

         if (ball_pan.pos_y < 32)

         {

              speed_y = 3 + random(3);

              snd_play (beep1_wav, 50, 0);

         }

 

If the ball goes below 32 pixels or over 555 pixels on y, it bounces back by changing its speed and plays the beep1_wav sound.

 

aum26_shot9

 

        if (ball_pan.pos_x > 740) // player scores!

         {

              snd_play (goal_wav, 70, 0);

              speed_x = -3 - random(3);

              player_score += 1;

              ball_pan.pos_x = 740;

         }

 

If ball_pan.pos_x > 740, the player has scored! We play the goal_wav sound, we change the speed of the ball to make bounce back to the player, we increase player's score and then we move the ball to 740 pixels on x. Now why is that?

 

aum26_shot10

 

Well, the ball could go to up to 741, 742, 743, ... pixels before being bounced back and this would register several goals for a single shoot. The solution is to move the ball outside the dangerous area and to change ball's speed from positive to negative. This way the code will register a single goal for a single shoot.

 

         if (ball_pan.pos_x < 40) // computer scores!

         {

              snd_play (goal_wav, 70, 0);

              speed_x = 3 + random(3);

              computer_score += 1;

              ball_pan.pos_x = 40;

         }

 

If the computer scores we apply the same scenario.

 

         if ((ball_pan.pos_y > player_pan.pos_y - 12) && (ball_pan.pos_y < player_pan.pos_y + 96) && (ball_pan.pos_x > 60) && (ball_pan.pos_x < 72))

         {

               // the player has blocked the ball

              snd_play (beep2_wav, 70, 0);

              speed_x = 3 + random(3);

              speed_y = 3 - random(3);

         }

 

If the player has blocked the ball we play the beep2_wav sound and we bounce the ball back.

 

Who wants a perfect computer opponent? I know I don't want one.

 

         if ((ball_pan.pos_y > 100) && (ball_pan.pos_y < 490))

         {

              computer_pan.pos_y = ball_pan.pos_y - 42;

         }

 

We want the computer to make mistakes so we limit its movement on the y axis, instead of letting him go from 35 to 470, like the player does. This way you have a fair chance to beat the computer. Please note that the computer uses an offset of 42 pixels on y to follow the ball; this way it will hit the center of the ball with the center of its paddle (if it can reach the ball, of course :).

 

         if ((ball_pan.pos_y > computer_pan.pos_y - 12) && (ball_pan.pos_y < computer_pan.pos_y + 96) && (ball_pan.pos_x > 703) && (ball_pan.pos_x < 715))

         {

               // the computer has blocked the ball

              snd_play (beep2_wav, 70, 0);

              speed_x = -3 - random(3);

              speed_y = 3 - random(3);

         }

 

If the computer has blocked the ball we play a sound and then we bounce the ball back.

 

Ok, this isn't beginner's corner, but how does that collision detection system work? How can you tell if the ball has hit the paddle?

I'll explain how the system works for the computer; I'm using exactly the same method for the player so you shouldn't have any problem with it. The sad truth is that there isn't any "true" collision detection system in any 2D application, so it must be faked. Let's use this example:

 

aum26_shot11

 

Could you tell that the ball will hit the paddle? It looks like it will, isn't it? First of all, the height of the ball is similar to the height of the paddle. In fact, I'm pretty sure that the ball would hit the paddle if it would be placed in any of the positions from the picture below:

 

aum26_shot12

 

Ok, so we have to make sure that the bottom of the ball is placed below the top of the paddle (ball #1). Don't forget that any panel has the origin in its upper left corner, so the first statement will look this way:

 

if (ball_pan.pos_y > computer_pan.pos_y - 12)

 

The ball has 12 x 12 pixels, get it? How would we write the statement for ball #7? If the top of the ball is placed above the bottom of the paddle, isn't it?

 

if (ball_pan.pos_y < computer_pan.pos_y + 96)

 

That's the correct code because the paddle has 96 pixels on y.

 

This solves the collision detection on y; let's move to the x axis then.

 

aum26_shot13

 

How can we be sure that the ball will not pass through the paddle? Well, we must make sure that the right side of the ball has touched the left side of the paddle, in other words:

 

if (ball_pan.pos_x > 703)

 

because the computer paddle is placed at 715 pixels on x and the ball has 12 pixels. Then we must make sure that the ball has hit the paddle, and not the empty space behind it:

 

if (ball_pan.pos_x < 715)

 

If I wouldn't use this final statement it would appear as if the computer had a thick paddle, like in the picture below:

 

aum26_shot14

 

If you combine the four statements above you will get the collision detection line used in the game.

 

 

Force field

 

I like force fields! The look cool and they pump adrenaline through your veins. And they're not too hard to create, either!

 

First of all we need to create the part of the force field that looks nice.

 

action force_field

{

    var number_of_particles = my.skill1;

    var particle_distance = my.skill2;

    if (my.skill1 == 0) {number_of_particles = 600;}

    if (my.skill2 == 0) {particle_distance = 0.2;}

    vec_set (temp, my.pos);

    while (number_of_particles > 0) // don't use a wait(1) here

    {

         temp.x += particle_distance * cos(my.pan);

         temp.y += particle_distance * sin(my.pan);

         effect(particle_barrier, 1, temp, normal);

         number_of_particles -= 1;

    }

}

 

We define two local variables: one will store the number of the particles used for the force field (hence the length of the force field) and the second will store the distance between two consecutive particles. If you forget to set these values using skill1 and skill2 they will default to 600 and 0.2. We set temp to the position of the force field entity and then we start generating particles inside a while loop (without wait!) until all the particles set with skill1 are generated.

 

Time to see the particle related functions:

 

function particle_barrier()

{

    my.bmap = redflare_pcx;

    my.flare = on;

    my.bright = on;

    my.size = 10;

    my.function = keep_particles;

}

function keep_particles()

{

    my.lifespan = 10;

    my.alpha = 30 + random(70);

}

 

The only thing that should be observed here is the fact that lifespan is set to 10 all the time, in order to keep the particles "alive". Oh, and we set random alpha factors for the particles to show some laser activity. This part was nice indeed but how do we kill the player?

 

The easiest method is to use an invisible entity that is overlapping with the force field. Here's the code for it:

 

action killer

{

    my.invisible = on;

    my.enable_impact = on;

    my.enable_entity = on;

    my.event = kill_them;

}

 

function kill_them()

{

    wait (1);

    if (you != null)

    {

         you._health = -10;

    }

}

 

The killer entity is a simple wmb entity that is sensitive to other entities. If it collides with another entity (you != null) it sets the health for that entity to -10, killing it instantly.

 

How do you set up a force field, a laser barrier like the one in my example?

1) Place a small entity in your level and attach it action force_field to create the effect. This will be the particle generator, so you should place another identical entity at the end of the particle beam, without attaching any action to it, to make it look convincing.

2) Create a convenient wmb entity that covers your force field(s), place it in your level and attach it the action named killer. This entity will do the killing.

 

The example level included in the zip file comes with three particle generators (three force fields) and one big killer entity.