aGPJ Tutorial 4


A GameProgrammer's Journey

0x08 graphic

Table of contents

Tutorial 4 - How to create a platform game

Here we go again!

Welcome to the next part of the platform series. In this tutorial we are going to finish the work we've started in the previous tutorial.

If you have not finished the previous tutorial, I strongly suggest you finish that one first.

To make sure you have the correct code and images, download the file at the bottom of the page The images in this file are made by me, you are of course allowed to use your own if you want to.

Right, grab yourself a soda, unplug the phone, lock the doors so you can't be disturbed... here we go!

Sound & Images

0x01 graphic
(Doubleclick on the icon to get the images for this tutorial)

What's the plan

Usually the big idea in platform games is not very spectacular. You've got your basic hero, some coins or items to collect, a few enemies to get in the hero's way and an exit of somesort, usually to go to the next level.

All these ingredients are also going to be in our game. Note that I'm not going to dicuss all parts in great detail. I do not want this tutorial to reach the size of a book, there are still going to be things for you to figure out yourself, in case you're planning to write the next Prince of Persia or something :)

Drawing the Items

We are going to start with something simple: Items. The procedure for creating items and drawing them is fairly easy.

To get the items on our screen we are going to use the same methode as we used for the tiles. Let's have a look at the new fgland.bmp, that was in the zip you've downloaded a few minutes ago. There are two new colors in the image.

0x01 graphic

One, purple, wil be used in a later chapter, the other, light blue, is meant for the items.

To make this work we need to do four things:

  1. create the items class and variables

  2. adjust the procedure Loadmap a little

  3. write a procedure which loads the actual items, and make sure it gets called when the game starts

  4. draw the items.

Ok, here we go with the first part;

type TDirection = (dLeft, dRight, rNone);

TGameItemType = (banana, apple);

type

TGameItem = class(TImageSprite)

SpriteImg : TDirectDrawSurface;

isDead : boolean;

kind : TGameItemType;

end;

const maxSprites = 10;

maxItems = 10;

var

gameitem : array [0..maxItems] of TGameItem;

Then, the modification to the loadMap procedure:

procedure TForm1.LoadMap(level:byte);

var xpos,ypos : integer;

currentColor : TColor;

Bitmap: TBitmap;

begin

try

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile(ExtractFilePath(application.ExeName)+'fgland.bmp');

for xpos:=0 to MAPWIDTH do

for ypos:=0 to MAPHEIGHT do

begin

currentColor := bitmap.Canvas.Pixels[xpos,ypos];

case currentColor of

clfuchsia..clAqua :

begin

if TileInfo[xpos-1,ypos].tilenr = 100 then

TileInfo[xpos,ypos].tilenr:=100

else TileInfo[xpos,ypos].tilenr:=6;

end;

clwhite : TileInfo[xpos,ypos].tilenr:=100;

(..)

And the new procedure called loadItems;

procedure TForm1.LoadItems(level:byte);

var x,y : integer;

currentColor: TColor;

Bitmap: TBitmap;

itemCounter : integer;

itemKind : byte;

begin

try

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile(ExtractFilePath(application.ExeName)+'fgland.bmp');

itemCounter := 0;

for x:=0 to MAPWIDTH do

for y:=0 to MAPHEIGHT do

begin

itemKind := random(2);

currentColor := bitmap.Canvas.Pixels[x,y];

if (currentColor = clAqua) and (itemCounter <= maxItems) then

begin

gameItem[itemCounter] := TGameItem.Create(Form1.DXSpriteEngine1.Engine);

gameItem[itemCounter].x := x*32;

gameItem[itemCounter].y := y*32+2;

gameItem[itemCounter].isDead:=false;

gameItem[itemCounter].SpriteImg := TDirectDrawsurface.Create(DXDraw1.ddraw);

gameItem[itemCounter].SpriteImg.LoadFromGraphic

(Form1.DXImagelist1.items.items[itemKind+12].picture.graphic);

gameItem[itemCounter].SpriteImg.TransparentColor:=clblack;

case itemKind of

0: begin

gameItem[itemCounter].kind := apple;

gameItem[itemCounter].width := 30;

gameItem[itemCounter].height := 27;

end;

1: begin

gameItem[itemCounter].kind := banana;

gameItem[itemCounter].width := 21;

gameItem[itemCounter].height := 29;

end;

end;

inc(itemCounter);

end;

end;

Bitmap.free;

except

messagedlg('error while loading the map',mtinformation,[mbok],0);

end;

end;

Ok, so what does all this do? The modification is actually a check to see which tile we are. The background of the tile can either be the open-air (tilenumber = 100), or a wall in which case the tilenumber = 6.

The procedure loops through the bitmap and checks for all items. When it has found one it'll create an item.

Of course, we must not forget to actually declare and call the procedure...

{ Private declarations }

public

procedure LoadMap(level:byte);

procedure LoadItems(level:byte);

(..)

wallpaper3 := TDirectDrawsurface.Create(DXDraw1.ddraw);

wallpaper3.LoadFromGraphic(Form1.DXImagelist1.items.items[10].picture.graphic);

Loadmap(0);

LoadItems(0);

(..)

The final part of this chapter, drawing the items is done rather easy now:

for imgCounter:= 0 to maxItems do

begin

if Gameitem[imgCounter].isDead = false then

begin

dxdraw1.Surface.draw(round(Gameitem[imgCounter].x)

+scrollHorizontal,round(Gameitem[imgCounter].y),

Gameitem[imgCounter].SpriteImg.clientrect, Gameitem[imgCounter].SpriteImg,true);

