PDA

View Full Version : Collision done directly with OnCollision ( don't understand it, help me understand)



robert83
28-08-2011, 02:54 PM
Hello everyone,

On my quest to find better (and more simple) ways to do collision I've stumbled upon this on a Asypre 2d tutorial, and quickly converted it to Andorra 2d ( did not knew before that I can get Sprite values from OnCollision ( example : TileLeft := Trunc(TPlatform(Sprite).X); )
// though I was wondering why it was written all over the Andorra 2d tutorials that the sprite engine is a very powerfull thing and that you are capable of writing games with it, given if you know how :)

Anyway it works, but not perfectly because I don't understand what these +4 -2 +5 etc are for... I've already changed parts of the original code which I was 100% sure in. The problem I'm having manifests itself by allowing my character to move inside tile if he's under it and he's head is just a bit higher then tile bottom, I can then move left and right in it.

I've opened the resource file that comes with the original project, the HERO pattern size is 32x48 , the blocks that he can do collision with are 32x32 (block1) and 64x64 (block2)

Part of the original code


TileLeft := Trunc(TTile(Sprite).X);
TileTop := Trunc(TTile(Sprite).Y);
TileRight := Trunc(TTile(Sprite).X) + TTile(Sprite).PatternWidth;
TileBottom := Trunc(TTile(Sprite).Y) + TTile(Sprite).PatternHeight;
if (TTile(Sprite).ImageName = 'Block1') or (TTile(Sprite).ImageName = 'Block2') then


My modified onMove code


procedure TBlurp.DoMove(TimeGap: Double);
begin
inherited;
Left := Round(X+2); // what is this +2 , same with +1 so it seems,no + not work
Top := Round(Y+4); // what is this ?
Right := Round(X + Image.Width);
Bottom := Round(Y + Image.Height);

// setting the camera to follow our HERO
if (X > 320) and (X < (40*TileW)-320-TileW) then
begin
// - 64 , because when loading the level my entire background is starting from 64 instead of 0
// looks better if there is no black line at the edge of the level...
camera_x:=(X*-1)+320-TileW;
end;
if (Y > 240) and (Y < (29*TileH)-240) then
begin
// 29 tiles down * 64 tile.height -240 half of the screen, only moving when at center of the screen
// well not really the center...but close anyway :)
camera_y:=(Y*-1)+240;
end;

if GetKeyState(VK_LEFT) < 0 then
begin
vx:=123;
x := x - vx * TimeGap;
end;
if GetKeyState(VK_RIGHT) < 0 then
begin
vx:=123;
x := x + vx * TimeGap;
end;
if GetKeyState(VK_UP) < 0 then
begin
vy:=123;
y := y - vy * TimeGap;
end;
if GetKeyState(VK_DOWN) < 0 then
begin
vy:=123;
y := y + vy * TimeGap;
end;

Collision;
end;


The oncollision


procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);
var
TileLeft, TileRight,
TileTop, TileBottom: Integer;
begin
if Sprite is TPlatform then
begin
TileLeft := Trunc(TPlatform(Sprite).X);
TileTop := Trunc(TPlatform(Sprite).Y);
TileRight := Trunc(TPlatform(Sprite).X) + TPlatform(Sprite).Image.Width;
TileBottom := Trunc(TPlatform(Sprite).Y) + TPlatform(Sprite).Image.Height;
// up
if GetKeyState(VK_UP) < 0 then
begin
if (Self.Top+5 > TileBottom) and
(Self.Right-4 > TileLeft) and
(Self.Left+3 < TileRight) then
Y := TileBottom;
end;
// down
if GetKeyState(VK_DOWN) < 0 then
begin
if (Self.Bottom -4 < TileTop ) and
(Self.Right -4 > TileLeft) and
(Self.Left +3 < TileRight) then
Y := TileTop-60;
end;
// left
if GetKeyState(VK_LEFT) < 0 then
begin
if (Self.Left+8 > TileRight) and
(Self.Top+5< TileBottom) and
(Self.Bottom-8>TileTop) then
X := TileRight;
end;
// right
if GetKeyState(VK_RIGHT) < 0 then
begin
if (Self.Right-8 < TileLeft )and
(Self.Top+5< TileBottom) and
(Self.Bottom-8>TileTop) then
X := TileLeft -30;
end;
end;
end;


