Last tweaks

void phpSDL_WM_SetCaption ( string title, string icon)

int phpSDL_SetColorKey ( array surface, int flag, int key)

int phpSDL_GetTicks ( void )

The actual mechanics of making a game are down to you - this is not a book about making games with PHP, after all. However, there are four special techniques I want to demonstrate to you before we finish and these are setting the window caption, transparency, collision detection, and animation.

As you can see, the caption on all our SDL windows has been SDL_app, which is not very professional. There is an easy way around this with the phpSDL_WM_SetCaption() function, which takes two parameters to set the window and icon name of the app. Windows does not actually support the second parameter, but you need to provide it anyway as PHP SDL is cross-platform. Here is an example of the function in action:

phpSDL_WM_SetCaption("A PHP Game", "A PHP Game");

Next up, transparency. This is the process of specifying a colour in our surface that we would like to be considered as see-through, thus allowing us to have more interesting shapes than squares. First, edit your picture so that there is a substantial amount of a rare colour in there. I used bright magenta (Red 255, Green 0, Blue 255), cut out a large magenta hole in my image for use as transparency, and this is how it looked afterwards:


If you have any idea how Hollywood does its special effects, you will know they often use brightly coloured blue or orange screens to mark the transparent areas that are filled in later by computer - you can use blue too, as long as it is not already in the picture.

Now, back in your script, add this line after the call to phpSDL_LoadBMP() :

phpSDL_SetColorKey($bmp, 0x00001000|0x00004000, phpSDL_MapRGB($bmp['format'], 255, 0, 255));

The new function, phpSDL_SetColorKey() sets the transparent colour in a surface, and takes three parameters: the surface to change, special flags, and the colour to use as transparent. The $bmp variable was the return value from phpSDL_LoadBMP(), so you know that already, and we've already used phpSDL_MapRGB() when working on the screen clearing, so the only confusion here should be the second parameter - the flags.

As you can see, there are two flags being passed in, both of which are hexadecimal numbers. As you have seen so far, with examples such as SDL_HWSURFACE and SDLK_ESCAPE, PHP SDL flags are usually mapped to easy-to-remember constants. However, thanks to the fact that PHP SDL is not release quality, the two flags we're after in our call to phpSDL_SetColorKey() are not available, so we need to use the numerical equivalent. 0x00001000 (do not bother trying to remember this - copying and pasting is fine) equates to SDL_COLORKEY, and means "we want to set the transparent colour". 0x00004000 equates to SDL_RLEACCEL, which means "our picture has transparent blocks arranged in such a way that it will be faster to encode the picture using RLE acceleration". In the example, we combine these two together to use both. If you set the second parameter to 0, it acts to clear the current transparency setting.

Using 0x00004000 (SDL_RLEACCEL) is usually a smart move. A standard bitmap picture looks something like this:

11111122111111
11111222211111
11112222221111

In that example, consider a 1 as meaning "this pixel is coloured red" and a 2 as meaning "this pixel is coloured blue", and you realise there is a lot of duplication involved with bitmaps which is why they are so big. RLE encoding changes the system so that a pixel count and a pixel colour is stored, which would make the example above look like this:

6x1,2x2,6x1
5x1,4x2,5x1
4x1,6x2,4x1

So rather than physically storing six 1s, an RLE bitmap stores the number of times each colour appears, thus saving lots of space. Similarly, this is a lot faster when drawing, but only if you have large horizontal blocks of your transparent colour. If your transparency is made up of many very small areas, it is almost certainly not worth using SDL_RLEACCEL. As it is very rare to have such random transparency, I nearly always enable RLE.

Once the call to phpSDL_SetColorKey() has been added, nothing else needs to be done - go ahead and rerun your script, and see the difference. Note that the values passed into phpSDL_MapRGB() for the transparency colour needs to be exactly correct in order to work.