end;

end;

Place the code above in the dxtimer event, right after the part where the other tiles are drawn.

We loop though all items, check if an item isn't dead (picked up) and then, taking the scrolling into consideration, draw the items.

Drawing the npc's

Now that you know how the items are done, putting some other characters in all this isn't hard anymore.

We'll follow the same plan again:

type

TOurSprite = class(TImageSprite)

SpriteImg : TDirectDrawSurface;

Direction : TDirection;

walking : boolean;

isDead : boolean;

Speed : single;

startFrame : integer;

CurrentFrame : integer;

MaxFrame : integer;

function testForWall(spriteXpos,spriteYpos:single;

spriteWidth,spriteHeight:integer):boolean;

procedure ChangeFrame(nr:byte);

end;

var

npc : array [0..maxSprites] of TOurSprite;

(...)

procedure TForm1.LoadEnemyMap(level:byte);

var x,y : integer;

currentColor: TColor;

Bitmap: TBitmap;

spriteCounter : byte;

begin

try

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile(ExtractFilePath(application.ExeName)+'fgland.bmp');

spriteCounter := 0;

for x:=0 to MAPWIDTH do

for y:=0 to MAPHEIGHT do

begin

currentColor:= bitmap.Canvas.Pixels[x,y];

if (currentColor = clfuchsia) and (spriteCounter <= maxSprites) then

begin

npc[spriteCounter] := TOurSprite.Create(Form1.DXSpriteEngine1.Engine);

npc[spriteCounter].x := x*32;

npc[spriteCounter].y := y*32-14;

npc[spriteCounter].width := 23;

npc[spriteCounter].height := 45;

npc[spriteCounter].isDead:=false;

npc[spriteCounter].Direction:=dRight;

npc[spriteCounter].speed := 1;

npc[spriteCounter].startFrame := 26;

npc[spriteCounter].CurrentFrame := npc[spriteCounter].startFrame;

npc[spriteCounter].MaxFrame := 9;

npc[spriteCounter].SpriteImg := TDirectDrawsurface.Create(DXDraw1.ddraw);

npc[spriteCounter].SpriteImg.LoadFromGraphic

(Form1.DXImagelist1.items.items[26].picture.graphic);

npc[spriteCounter].SpriteImg.TransparentColor:=rgb(125,150,255);

inc(spriteCounter);

end;

end;

Bitmap.free;

except

messagedlg('error while loading the map',mtinformation,[mbok],0);

end;

end;

You should recognize most things from the previous chapter. The class has a few extra's, but nothing fancy. Well, mayby the testForWall function, but that will be explained later on...

First, add this code, to call the procedure when the game is started:

(..)

Loadmap(0);

LoadItems(0);

LoadEnemyMap(0)

Then, it's time to write the drawing routine in the dxtimer event.

Make sure you place the code below the part that draws the tiles, otherwise you won't see the npcs.

for imgCounter := 0 to maxSprites do

begin

//(1) Npc moves right: turn npc to the left when it is close to an edge

if (TileInfo[(round(npc[imgCounter].x)+30) div 32 ,(round(npc[imgCounter].y)+65)

div 32].tilenr in [6,7,100]) or npc[imgCounter].testForWall(npc[imgCounter].x,

npc[imgCounter].y,npc[imgCounter].width+5,

npc[imgCounter].height) then

begin

npc[imgCounter].Direction := dLeft;

npc[imgCounter].startFrame := 16;

npc[imgCounter].CurrentFrame := npc[imgCounter].startFrame;

end;

//(1)Npc moves left: turn nps to the right when it is close to an edge

if (TileInfo[round(npc[imgCounter].x+2) div 32 ,round(npc[imgCounter].y+65)

div 32].tilenr in [6,7,100]) or npc[imgCounter].testForWall(npc[imgCounter].x,

npc[imgCounter].y, -5, npc[imgCounter].height) then

begin

npc[imgCounter].Direction := dRight;

npc[imgCounter].startFrame := 26;

npc[imgCounter].CurrentFrame := npc[imgCounter].startFrame;

end;

//(2)move the npc

if gamecounter mod 2 = 0 then

case npc[imgCounter].Direction of

dLeft : npc[imgCounter].x := npc[imgCounter].x - npc[imgCounter].speed;

dRight : npc[imgCounter].x := npc[imgCounter].x + npc[imgCounter].speed;

end;

//(3)If npc isn't dead then draw it

if npc[imgCounter].isDead = false then

begin

npc[imgCounter].ChangeFrame(0);

dxdraw1.Surface.draw(round(npc[imgCounter].x)+scrollHorizontal,round(npc[imgCounter].y),

npc[imgCounter].SpriteImg.clientrect, npc[imgCounter].SpriteImg,true);

end;

end;

A bit more code than the items had, I know, but then,... npc's move, items don't :) In short what is happening...

The testForWall function is called and returns a true/false value. If it's true the npc turns(1) otherwise it will move on (2).

But what exactly does the testForWall function do? Here's the answer...

function TOurSprite.testForWall(spriteXpos,spriteYpos:single;

spriteWidth,spriteHeight:integer):boolean;

begin

testForWall := false;

//test topside

if not (TileInfo[((round(spriteXpos)+spriteWidth) div 32),

(round(spriteYpos) div 32)].tilenr in [6,7,100])

//test bottomside