Again I don't understand these +5 -8 etc... what is happening here ?

I've attached my source which is almos working correctly...
And the original material

Greetings
Robert
575
574

Daikrys
28-08-2011, 04:40 PM
change the DoMove to


Left := Round(X); // what is this +2 , same with +1 so it seems,no + not work
Top := Round(Y); // what is this ?


cause you set +4 already in OnCollision, this means if you set X+2 here it means you test Left(wich is X) later with Left+2+4 instead of Left+4

EDIT: meh stupid me, it should also work if you remove the +5, -8 it is only good if you have a sprite wich has transperant borders to match power of two, means a 24x56 sprite in a 32x64 texture
so you would have to set all +/- to +4 and -4 to make the sprite collision correctly without space between tile and character

robert83
28-08-2011, 05:13 PM
Thank you,

I've changed onMove to


Left := Round(X);
Top := Round(Y);
Right := Round(X + Image.Width);
Bottom := Round(Y + Image.Height);


It still works,

but if I remove those +5,-8 it does not works for up,left,right

I forgot to mention my hero sprite is 30x60... does that mean.. I have do to it like
this ?


// up
if GetKeyState(VK_UP) < 0 then
begin
if (Self.Top+4 > TileBottom) and
(Self.Right-2 > TileLeft) and
(Self.Left+2 < TileRight) then
Y := TileBottom;
end;
// down
if GetKeyState(VK_DOWN) < 0 then
begin
if (Self.Bottom -4 < TileTop ) and
(Self.Right -2 > TileLeft) and
(Self.Left +2 < TileRight) then
Y := TileTop-60;
end;
// left
if GetKeyState(VK_LEFT) < 0 then
begin
if (Self.Left+2 > TileRight) and
(Self.Top+4< TileBottom) and
(Self.Bottom-4>TileTop) then
X := TileRight;
end;
// right
if GetKeyState(VK_RIGHT) < 0 then
begin
if (Self.Right-2 < TileLeft )and
(Self.Top+4< TileBottom) and
(Self.Bottom-4>TileTop) then
X := TileLeft -30;
end;
end;


It works, but honestly... it did not do any difference, my character can still enter a wall
from any direction if he is only like 1-2 pixel in the wall , I will try to draw this down maybe I'll understand it then why is it doing this... collision should stop it from getting in...or maybe I need to set the speed or something , I'll check vx, vy to 0 ...

Greetings
Robert

robert83
28-08-2011, 08:04 PM
Hello, I've managed to do it... but still don't understand why the heck is it working



procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);
var
TileLeft, TileRight,
TileTop, TileBottom: Integer;
begin
if Sprite is TPlatform then
begin
TileLeft := Trunc(TPlatform(Sprite).X);
TileTop := Trunc(TPlatform(Sprite).Y);
TileRight := Trunc(TPlatform(Sprite).X) + TPlatform(Sprite).Image.Width;
TileBottom := Trunc(TPlatform(Sprite).Y) + TPlatform(Sprite).Image.Height;

// up
if (GetKeyState(VK_UP) < 0) then
begin
if (Self.Top+3 > TileBottom) and
(Self.Right > TileLeft) and
(Self.Left < TileRight) then
begin
Y := TileBottom;
end;
end;
// down
if (GetKeyState(VK_DOWN) < 0) then
begin
if (Self.Bottom -3 < TileTop ) and
(Self.Right > TileLeft) and
(Self.Left < TileRight) then
begin
Y := TileTop-PlayerH;
end;
end;
// left
if GetKeyState(VK_LEFT) < 0 then
begin
if (Self.Left+3 > TileRight) and
(Self.Top< TileBottom) and
(Self.Bottom>TileTop) then
begin
X := TileRight;
end;
end;
// right
if GetKeyState(VK_RIGHT) < 0 then
begin
if (Self.Right-3 < TileLeft )and
(Self.Top< TileBottom) and
(Self.Bottom>TileTop) then
begin
X := TileLeft -PlayerW;
end;
end;
end;
end;