The third enhancement I'd like to demonstrate is collision detection, which is not a feature of SDL at all because it is something you do entirely in PHP code. However, many find it daunting, and I'd like to dispel any fears you have. Collision detection is, at its simplest level, a matter of comparing the rectangles of two objects and returning "true" if they overlap. For this test we're going to create an enemy of sorts using a second picture, enemy1.bmp - go and find another picture to use, and copy it to the same place as mypic.bmp.

Just below the call to phpSDL_SetColorKey(), add this:

$enemy = phpSDL_LoadBMP("enemy1.bmp");

Then, below the definition of $dest, add this:

$enemydest['x'] = 0;
$enemydest['y'] = 320;
$enemydest['w'] = 0;
$enemydest['h'] = 0;
$enemydest['movingright'] = true;

After the definition of $black, add this:

$red = phpSDL_MapRGB($video['format'], 255, 0, 0);

So far we've created a new enemy surface and set up a destination array for it. The extra "movingright" variable in there will be explained soon. The extra colour, $red, will be used to show when collision is taking place.

Now, amend the main game loop to this:

while(!$done) {
    if (
$enemydest['movingright']) {
        
$enemydest['x'] += 5;
    } else {
        
$enemydest['x'] -= 5;
    }

    if (
$enemydest['x'] + $enemy['w'] >= 640) $enemydest['movingright'] = false;
    if (
$enemydest['x'] < 0) $enemydest['movingright'] = true;

    
$collide = false;

    if (
$dest['x'] + $bmp['w'] > $enemydest['x']) {
        if (
$dest['x'] < $enemydest['x'] + $enemy['w']) {
            if (
$dest['y'] + $bmp['h'] > $enemydest['y']) {
                if (
$dest['y'] < $enemydest['y'] + $enemy['h']) {
                    
$collide = true;
                }
            }
        }
    }

    if (
$collide) {
        
phpSDL_FillRect($video, array("x"=>0,"y"=>0,"w"=>640,"h"=>480), $red);
    } else {
        
phpSDL_FillRect($video, array("x"=>0,"y"=>0,"w"=>640,"h"=>480), $black);
    }

    
$event = array();

The first part of that code, up to where $collide is set to false, manipulates the movement of the enemy sprite. The "movingright" element is set to true when the enemy should be moved to the right by 5 pixels, and false when it should be moved to the left by five pixels. As you can see, $enemydest holds the X and Y information of the enemy also, and it is set to bounce the enemy from the left-hand to the right-hand side of the screen continuously.

Now, $collide is set to false by default, which means no collision has taken place. However, we then compare the four key sets of values to decide whether a collision has taken place. In order, these are:

  1. The X co-ordinate of the player plus the width of the player sprite must be greater than the X co-ordinate of the enemy.

  2. The X co-ordinate of the player must be less than the X co-ordinate of the enemy plus the width of the enemy sprite

  3. The Y co-ordinate of the player plus the height of the player sprite must be greater than the Y co-ordinate of the enemy.

  4. The Y co-ordinate of the player must be less than the Y co-ordinate of the enemy plus the height of the enemy sprite.

If all four of these match up as true, we set $collide to be true. Moving on, if $collide has been set to true, we clear the screen using our new $red colour, and if $collide is not true, we use plain old black.

There are only two more things to do before the script can be run, and the first of these is to have the enemy sprite drawn using phpSDL_BlitSurface(). Add this line just before the call to phpSDL_Flip() :

phpSDL_BlitSurface($enemy, NULL, $video, $enemydest);

As we draw the enemy after the player, the enemy will appear to be on top of the player when they collide - you can move the enemy drawing to before the player to get the reverse effect.

The final change to make is to free the $enemy surface using phpSDL_FreeSurface(). Put this line just before the close PHP tag at the end of the script:

phpSDL_FreeSurface($enemy);

You're done! If you have followed all the steps so far you should be able to run the program again and see the whole screen light up when a collision takes place.


The last thing we're going to look at before we leave PHP SDL for good is animation. Animation, once you get to the code level, is simply a matter of displaying different bitmaps in sequence - an animation can be considered as playing frame 1, frame 2, and frame 3, then looping.

In order to provide a very basic animation, I created three copies of my enemy1.bmp picture, with each one being a little lighter than the previous one. Here are the four frames of my animation:


Generally the best way to handle complex objects such as this is to have a class upon which you can simply call Draw() - it automatically updates the animation and draws it in the right place. However, it'd take quite some time to explain all that, so we will do without OOP this time. Instead, we're going to turn $enemy into an array of surfaces, so replace you are the $enemyphpSDL_LoadBMP() line with this:

for ($i = 1; $i < 5; ++$i) {
    
$enemy[$i] = phpSDL_LoadBMP("enemy$i.bmp");
}

$enemyanim = 1;

Author's Note: You can achieve the same effect by having all four pictures in one physical surface, and varying your source rectangles to get the right part drawn. This is easier to understand, though!

Here we loop from 1 to 4, loading a surface into each of the four elements in $enemy. We also use a variable, $enemyanim, to store the frame number currently being shown. In order to make sure all the calculations are correct, you need to change instances of $enemy to $enemy[$enemyanim], e.g.:

if ($enemydest['x'] + $enemy['w'] >= 640) $enemydest['movingright'] = false;

Becomes this:

if ($enemydest['x'] + $enemy[$enemyanim]['w'] >= 640) $enemydest['movingright'] = false;

And:

phpSDL_BlitSurface($enemy, NULL, $video, $enemydest);

Becomes this:

phpSDL_BlitSurface($enemy[$enemyanim], NULL, $video, $enemydest);

Make sure and get them all, otherwise there will be problems as SDL tries to use a basic PHP array as a surface. You also need to increment the animation frame with each drawing loop iteration, so add this above where $collide is set:

++$enemyanim;
if (
$enemyanim == 5) $enemyanim = 1;

This makes your animation quite quick to begin with, but I will show you to how to deal with that shortly. The final change is in the resource cleanup - the current one-liner of phpSDL_FreeSurface($enemy) needs to be this:

for ($i = 1; $i < 5; ++$i) {
    
phpSDL_FreeSurface($enemy[$i]);
}

This cycles through all four surfaces in the array and frees them all properly. If your animation has a different number of frames, just change the loops - it should work fine.

If you run your script now, you should see the animation whizzing by quickly because we change the frame every time we update the scene. While this might be OK for some uses, you will usually want to time your animation so it changes on a regular basis. The easiest way to do this is to use a function called phpSDL_GetTicks(), which returns the number of milliseconds since the SDL library was started up using phpSDL_Init().

To use this, you first need to create a variable outside the loop to store the initial return value from phpSDL_GetTicks(). Add this just before $done = false:

$lastanimchange = phpSDL_GetTicks();

With that in place, we now need to make the animation change only when a certain number of milliseconds have passed. This code works on 1000 milliseconds, or one second:

if ($lastanimchange + 1000 < phpSDL_GetTicks()) {
    ++
$enemyanim;
    if (
$enemyanim == 5) $enemyanim = 1;
    
$lastanimchange = phpSDL_GetTicks();
}

What that code does is first check whether the last animation change time plus 1000 milliseconds is less than the return value of phpSDL_GetTicks(). If it is, then more than a second has passed since the last animation change, so we change the anim, then re-set $lastanimchange to the new value of phpSDL_GetTicks() so that another 1000 milliseconds must pass before the animation changes again.

Having all this done using OOP makes much more sense - it is not really smart to have the main game drawing loop have to have hard-coded values such as the enemy sprite's animation speed, and will likely lead to maintenance problems in the future. I have not used OOP here as it is quite long-winded to print, however please do so in your own work!

 

Next chapter: Games conclusion >>

Previous chapter: Clearing the screen

Jump to:

 

Home: Table of Contents

Follow us on Identi.ca or Twitter

Username:   Password:
Create Account | About TuxRadar