or not ((TileInfo[((round(spriteXpos)+spriteWidth) div 32),

((round(spriteYpos)+spriteHeight) div 32)].tilenr in [6,7,100])

and (TileInfo[(round(spriteXpos) div 32),

((round(spriteYpos)+spriteHeight) div 32)].tilenr in [6,7,100]))

then testForWall := true;

end;

The function is testing for four points: topleft, topright, bottomleft and bottomright. If one of those gets inside a tile which has a value of 6,7 or 100, which in this case means air or a background wall then it will return true, meaning the character has hit a wall. Otherwise it will return false, the character is free to go.

And last but not least, the changeFrame animation.

procedure TOurSprite.ChangeFrame(nr:byte);

begin

if nr = 0 then

begin

if GameCounter mod 5 = 0 then

begin

if CurrentFrame < startFrame+MaxFrame-1 then

inc(CurrentFrame)

else

CurrentFrame:= StartFrame;

end;

end

else

CurrentFrame := nr;

spriteImg.LoadFromGraphic(form1.dximagelist1.Items.Items[currentFrame].Picture.Graphic);

end;

Go ahead,.. look what we've done sofar.

The first steps

It's beginning to look pretty cool no? One important thing is still missing, though. Our hero.

Implementing him is not going to be as easy I'm afraid. The thing is, we can't just give him the same 'treatment' as the npcs.

They are 'stuck' on their own platform while our guy has to able to jump around from one platform to another.

Let's start with a new list of things to do:

  1. Modify ourSprite class;

  2. Draw hero at a certain starting position;

  3. create basic walk routines;

  4. add gravity;

  5. make hero jump.

Right. let's start with the first piece of code, a couple of modifications to the class OurSprite.

type

TOurSprite = class(TImageSprite)

SpriteImg : TDirectDrawSurface;

Direction : TDirection;

walking : boolean;

isDead : boolean;

Speed : single;

startFrame : integer;

CurrentFrame : integer;

MaxFrame : integer;

isJumping : boolean;

OnPlatform : boolean;

inAir: boolean;

yVelocity : integer;

function testForWall(spriteXpos,spriteYpos:single;

spriteWidth,spriteHeight:integer):boolean;

procedure ChangeFrame(nr:byte);

//procedure testForCollision;

end;

var player : TOurSprite;

Before we are able to display our hero on to the screen, we have to create him. We'll do this in the oninitialize event.

procedure TForm1.DXDraw1Initialize(Sender: TObject);

(..)

player:= TOurSprite.Create(Form1.DXSpriteEngine1.Engine);

player.SpriteImg := TDirectDrawsurface.Create(DXDraw1.ddraw);

player.SpriteImg.LoadFromGraphic(Form1.DXImagelist1.items.items[16].picture.graphic);

player.SpriteImg.TransparentColor:=clblack;

player.x:=6*32;

player.y:=(4*32)-14;

player.Height:= 46;

player.width := 28;

player.Direction:=dRight;

player.startFrame := 26;

player.CurrentFrame := player.startFrame;

player.MaxFrame := 9;

Nothing special to see here, so I'm quickly moving on to point three. The basic walk routines.

In the previous tutorial, I showed you how to make the map scroll. We are now going to use this code to make our guy move. The trick is to let the character move together with the screen behind him. Here's how it goes.

Modify the code which is used to scroll the screen in to the following:

DXInput1.Update;

player.walking :=false;

if not player.testForWall(player.x,player.y,-2,player.height-1) then

begin

if (isleft in DXInput1.States) then

begin

if (scrollHorizontal< 0) and (player.x + scrollHorizontal< 400) then

inc(scrollHorizontal,2);

if player.Direction <> dLeft then

begin

player.startFrame := 16;

player.currentFrame := 16;

end;

player.Direction:=dLeft;

player.walking:=true;

end;

end;

if not player.testForWall(player.x,player.y,player.width,player.height-1) then

begin

if (isRight in DXInput1.States) then

begin

if (player.x > 400) and (abs(scrollHorizontal) < (MAPWIDTH*32)-dxdraw1.Width)

then dec(scrollHorizontal,2);

if player.Direction <> dRight then

begin

player.startFrame := 26;

player.currentFrame := 26;

end;

player.Direction:=dRight;

player.walking:=true

end;

end;

if player.walking then

begin

case player.Direction of

dLeft : player.x := player.x - 2;

dRight : player.x := player.x + 2;

end;

player.ChangeFrame(0);

end

else

begin

case player.Direction of

dLeft : player.ChangeFrame(16);

dRight : player.ChangeFrame(26);

end;

end;

When the left or right button has been pressed and the testForWall function returns false (no hit) the map will start to scroll and our hero will start walking.

Notice the commented lines. When the end of the map has been reached (and the scrolling stops) what do you think will our character do?

Simple, our hero will continue to walk in the choosen direction (as it should be). But, what do think will happen when he starts to move in to the opposite direction? Correct, the screen will immediately start to scroll again. But that is not what we want. We want the screen to start scrolling when the hero is in the center. That is why I added that code. Go ahead and remove the {}, add the code that draws the guy and have a look at the game so far.

dxdraw1.surface.draw(round(player.x)+scrollHorizontal,round(player.y), player.SpriteImg.clientrect,

player.SpriteImg,true);

Adding gravity

When you run the game you'll see that our hero is not obeying the laws of gravity very well. This would be cool if he's superman, but, unfortunately for us, he's not. So we have to add some gravity to the game, which will hopefully give our character some vertical movements too...

When you've been looking around on the Web you might have seen this document . It's a pretty good explanation of how to add gravity to sprites. Excellent starting material for our platform game.