it works like 99% of the time... sometimes, I can't seem to reproduce it every time I'm able to enter the tile....pfff

EDIT : by increasing left and right to +3 and -3 it seems I'm unable to enter the wall now...evern after trying to quiet a while...

my character is 30x60...

Greetings
Robert
576

User137
29-08-2011, 06:49 AM
Don't know why, but it just looks to me like the whole code is done by nearly hardest possible way. I can't even follow what the code is doing... Are you calculating collisions same place you move player? SpriteEngine way of coding is relic of the past DelphiX which was absolutely horrible way to make games. DoMove, DoCollision; all of those are optional steps that make the code more complicated than it should be. With vectors the code amount would be reduced by 60% while code would become more readable and faster.

That said, once you've already started that way i'm not one telling you to change all the work. Just, think the code through wholly. In what order are those class methods called and how do the variables change in between. Does your collision take into account if player is 5 pixels inside the wall, aswell as 6 or 16 pixels? If your movement is not frame-based or floating point then you can't rely on certain things.

Are the floats absolutely rounded correctly with either Trunc() or Round()? Do you draw the player from its center point or corner?

ps. I have also msn if you'd like to chat about things more detailed. Send a pm :)

robert83
29-08-2011, 08:51 AM
Hello,

I'm just trying out multiple ways to do the same thing...at first this seemed unbelievable simple should it work, my original idea was this (this you will understand ... better) :

I'm moving my character on the onMOVE with vx, vy (velocity), then testing for Collision, if my character collides with TPlatForm... then I check if he was going up down left or right... and according to that I set my character position to be positioned next to wall , like this :
if my character is going up, and he hits the TPlatform right above I do the following , Y:=TPlatForm(Sprite).Y+TPlatForm(Sprite).Height; set my character's Y coordinate to
TPlatForm(Sprite) -> this is the Sprite I've hit Y+ it's Height, so I'm right bellow it, no empty space... no need to check speed or whatever since it's not possible to get inside it this way.

Anyway when I've tried this out I was amazed that it worked, and that it's such a simple solution ... the problem came when I was trying to do the following :
Pressed up to hit the wall... then I've kept pressing up... and then tried to move left and right along the upper part... my character was moving incorrectly (at very high speed)... so I've tried the following to make it good, if I hit upper or lover sprite I've set vx:=0, if I've hit left or right I've set vy:=0, now this fixed my problem or so I thought... when I've hit the sprite above me or bellow and kept pressing up or down and at the same time left and right I was moving along the wall nicely... but if there was some obstacle next to me and I've reached it... my character "magicaly" transported itself right in top of it or bellow, since the code was always placing my character just bellow or above a sprite should it collide.
577

In this case I was pressing up Constantly and right as well, I was moving fine allong the wall , until I've reached the corner... there what happened was my Characters Y position was updated to the Y position of the sprite I've hit from RIGHT side.



unit Main;
interface
uses
Windows,Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, AdDraws, AdClasses, AdTypes, AdPerformanceCounter, AdDevIL,AdSprites,Math;
const
TileW = 32; // tile width
TileH = 32; // tile height
PlayerH = 60; // Player Height
PlayerW = 30; // Player Width
type
TPlatform = class(TImageSprite)
private
protected
procedure DoMove(TimeGap: Double); override;
procedure DoCollision(Sprite:TSprite; var Done:boolean); override;
public
constructor Create(AParent:TSprite);override;
end;
TBlurp = class(TImageSprite)
private
vx,vy : Single; //current velocity in pixels/second
protected
procedure DoMove(TimeGap: Double);override;
procedure DoCollision(Sprite:TSprite; var Done:boolean);override;
public
constructor Create(AParent:Tsprite);override;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
AdDraw:TAdDraw;
AdSpriteEngine:TSpriteEngine;
AdImageList:TAdImageList;
AdPerCounter:TAdPerformanceCounter;
AdPixelCollisionTester: TAdSpritePixelCollisionTester;
Blurp:TBlurp;
procedure Idle(Sender:TObject; var Done:boolean);
procedure LoadLevel;
end;
var
Form1: TForm1;
ActTime : double;
// camera positions
camera_x : double;
camera_y : double;