There's one tiny problem though. The article describes that when a certain point has been reached, the character should stop falling. The problem is that in a platform game, you don't know where that point is going to be. It could be 2 tiles below but it could also be 3 or 5 tiles. ie you can never for example say, "if player.y = 100 then falling:=false" or something like that. Because most of the time you'll end up somewhere inside a platform.

What we need to do is to figure out when such a point is, at a given time. There are probably numerous ways to solve this. This is one of them.

procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);

var xpos,ypos : integer;

stepsToFloor: integer;

floorFound : boolean;

imgCounter : integer;

begin

DXInput1.Update;

player.walking :=false;

stepsToFloor:=0;

floorFound := false;

player.inAir := true;

while (not floorFound) do

begin

dec(stepsToFloor);

if (player.testforwall(player.x,player.y,player.width-1,abs(stepsToFloor)))

then floorFound:= true;

if stepsToFloor < -100 then floorFound := true;

end;


Ok, what's happening here. It's actually pretty simple. We use a variable stepsToFloor in combination with the testForWall function. when y+stepsToFloor results is true then we have found a floor.
In case stepsToFloor reaches -100 (don't ask me why I used a negative value here) then apparently no floor has been found.
Notice that this piece of code will be called before the vertical movement is actually performed!

Now, when we do find a floor, ie stepsToFloor > -100. Then we continue with this:

if (abs(player.yVelocity) - abs(stepsToFloor+player.height)) >= 0 then

begin

player.y := abs(player.y + abs(stepsToFloor+player.height));

player.isJumping:=false;

player.OnPlatform:=true;

player.inAir:=false;

player.yVelocity:=0;

end

else

player.isJumping:=true;

We test the value of stepsToFloor and use it to calculate the y-velocity.

Have a look at the following example:
current y-velocity = 8;
current y-position = 400;
next platform = 416;

With these values all is well, the character will fall, the code above will not be used.
But at the next loop the y-velocity has increased a bit (see the
doc why)

current y-velocity = 10;
current y-position = 408;
next platform = 416;

With the curtent velocity the character will end up inside the floor. This time however the code will be used and the y-velocity will be changed to the remaining value of stepsToFloor and the character will land exactly on top of the platform.

The rest is easy now.

if (player.inAir) then //* Move Joe if we are jumping */

begin

if (player.yVelocity > -40) then // Limit Joe's velocity to -40 */

begin

player.yVelocity := player.yVelocity - 1;

player.y := player.y - player.yvelocity;

end;

end;

Every time our hero is falling we decrease the y position a little.
As you can see, I even used some of Edgar Roman's comments to see what piece of his puzzle goes into mine :)

That's about it. You can run the game now.

The first jumps

When you run the game, you should be able to move the character and walk off of the platform and land a couple of tiles below.

The next thing we are going to do is handling the jumping. Because we do want to see our guy move towards the end of the level.

Fortunately with the code in the previous chapter doing all the hard work, this is only a matter of giving our hero a gentle kick in the butt. :)

if (isbutton1 in Form1.dxinput1.States) and (player.isjumping = false) then

begin

player.yVelocity:=10;

player.y:=player.y-5;

player.inAir:=true;

player.isJumping:=true;

player.OnPlatform:=false;

end;

if (player.inAir) then //* Move Joe if we are jumping */

(..)

There you go. When button 1 (spacebar) is pressed, the y_velocity is initiated, the y-position is placed a few pixels up (the kick in the butt) and a couple of vars are set. That's really all there is to it... it's all sooo easy :)

Beam me up scotty

In platform games it's not uncommen for characters to 'warp' themselves to other places on the map. How is this accomplished?

Well, it's really not that hard.

Let's say we have the following transport device in our game. (You should know by now how you draw and create images, so I'm going a little bit faster here)

var

teleporterStart : TDirectDrawSurface;

teleporterEnd : TDirectDrawSurface;

(..)

teleporterStart := TDirectDrawsurface.Create(Form1.DXDraw1.ddraw);

teleporterStart.LoadFromGraphic(Form1.DXImagelist1.items.items[14].picture.graphic);

teleporterStart.TransparentColor:=clblack;

teleporterEnd := TDirectDrawsurface.Create(Form1.DXDraw1.ddraw);

teleporterEnd.LoadFromGraphic(Form1.DXImagelist1.items.items[15].picture.graphic);

(..)

dxdraw1.Surface.Draw((32*16)+scrollHorizontal,(32*14),

teleporterStart.clientrect, teleporterStart,true);

dxdraw1.Surface.Draw((32*44)+scrollHorizontal,(32*3),

teleporterEnd.clientrect, teleporterEnd,false);

We can then use the following code to actually perform a transport.

Place this code just below the jump code:

if (isup in Form1.dxinput1.States) and ((round(player.x)-400 in [115..130]) and (player.y > 448)) then

begin

player.y:=(1*32)-14;

player.x:=(32*44);

scrollHorizontal := -(32*32);

end;

What's happing here is that I'm checking for the characters coordinates. If they match then a transport will occur. This actually means that the x- and y value of the hero as well as variable that holds the scrolling value are changed.

Note that in case you are going to use this in your own games, make sure you include the start/end coordinates in an editor!

Right...the only thing that is missing now is a fancy animation a la StarTrek, but I'll leave that to you :)

Finishing the items

Our game is coming up rather nicely now. Only a few things are remaining: Collecting the items and freeing the images.

Because of the way I handled the movements, we can't really use the collision detection system found in DelphiX. This isn't such a big problem though. The items in our game don't require a fancy detection system.

procedure TOurSprite.testForCollision;

var counter : byte;

begin

for counter := 0 to maxItems do

begin

if gameitem[counter].isDead = false then

begin

if (x+(width div 2) >= gameItem[counter].X) and

(x+(width div 2) <= gameItem[counter].x+gameItem[counter].width) and

((y+(height div 2) > gameItem[counter].y) and

(y+(height div 2) < gameItem[counter].y+gameItem[counter].height)) then

gameItem[counter].isDead := true;

end;

end;

end;

This is all. We loop through all the items and check for a certain point. If that point corresponds with the position of the player then we mark the item as dead, ie the item nolonger gets drawn.

To make all this work, we must not forget to make the call to this procedure.

procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);

var xpos,ypos : integer;

stepsToFloor: integer;

floorFound : boolean;

imgCounter : integer;

begin

(..)

player.testForCollision;

As we are nearing the end now, we must not forget to free all objects and images used in the game.

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);

var imgCounter: byte;

begin

for imgCounter := 0 to MAXTILES do Tiles[imgCounter].free;

buildings.free;

wallpaper1.free;

wallpaper2.free;

wallpaper3.free;

teleporterStart.free;

teleporterEnd.free;

for imgCounter := 0 to maxItems do

begin

gameitem[imgCounter].SpriteImg.free;

gameitem[imgCounter].Free;

end;

for imgCounter := 0 to maxSprites do

begin

npc[imgcounter].SpriteImg.free;

npc[imgCounter].free;

end;

player.SpriteImg.free;

player.Free;

end;

The End

Well done. The final piece of this tutorial has been layed and I can congratulate you on a job well done. If all is right, then you now have a working platform game.

Of course there are a lot of things that could need more work. A score system, real enemies that actually do something, or perhaps extra levels. But I'll leave those things entirely up to you. With the basics done, you are now ready to make it in to anything you want.

I'm looking forward to your creations.

Until next time, Happy coding

PS. I have added the complete source for this tutorial on the next page!

Full source code

Below the entire source for the game.

unit Tut4Unit;

//*******************************************************/

// Source for the tutorial: How to create a platform game

// Copyright 2002-2003 by Alexander Rosendal

// http://www.gameprogrammer.net

// for questions mail to: traveler@gameprogrammer.net

//*******************************************************/

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

Dialogs, DXDraws, DXSprite, DXInput, DXClass;

type

TTileInfo = record

tilenr : integer;

obstacle : boolean; // can both be wall or floor

item : byte; // bonus, health, etc

end;

type TDirection = (dLeft, dRight, rNone);

TGameItemType = (banana, apple);

type

TGameItem = class(TImageSprite)

SpriteImg : TDirectDrawSurface;

isDead : boolean;

kind : TGameItemType;

end;

type

TOurSprite = class(TImageSprite)

SpriteImg : TDirectDrawSurface;

Direction : TDirection;

walking : boolean;

isDead : boolean;

Speed : single;

startFrame : integer;

CurrentFrame : integer;

MaxFrame : integer;

isJumping : boolean;

OnPlatform : boolean;

inAir: boolean;

yVelocity : integer;

function testForWall(spriteXpos,spriteYpos:single;

spriteWidth,spriteHeight:integer):boolean;

procedure ChangeFrame(nr:byte);

procedure testForCollision;

end;

var player : TOurSprite;

const maxSprites = 10;

maxItems = 10;

var

gameitem : array [0..maxItems] of TGameItem;

npc : array [0..maxSprites] of TOurSprite;

var

teleporterStart : TDirectDrawSurface;

teleporterEnd : TDirectDrawSurface;

type

TForm1 = class(TForm)

DXDraw1: TDXDraw;

DXTimer1: TDXTimer;

DXInput1: TDXInput;

DXSpriteEngine1: TDXSpriteEngine;

DXImageList1: TDXImageList;