implementation
{$R *.dfm}
procedure TForm1.LoadLevel;
var
level:TStringList;
ax,ay:integer;
begin
level := TStringList.Create;
level.LoadFromFile(ExtractFilePath(Application.Exe Name)+'level2.txt');
for ay := 0 to level.Count-1 do
begin
for ax := 1 to length(level[ay]) do
begin
case level[ay][ax] of
'x':
begin
with TPlatform.Create(AdSpriteEngine) do
begin
Image := AdImageList.Find('WALL');
x := ax*TileW;
y := ay*TileH;
z := 0;
end;
end;
'X':
begin
with TPlatForm.Create(AdSpriteEngine) do
begin
Image := AdImageList.Find('WALL');
x := ax*TileW;
y := ay*TileH;
z := 0;
end;
end;
end;
end;
end;
level.Free;
end;

constructor TPlatform.Create(AParent: TSprite);
begin
inherited;
end;
constructor TBlurp.Create(AParent: TSprite);
begin
inherited;
end;

procedure TBlurp.DoMove(TimeGap: Double);
begin
inherited;
// setting the camera to follow our HERO
if (X > 320) and (X < (40*TileW)-320-TileW) then
begin
// - 64 , because when loading the level my entire background is starting from 64 instead of 0
// looks better if there is no black line at the edge of the level...
camera_x:=(X*-1)+320-TileW;
end;
if (Y > 240) and (Y < (29*TileH)-240) then
begin
// 29 tiles down * 64 tile.height -240 half of the screen, only moving when at center of the screen
// well not really the center...but close anyway :)
camera_y:=(Y*-1)+240;
end;

if vy < 0 then
// moving up
begin
// not obstructed so move normally
y := y + vy * TimeGap
end;
if vy > 0 then
// moving down
begin
y := y + vy * TimeGap
end;
if vx < 0 then
// moving left
begin
x := x + vx * TimeGap
end;
if vx > 0 then
// moving right
begin
x := x + vx * TimeGap
end;


Collision;
end;

procedure TPlatform.DoMove(TimeGap : Double);
begin
inherited;
Collision;
end;

procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);
begin
if Sprite is TPlatform then
begin
//up
if vy < 0 then
begin
Y:=TPlatForm(Sprite).Y+TPlatForm(Sprite).Height;
end;
// down
if vy > 0 then
begin
Y:=TPlatForm(Sprite).Y-Height;
end;
// left
if vx < 0 then
begin
X:=TPlatForm(Sprite).X+TPlatForm(Sprite).Width;
end;
// right
if vx > 0 then
begin
X:=TPlatForm(Sprite).X-Width;
end;
end;
end;
procedure TPlatform.DoCollision(Sprite:TSprite; var Done:boolean);
begin
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
AdPerCounter := TAdPerformanceCounter.Create;
AdDraw := TAdDraw.Create(self);
AdDraw.DllName := 'AndorraDX93D.dll';

if AdDraw.Initialize then
begin
Application.OnIdle := Idle;
AdImageList := TAdImageList.Create(AdDraw);
AdImageList.LoadFromFile('BackGround.ail');

//create the SpriteEngine
AdSpriteEngine := TSpriteEngine.Create(nil);
AdSpriteEngine.Surface := AdDraw;
//Create the collision tester
AdPixelCollisionTester := TAdSpritePixelCollisionTester.Create(AdDraw);
LoadLevel;
//create TImageSprite
Randomize;
Blurp := TBlurp.Create(AdSpriteEngine);
with Blurp.Create(AdSpriteEngine) do
begin
Image := AdImageList.Find('TallHero2');
X := 144;
Y := 144;
Z := 0;
CollisionTester := AdPixelCollisionTester;
end;