procedure DXDraw1Initialize(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure DXTimer1Timer(Sender: TObject; LagCount: Integer);

procedure FormClose(Sender: TObject; var Action: TCloseAction);

private

{ Private declarations }

public

procedure LoadMap(level:byte);

procedure LoadItems(level:byte);

procedure LoadenemyMap(level:byte);

{ Public declarations }

end;

const MAPHEIGHT = 18;

const MAPWIDTH = 100;

const MAXTILES = 7;

var

Form1: TForm1;

gameCounter : integer;

TileInfo : array [0..MAPWIDTH, 0..MAPHEIGHT ] of TTileInfo;

Tiles : array[0..MAXTILES] of TDirectDrawSurface;

scrollHorizontal: integer;

buildings : TDirectDrawSurface; // used for the background

wallpaper1 : TDirectDrawSurface;

wallpaper2 : TDirectDrawSurface;

wallpaper3 : TDirectDrawSurface;

implementation

{$R *.DFM}

procedure TForm1.DXDraw1Initialize(Sender: TObject);

var imgCounter : byte;

begin

buildings := TDirectDrawsurface.Create(DXDraw1.ddraw);

buildings.LoadFromGraphic(Form1.DXImagelist1.items.items[11].picture.graphic);

wallpaper1 := TDirectDrawsurface.Create(DXDraw1.ddraw);

wallpaper1.LoadFromGraphic(Form1.DXImagelist1.items.items[8].picture.graphic);

wallpaper2 := TDirectDrawsurface.Create(DXDraw1.ddraw);

wallpaper2.LoadFromGraphic(Form1.DXImagelist1.items.items[9].picture.graphic);

wallpaper3 := TDirectDrawsurface.Create(DXDraw1.ddraw);

wallpaper3.LoadFromGraphic(Form1.DXImagelist1.items.items[10].picture.graphic);

teleporterStart := TDirectDrawsurface.Create(Form1.DXDraw1.ddraw);

teleporterStart.LoadFromGraphic(Form1.DXImagelist1.items.items[14].picture.graphic);

teleporterStart.TransparentColor:=clblack;

teleporterEnd := TDirectDrawsurface.Create(Form1.DXDraw1.ddraw);

teleporterEnd.LoadFromGraphic(Form1.DXImagelist1.items.items[15].picture.graphic);

Loadmap(0);

LoadItems(0);

LoadEnemyMap(0);

player:= TOurSprite.Create(Form1.DXSpriteEngine1.Engine);

player.SpriteImg := TDirectDrawsurface.Create(DXDraw1.ddraw);

player.SpriteImg.LoadFromGraphic(Form1.DXImagelist1.items.items[16].picture.graphic);

player.SpriteImg.TransparentColor:=clblack;

player.x:=6*32;

player.y:=(4*32)-14;

player.Height:= 46;

player.width := 28;

player.Direction:=dRight;

player.startFrame := 26;

player.CurrentFrame := player.startFrame;

player.MaxFrame := 9;

for imgCounter := 0 to MAXTILES do

begin

Tiles[imgCounter] := TDirectDrawsurface.Create(DXDraw1.ddraw);

Tiles[imgCounter].LoadFromGraphic(

Form1.DXImagelist1.items.items[imgCounter].picture.graphic);

end;

With dxdraw1.surface.Canvas do

begin

brush.Style := bsclear;

font.color := clred;

font.size := 9;

font.name := 'Arial';

end;

gameCounter := 1;

dxtimer1.Enabled:=true;

end;

procedure TForm1.FormCreate(Sender: TObject);

begin

Height:=600;

Width:=800;

end;

procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);

var xpos,ypos : integer;

stepsToFloor: integer;

floorFound : boolean;

imgCounter : integer;

begin

DXInput1.Update;

player.walking :=false;

stepsToFloor:=0;

floorFound := false;

player.inAir := true;

while (not floorFound) do

begin

dec(stepsToFloor);

if (player.testforwall(player.x,player.y,player.width-1,abs(stepsToFloor)))

then floorFound:= true;

if stepsToFloor < -100 then floorFound := true;

end;

if (abs(player.yVelocity) - abs(stepsToFloor+player.height)) >= 0 then

begin

player.y := abs(player.y + abs(stepsToFloor+player.height));

player.isJumping:=false;

player.OnPlatform:=true;

player.inAir:=false;

player.yVelocity:=0;

end

else

player.isJumping:=true;

if (isbutton1 in Form1.dxinput1.States) and (player.isjumping = false) then

begin

player.yVelocity:=10;

player.y:=player.y-5;

player.inAir:=true;

player.isJumping:=true;

player.OnPlatform:=false;

end;

if (isup in Form1.dxinput1.States) and ((round(player.x)-400 in [115..130])

and (player.y > 448)) then

begin

player.y:=(1*32)-14;

player.x:=(32*44);

scrollHorizontal := -(32*32);

end;

if (player.inAir) then //* Move Joe if we are jumping */

begin

if (player.yVelocity > -40) then // Limit Joe's velocity to -40 */

begin

player.yVelocity := player.yVelocity - 1;

player.y := player.y - player.yvelocity;

end;

end;

if not player.testForWall(player.x,player.y,-2,player.height-1) then

begin

if (isleft in DXInput1.States) then

begin

if (scrollHorizontal< 0) and (player.x + scrollHorizontal< 400) then

inc(scrollHorizontal,2);

if player.Direction <> dLeft then

begin

player.startFrame := 16;

player.currentFrame := 16;

end;

player.Direction:=dLeft;

player.walking:=true;

end;

end;

if not player.testForWall(player.x,player.y,player.width,player.height-1) then

begin

if (isRight in DXInput1.States) then

begin

if (player.x > 400) and (abs(scrollHorizontal) < (MAPWIDTH*32)-dxdraw1.Width)

then dec(scrollHorizontal,2);

if player.Direction <> dRight then

begin

player.startFrame := 26;

player.currentFrame := 26;

end;

player.Direction:=dRight;

player.walking:=true

end;

end;

if player.walking then

begin

case player.Direction of

dLeft : player.x := player.x - 2;

dRight : player.x := player.x + 2;

end;

player.ChangeFrame(0);

end

else

begin

case player.Direction of

dLeft : player.ChangeFrame(16);

dRight : player.ChangeFrame(26);

end;

end;

inc(gameCounter);

player.testForCollision;

for imgCounter := 0 to 2 do

dxdraw1.Surface.Draw(imgCounter*357,0, buildings.clientrect, buildings,false);

for xpos := 0 to (form1.Width) div 32 do

for ypos := 0 to form1.Height div 32 do

dxdraw1.surface.draw((xpos*32)+(scrollHorizontal mod 32),(ypos*32),

tiles[1].clientrect,tiles[TileInfo[xpos+(abs(scrollHorizontal div 32)),ypos].tilenr],false);

dxdraw1.Surface.Draw((32*23)+scrollHorizontal,(32*10), wallpaper1.clientrect, wallpaper1,false);

dxdraw1.Surface.Draw((32*77)+scrollHorizontal,(32*5), wallpaper1.clientrect, wallpaper1,false);

dxdraw1.Surface.Draw((32*88)+scrollHorizontal,(32*13), wallpaper2.clientrect, wallpaper2,false);

dxdraw1.Surface.Draw((32*27)+scrollHorizontal,(32*12), wallpaper2.clientrect, wallpaper2,false);

dxdraw1.Surface.Draw((32*4)+scrollHorizontal,(32*10), wallpaper3.clientrect, wallpaper3,false);

dxdraw1.Surface.Draw((32*16)+scrollHorizontal,(32*14),

teleporterStart.clientrect, teleporterStart,true);

dxdraw1.Surface.Draw((32*44)+scrollHorizontal,(32*3),

teleporterEnd.clientrect, teleporterEnd,false);

for imgCounter:= 0 to maxItems do

begin

if Gameitem[imgCounter].isDead = false then

begin

dxdraw1.Surface.draw(round(Gameitem[imgCounter].x)

+scrollHorizontal,round(Gameitem[imgCounter].y),

Gameitem[imgCounter].SpriteImg.clientrect, Gameitem[imgCounter].SpriteImg,true);

end;

end;

for imgCounter := 0 to maxSprites do

begin

//(1) Npc moves right: turn npc to the left when it is close to an edge

if (TileInfo[(round(npc[imgCounter].x)+30) div 32 ,(round(npc[imgCounter].y)+65)

div 32].tilenr in [6,7,100]) or npc[imgCounter].testForWall(npc[imgCounter].x,

npc[imgCounter].y,npc[imgCounter].width+5,

npc[imgCounter].height) then

begin

npc[imgCounter].Direction := dLeft;

npc[imgCounter].startFrame := 16;

npc[imgCounter].CurrentFrame := npc[imgCounter].startFrame;

end;

//(1)Npc moves left: turn nps to the right when it is close to an edge

if (TileInfo[round(npc[imgCounter].x+2) div 32 ,round(npc[imgCounter].y+65)

div 32].tilenr in [6,7,100]) or npc[imgCounter].testForWall(npc[imgCounter].x,

npc[imgCounter].y, -5, npc[imgCounter].height) then

begin

npc[imgCounter].Direction := dRight;

npc[imgCounter].startFrame := 26;

npc[imgCounter].CurrentFrame := npc[imgCounter].startFrame;

end;

//(2)move the npc

if gamecounter mod 2 = 0 then

case npc[imgCounter].Direction of

dLeft : npc[imgCounter].x := npc[imgCounter].x - npc[imgCounter].speed;

dRight : npc[imgCounter].x := npc[imgCounter].x + npc[imgCounter].speed;

end;

//(3)If npc isn't dead then draw it

if npc[imgCounter].isDead = false then

begin

npc[imgCounter].ChangeFrame(0);

dxdraw1.Surface.draw(round(npc[imgCounter].x)+scrollHorizontal,

round(npc[imgCounter].y),

npc[imgCounter].SpriteImg.clientrect, npc[imgCounter].SpriteImg,true);

end;

end;

dxdraw1.surface.draw(round(player.x)+scrollHorizontal,round(player.y),

player.SpriteImg.clientrect, player.SpriteImg,true);

DXDraw1.flip;

end;

function TOurSprite.testForWall(spriteXpos,spriteYpos:single;

spriteWidth,spriteHeight:integer):boolean;

begin

testForWall := false;

//test topside

if not (TileInfo[((round(spriteXpos)+spriteWidth) div 32),

(round(spriteYpos) div 32)].tilenr in [6,7,100])

//test bottomside

or not ((TileInfo[((round(spriteXpos)+spriteWidth) div 32),

((round(spriteYpos)+spriteHeight) div 32)].tilenr in [6,7,100])

and (TileInfo[(round(spriteXpos) div 32),

((round(spriteYpos)+spriteHeight) div 32)].tilenr in [6,7,100]))

then testForWall := true;

end;

procedure TOurSprite.testForCollision;

var counter : byte;

begin

for counter := 0 to maxItems do

begin

if gameitem[counter].isDead = false then

begin

if (x+(width div 2) >= gameItem[counter].X) and

(x+(width div 2) <= gameItem[counter].x+gameItem[counter].width) and

((y+(height div 2) > gameItem[counter].y) and

(y+(height div 2) < gameItem[counter].y+gameItem[counter].height)) then

gameItem[counter].isDead := true;

end;

end;

end;

procedure TOurSprite.ChangeFrame(nr:byte);

begin

if nr = 0 then

begin

if GameCounter mod 5 = 0 then

begin

if CurrentFrame < startFrame+MaxFrame-1 then

inc(CurrentFrame)

else

CurrentFrame:= StartFrame;

end;

end

else

CurrentFrame := nr;

spriteImg.LoadFromGraphic(

form1.dximagelist1.Items.Items[currentFrame].Picture.Graphic);

end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);

var imgCounter: byte;

begin

for imgCounter := 0 to MAXTILES do Tiles[imgCounter].free;

buildings.free;

wallpaper1.free;

wallpaper2.free;

wallpaper3.free;

teleporterStart.free;

teleporterEnd.free;

for imgCounter := 0 to maxItems do

begin

gameitem[imgCounter].SpriteImg.free;

gameitem[imgCounter].Free;

end;

for imgCounter := 0 to maxSprites do

begin

npc[imgcounter].SpriteImg.free;

npc[imgCounter].free;

end;

player.SpriteImg.free;

player.Free;

end;

procedure TForm1.LoadMap(level:byte);