// The Entire Engine is moved left by -64 , this is because of the Level Loader ...
Camera_X:=-TileW;
Camera_Y:=0;
AdSpriteEngine.X:=camera_x;
AdSpriteEngine.Y:=camera_y;

// ************* hater *********************************
end
else
begin
ShowMessage('Error while initializing Andorra 2D. Try to use another display'+
'mode or use another video adapter.');
halt; //<-- Completely shuts down the application
end;
end;
procedure TForm1.Idle(Sender: TObject; var Done: boolean);
begin
if AdDraw.CanDraw then // Only continue, if drawing is possible
begin
AdPerCounter.Calculate;
AdPerCounter.MaximumFrameRate:=60;

AdDraw.ClearSurface(clBlack); // Fill the surface with black color
AdDraw.BeginScene;
// Here you need to perform all drawing operations later
ActTime := ActTime + AdPerCounter.TimeGap;
if ActTime > 10 then
begin
Blurp.vx:=0;
Blurp.vy:=0;
if GetKeyState(VK_LEFT) < 0 then
begin
Blurp.vx:=-123;
end;
if GetKeyState(VK_RIGHT) < 0 then
begin
Blurp.vx:=+123;
end;
if GetKeyState(VK_UP) < 0 then
begin
Blurp.vy:=-123
end;
if GetKeyState(VK_DOWN) < 0 then
begin
Blurp.vy:=123
end;

AdSpriteEngine.X:=camera_x;
AdSpriteEngine.Y:=camera_y;
ActTime := 0;
end;

AdSpriteEngine.Draw;
AdSpriteEngine.Move(AdPerCounter.TimeGap/1000);
AdSpriteEngine.Dead;
// debug


AdDraw.EndScene;
AdDraw.Flip; // Draws you result on the screen. Otherwise, you would not see anything
end;
Done := false; // Important, otherwise the function will not be called in every loop
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
AdSpriteEngine.Free;
AdImageList.Free;
AdPerCounter.Free;
AdDraw.Free;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_ESCAPE then Close;
end;
end.



Attached the code so you can see it in work :
578


Greetings
Rob

ps.: in the next post I'll try and explain what my newer improved code does...and the parts I don't understand...

User137
29-08-2011, 10:14 AM
procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);

This procedure is acting wrong in theory. Because when you press Up and Right simultaneously you should get sprite to move both down and left edge. When you move along wall to right direction for example and hit wall, you will be in situation where player moved inside 3 different blocks. This simply will not work even with vx:=0 etc whereever.

One note from code also, the lagtime code will not make game smooth as it is.

ActTime := ActTime + AdPerCounter.TimeGap;
if ActTime > 10 then
begin
...
ActTime := 0;
end;
This should be:

ActTime := ActTime + AdPerCounter.TimeGap;
if ActTime > 10 then
begin
...
ActTime := ActTime - 10;
end;
Otherwise the frametime will not try to balance it to 10 milliseconds, but instead always with little variety above 10.

Also, this is still same topic as: http://www.pascalgamedevelopment.com/showthread.php?10549-Collission-detection-on-level-(-source-Travels-doc-some-other-examples)/page3
There was many suggestions for collision detection, why did you turn them down?

robert83
29-08-2011, 10:50 AM
[I'm just experimenting... tried 3 different methods for now, this one got my atention because it's a built-in thing]


So about the improved code (I've found this inside an example from Asphyre 2d , the SpriteEngineRPGMap one to be exact)

Every time I move my character I write the following values into it's property...

579



Left := Round(X);
Top := Round(Y);
Right := Round(X + Image.Width);
Bottom := Round(Y + Image.Height);


When my character collides with the PlatForm Sprite I do this :

1. Get same coordinates from Sprite I've just hit



procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);
var
TileLeft, TileRight,
TileTop, TileBottom: Integer;
begin
if Sprite is TPlatform then
begin
TileLeft := Trunc(TPlatform(Sprite).X);
TileTop := Trunc(TPlatform(Sprite).Y);
TileRight := Trunc(TPlatform(Sprite).X) + TPlatform(Sprite).Image.Width;
TileBottom := Trunc(TPlatform(Sprite).Y) + TPlatform(Sprite).Image.Height;
// up
if (GetKeyState(VK_UP) < 0) then
begin
if (Self.Top+3 > TileBottom) and
(Self.Right > TileLeft) and
(Self.Left < TileRight) then
begin
Y := TileBottom;
end;
end;