var xpos,ypos : integer;

currentColor : TColor;

Bitmap: TBitmap;

begin

try

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile(ExtractFilePath(application.ExeName)+'fgland.bmp');

for xpos:=0 to MAPWIDTH do

for ypos:=0 to MAPHEIGHT do

begin

currentColor := bitmap.Canvas.Pixels[xpos,ypos];

case currentColor of

clfuchsia..clAqua :

begin

if TileInfo[xpos-1,ypos].tilenr = 100 then

TileInfo[xpos,ypos].tilenr:=100

else TileInfo[xpos,ypos].tilenr:=6;

end;

clwhite : TileInfo[xpos,ypos].tilenr:=100;

clblack : TileInfo[xpos,ypos].tilenr:=5;

clred : TileInfo[xpos,ypos].tilenr:=6;

clblue : TileInfo[xpos,ypos].tilenr:=7;

clgreen : TileInfo[xpos,ypos].tilenr:=random(5);

end;

end;

except

messagedlg('error while loading the map',mtinformation,[mbok],0);

end;

Bitmap.free;

end;

procedure TForm1.LoadItems(level:byte);

var x,y : integer;

currentColor: TColor;

Bitmap: TBitmap;

itemCounter : integer;

itemKind : byte;

begin

try

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile(ExtractFilePath(application.ExeName)+'fgland.bmp');

itemCounter := 0;

for x:=0 to MAPWIDTH do

for y:=0 to MAPHEIGHT do

begin

itemKind := random(2);

currentColor := bitmap.Canvas.Pixels[x,y];

if (currentColor = clAqua) and (itemCounter <= maxItems) then

begin

gameItem[itemCounter] := TGameItem.Create(Form1.DXSpriteEngine1.Engine);

gameItem[itemCounter].x := x*32;

gameItem[itemCounter].y := y*32+2;

gameItem[itemCounter].isDead:=false;

gameItem[itemCounter].SpriteImg := TDirectDrawsurface.Create(DXDraw1.ddraw);

gameItem[itemCounter].SpriteImg.LoadFromGraphic

(Form1.DXImagelist1.items.items[itemKind+12].picture.graphic);

gameItem[itemCounter].SpriteImg.TransparentColor:=clblack;

case itemKind of

0: begin

gameItem[itemCounter].kind := apple;

gameItem[itemCounter].width := 30;

gameItem[itemCounter].height := 27;

end;

1: begin

gameItem[itemCounter].kind := banana;

gameItem[itemCounter].width := 21;

gameItem[itemCounter].height := 29;

end;

end;

inc(itemCounter);

end;

end;

Bitmap.free;

except

messagedlg('error while loading the map',mtinformation,[mbok],0);

end;

end;

procedure TForm1.LoadEnemyMap(level:byte);

var x,y : integer;

currentColor: TColor;

Bitmap: TBitmap;

spriteCounter : byte;

begin

try

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile(ExtractFilePath(application.ExeName)+'fgland.bmp');

spriteCounter := 0;

for x:=0 to MAPWIDTH do

for y:=0 to MAPHEIGHT do

begin

currentColor:= bitmap.Canvas.Pixels[x,y];

if (currentColor = clfuchsia) and (spriteCounter <= maxSprites) then

begin

npc[spriteCounter] := TOurSprite.Create(Form1.DXSpriteEngine1.Engine);

npc[spriteCounter].x := x*32;

npc[spriteCounter].y := y*32-14;

npc[spriteCounter].width := 23;

npc[spriteCounter].height := 45;

npc[spriteCounter].isDead:=false;

npc[spriteCounter].Direction:=dRight;

npc[spriteCounter].speed := 1;

npc[spriteCounter].startFrame := 26;

npc[spriteCounter].CurrentFrame := npc[spriteCounter].startFrame;

npc[spriteCounter].MaxFrame := 9;

npc[spriteCounter].SpriteImg := TDirectDrawsurface.Create(DXDraw1.ddraw);

npc[spriteCounter].SpriteImg.LoadFromGraphic

(Form1.DXImagelist1.items.items[26].picture.graphic);

npc[spriteCounter].SpriteImg.TransparentColor:=rgb(125,150,255);

inc(spriteCounter);

end;

end;

Bitmap.free;

except

messagedlg('error while loading the map',mtinformation,[mbok],0);

end;

end;

end.

A GameProgrammer's Journey - Tutorial 4

Copyright Alexander Rosendal 2000-2003

Page 3

http://www.gameprogrammer.net/

Disclaimer: The tutorial below, including all images, is copyrighted by Alexander Rosendal. You are allowed to use the Delphi code and images for your own purposes. You are not allowed to copy (parts of) these tutorials without my prior written permission!

Give credit where credit is due. Thank you!
 



Wyszukiwarka

Podobne podstrony:
aGPJ Tutorial 2
bugzilla tutorial[1]
freeRadius AD tutorial
Alignmaster tutorial by PAV1007 Nieznany
free sap tutorial on goods reciept
ms excel tutorial 2013
Joomla Template Tutorial
ALGORYTM, Tutoriale, Programowanie
8051 Tutorial uart
B tutorial
Labview Tutorial
Obraz partycji (ghost2003) Tutorial
[LAB5]Tutorial do kartkówki
M2H Networking Tutorial Original
ABAQUS Tutorial belka z utwierdzeniem id 50029 (2)
eagle tutorial
c language tutorial
P J Ashenden VHDL tutorial
Anime drawing tutorials [ENG]

więcej podobnych podstron