580

Now that I drew it down... checking the logic +1 or -1 depending on direction should also be enough... maybe I'll look into my rounding... maybe I don't need it...

And I think I also understand why it does it like this ... in this case if I'm touching a wall from left or right... but Not touching a wall from TOP... it won't place me right bellow an incorrect tile...

I'll try to clean the code a bit more...

Greetings
Rob

robert83
29-08-2011, 10:56 AM
unit Main;
interface
uses
Windows,Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, AdDraws, AdClasses, AdTypes, AdPerformanceCounter, AdDevIL,AdSprites,Math;
const
TileW = 32; // tile width
TileH = 32; // tile height
PlayerH = 60; // Player Height
PlayerW = 30; // Player Width
type
TPlatform = class(TImageSprite)
private
protected
procedure DoMove(TimeGap: Double); override;
procedure DoCollision(Sprite:TSprite; var Done:boolean); override;
public
constructor Create(AParent:TSprite);override;
end;
TBlurp = class(TImageSprite)
private
vx,vy : Single; //current velocity in pixels/second
FLeft, FTop, FRight, FBottom: double;
nextv,v : double;
jumping : boolean;
protected
procedure DoMove(TimeGap: Double);override;
procedure DoCollision(Sprite:TSprite; var Done:boolean);override;
public
constructor Create(AParent:Tsprite);override;
property Left: double read FLeft write FLeft;
property Top: double read FTop write FTop;
property Right: double read FRight write FRight;
property Bottom: double read FBottom write FBottom;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
AdDraw:TAdDraw;
AdSpriteEngine:TSpriteEngine;
AdImageList:TAdImageList;
AdPerCounter:TAdPerformanceCounter;
AdPixelCollisionTester: TAdSpritePixelCollisionTester;
Blurp:TBlurp;
procedure Idle(Sender:TObject; var Done:boolean);
procedure LoadLevel;
end;
var
Form1: TForm1;
ActTime : double;
// camera positions
camera_x : double;
camera_y : double;

implementation
{$R *.dfm}
procedure TForm1.LoadLevel;
var
level:TStringList;
ax,ay:integer;
begin
level := TStringList.Create;
level.LoadFromFile(ExtractFilePath(Application.Exe Name)+'level2.txt');
for ay := 0 to level.Count-1 do
begin
for ax := 1 to length(level[ay]) do
begin
case level[ay][ax] of
'x':
begin
with TPlatform.Create(AdSpriteEngine) do
begin
Image := AdImageList.Find('WALL');
x := ax*TileW;
y := ay*TileH;
z := 0;
CollisionTester := AdPixelCollisionTester;
end;
end;
'X':
begin
with TPlatForm.Create(AdSpriteEngine) do
begin
Image := AdImageList.Find('WALL');
x := ax*TileW;
y := ay*TileH;
z := 0;
CollisionTester := AdPixelCollisionTester;
end;
end;
end;
end;
end;
level.Free;
end;

constructor TPlatform.Create(AParent: TSprite);
begin
inherited;
end;
constructor TBlurp.Create(AParent: TSprite);
begin
inherited;
vx:=128;
vy:=128;
V := 10; // starting velocity, used for calculating gravity effect on our little hero
jumping:=true; // cause our character is not jumping right now
end;

procedure TBlurp.DoMove(TimeGap: Double);
begin
inherited;
Left := X;
Top := Y;
Right := X + Image.Width;
Bottom := Y + Image.Height;
// setting the camera to follow our HERO
if (X > 320) and (X < (40*TileW)-320-TileW) then
begin
// - 64 , because when loading the level my entire background is starting from 64 instead of 0
// looks better if there is no black line at the edge of the level...
camera_x:=(X*-1)+320-TileW;
end;
if (Y > 240) and (Y < (29*TileH)-240) then
begin
// 29 tiles down * 64 tile.height -240 half of the screen, only moving when at center of the screen
// well not really the center...but close anyway :)
camera_y:=(Y*-1)+240;
end;
// jump

{ if (jumping = true) then
begin
Y:=Y-5; // the kick in the but
end; }
// gravity done here
{ if jumping = true then
begin
if nextv < 15 then nextv:=nextv+(v*TimeGap);
Y:=Y+nextv;
end
else
begin
nextV:=0;
jumping:=true;
end; }

if GetKeyState(VK_LEFT) < 0 then
begin
x := x - vx * TimeGap;
end;
if GetKeyState(VK_RIGHT) < 0 then
begin
x := x + vx * TimeGap;
end;
if GetKeyState(VK_UP) < 0 then
begin
y := y - vy * TimeGap;
end;
if GetKeyState(VK_DOWN) < 0 then
begin
y := y + vy * TimeGap;
end;

Collision;
end;

procedure TPlatform.DoMove(TimeGap : Double);
begin
inherited;
end;

procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);
var
TileLeft, TileRight,
TileTop, TileBottom: double;
begin
if Sprite is TPlatform then
begin
TileLeft := TPlatform(Sprite).X;
TileTop := TPlatform(Sprite).Y;
TileRight := TPlatform(Sprite).X + TPlatform(Sprite).Image.Width;
TileBottom := TPlatform(Sprite).Y + TPlatform(Sprite).Image.Height;

// up
if (GetKeyState(VK_UP) < 0) then
begin
if (Self.Top+1 > TileBottom) and
(Self.Right > TileLeft) and
(Self.Left < TileRight) then
begin
Y := TileBottom;
end;
end;
// down
if (GetKeyState(VK_DOWN) < 0) then
begin
if (Self.Bottom -1 < TileTop ) and
(Self.Right > TileLeft) and
(Self.Left < TileRight) then
begin
Y := TileTop-PlayerH;
end;
end;
// left
if GetKeyState(VK_LEFT) < 0 then
begin
if (Self.Left+1 > TileRight) and
(Self.Top< TileBottom) and
(Self.Bottom>TileTop) then
begin
X := TileRight;
end;
end;
// right
if GetKeyState(VK_RIGHT) < 0 then
begin
if (Self.Right-1 < TileLeft )and
(Self.Top< TileBottom) and
(Self.Bottom>TileTop) then
begin
X := TileLeft -PlayerW;
end;
end;
end;
end;
procedure TPlatform.DoCollision(Sprite:TSprite; var Done:boolean);
begin
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
AdPerCounter := TAdPerformanceCounter.Create;
AdDraw := TAdDraw.Create(self);
AdDraw.DllName := 'AndorraDX93D.dll';

if AdDraw.Initialize then
begin
Application.OnIdle := Idle;
AdImageList := TAdImageList.Create(AdDraw);
AdImageList.LoadFromFile('BackGround.ail');

//create the SpriteEngine
AdSpriteEngine := TSpriteEngine.Create(nil);
AdSpriteEngine.Surface := AdDraw;
//Create the collision tester
AdPixelCollisionTester := TAdSpritePixelCollisionTester.Create(AdDraw);
LoadLevel;
//create TImageSprite
Randomize;
Blurp := TBlurp.Create(AdSpriteEngine);
with Blurp.Create(AdSpriteEngine) do
begin
Image := AdImageList.Find('TallHero2');
X := 144;
Y := 144;
Z := 0;
CollisionTester := AdPixelCollisionTester;
end;

// The Entire Engine is moved left by -64 , this is because of the Level Loader ...
Camera_X:=-TileW;
Camera_Y:=0;
AdSpriteEngine.X:=camera_x;
AdSpriteEngine.Y:=camera_y;

// ************* hater *********************************
end
else
begin
ShowMessage('Error while initializing Andorra 2D. Try to use another display'+
'mode or use another video adapter.');
halt; //<-- Completely shuts down the application
end;
end;
procedure TForm1.Idle(Sender: TObject; var Done: boolean);
begin
if AdDraw.CanDraw then // Only continue, if drawing is possible
begin
AdPerCounter.Calculate;
AdPerCounter.MaximumFrameRate:=60;

AdDraw.ClearSurface(clBlack); // Fill the surface with black color
AdDraw.BeginScene;
// Here you need to perform all drawing operations later
ActTime := ActTime + AdPerCounter.TimeGap;
if ActTime > 10 then
begin
AdSpriteEngine.X:=camera_x;
AdSpriteEngine.Y:=camera_y;
ActTime := ActTime-10;
end;

AdSpriteEngine.Draw;
AdSpriteEngine.Move(AdPerCounter.TimeGap/1000);
AdSpriteEngine.Dead;
// debug

AdDraw.EndScene;
AdDraw.Flip; // Draws you result on the screen. Otherwise, you would not see anything
end;
Done := false; // Important, otherwise the function will not be called in every loop
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
AdSpriteEngine.Free;
AdImageList.Free;
AdPerCounter.Free;
AdDraw.Free;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_ESCAPE then Close;
end;

end.


Works like a charm, round trunc was my problem, and now I understand why I need +1 or -1 for x or y. Only needed to draw it down.

Greetings
Robert

User137
29-08-2011, 11:14 AM
Your main problem is that you are not identifying which side you are hitting, and code you posted above has values after VK_UP all illogical.

Maybe something like this. I'm turning the logics upside down, by assuming that DoCollision is called only when and with sprite that it collides with. So all i do is check which side the block is in relation to player and move (untested):

procedure TBlurp.DoCollision(Sprite:TSprite; var Done:boolean);
var
TileLeft, TileRight, TileTop, TileBottom: Integer;
begin
if Sprite is TPlatform then begin
TileLeft := Trunc(TPlatform(Sprite).X);
TileTop := Trunc(TPlatform(Sprite).Y);
TileRight := TileLeft + TPlatform(Sprite).Image.Width;
TileBottom := TileTop + TPlatform(Sprite).Image.Height;

if TileLeft div TileW = trunc(X+Width/2) div TileW then
if TileTop div TileH < trunc(Y+Height/2) div TileH then begin
Y := TileBottom;
end else if TileTop div TileH > trunc(Y+Height/2) div TileH then begin
Y := TileTop - Height;
end;
if TileTop div TileH = trunc(Y+Height/2) div TileH then
if TileLeft div TileW < trunc(X+Width/2) div TileW then begin
X := TileRight;
end else if TileLeft div TileW > trunc(X+Width/2) div TileW then begin
X := TileLeft - Width;
end;
end;
end;
Edited. Noticed it might have acted wrong without extra if's. And another major error hopefully fixed. Now i notice this could use more precalculation to vars... The code blew up in hands.

Also wanted to mention this earlier. By math logics this can be simplified:

camera_x:=(X*-1)+320-TileW;

camera_x:=320-X-TileW;


Last edit: Realising that this code of mine will not cause collision when moving towards corners, it's almost as good as nothing. So i'll stick to what i said earlier, you need something really different kind of collision method for this problem.

The versions you showed earlier with gravity seemed to work almost fine. Just a few fixable bugs but they had overall more potential to work. One thing for example, when the jumping state is on and player hits block above, you must set the vertical velocity 0 and make player start dropping. This caused player to stick to the roof for small time.

Last edit2 (lol i'm just bored): I think it might work with this kind of doCollision too if instead of just checking player position, use the player movement direction (vx, vy) as help to determine what wall to collide with. What mainly boggles my mind is situation where there is 3 roof blocks above, and player holds keys Left and Up. First collision test will be to the top left block which will same time cause player to stop vertical and horizontal movement in corner. That is why in above code i ignore corner blocks, but that causes another issue when there is only corner and nothing above, it wouldn't collide.