PDA

View Full Version : Car Game Troubles



Wizard
04-05-2007, 09:40 AM
Hi everyone.

Ive programmed a 2D game in Delphi 6 and UnDelphiX. The user would drive a car on a road and will have to kill monsters etc. When the up key is pressed the road and monsters moves down at the same speed and it then appears as if the car is moving up.

I have 2 problems:

Ive tried to get the movement independent of fps but all the ways Ive tried results in the movement being jerky. Is there another way?


dxSpriteEngine1.Move(LagCount);
DXTimer1.Interval := 1000 div 75; This gives 64 fps
Or


dxtimer1.Interval:=1000 div 70;
NewTime := TimeGetTime;
UpdateSpeed := (NewTime - OldTime) * (OneMSec ) //or * (LagCount/2);
OldTime := NewTime;
Y := Y + (0.5 * updatespeed)

Ive posted about my 2nd problem but got no response, please help:


constructor TRoad.create(parent:TSprite;imgs:TDXImageList;imgI ndex:integer);
begin
inherited create(parent);
self.Image:=imgs.Items[ImgIndex];
self.Width:=self.image.Width;
self.Height:=self.Image.Height;
self.PixelCheck:=false;
self.x:=0;
self.Y:= form1.DXDraw1.Surface.Height - height; //form1.dxdraw1.height - height; // self.engine.surface.height ???????? None of these settings works on other PC???????
self.Z:=0;
end;
The problem is the self.y line of code. On one pc the setting self.y := -11600 (length of road image) works but not on another pc. Sometimes the road only appears halfway and it should appear at the start.

Hope this clarifies my problems and your feedback will be appreciated. BTW Gr8 site!!!!

Angelo
04-05-2007, 10:07 AM
About your problems:

#1.
What is the exact problem there? It could be me, but I don't exactly get the point. Are you trying to create a movement by fps?

#2.
I guess the 2 pc's have different resolutions?
What is the exact value of "form1.DXDraw1.Surface.Height" on both pc's ?
It's clear that 1 number is varieing on the pc's.

Wizard
04-05-2007, 10:18 AM
No, movement must be independent of fps. If it is by fps the game runs smoothley but the fps is too high to accomadate the same speed on different pc's. So I'm trying to get movement independent of fps but the ways I've tried only makes movement jerky :-(

Nope they have the same resolution, I checked that. The road image is exactly 11600 in length and all that needs to happen is for the form to load the exact same image at the correct starting y point and it's not happening :-(

Angelo
04-05-2007, 10:33 AM
Ok, now I fully understand you.

How to make movement independent of FPS...
You could try it with a timer, but that's not a real solution I think.
Well, in my projects I have it outside the "game-procedure", and I mean by saying this, that I separate the key-handling and ingame procedures.

For your second problem, I think the defined "height" is varieing.
Or you haven't defined it before (I had that once, I forgot to set the value of it, and somehow it has different values then...).
But one thing is certain, 1 variable is varieing...

Wizard
04-05-2007, 10:53 AM
Ok, regarding the 2nd problem. I've done some tests and it would seem that if I have a value of 12200 set as the pattern height of the road image in the object inspector and the following in the procedure: self.engine.surface.height - height, that it works. Don't know, will have to test it on my LapTop tonight.

But I'm still stumped on the 1st question...

Angelo
04-05-2007, 11:09 AM
#1:

How does your system works?
I mean, the key handling.
In one of my projects the keys determined whether something was executed or not.
In another example you could use a kind of timer.
The procedure inside the timer will check if a key is down or not, but I think you currently have that system, am I right?
Because if you hit the key while the procedure is checking it will not check now but in the next interval, which means your movement is meshed up.

I can suggest reading this (directly copied from GLScene, but very usefull since this works 100%)
//
// This unit is part of the GLScene Project, http://glscene.org
//
{: Keyboard<p>

Provides on demand state of any key on the keyboard as well as a set of
utility functions for working with virtual keycodes.<p>

Note that windows maps the mouse buttons to virtual key codes too, and you
can use the functions/classes in this unit to check mouse buttons too.<br>
See "Virtual-Key Codes" in the Win32 programmers r?ferences for a list of
key code constants (VK_* constants are declared in the "Windows" unit).<p>

Historique : <font><ul>
17/11/03 - Egg - Fixed IsKeyDown (VK) (A. P. Mohrenweiser)
09/10/00 - Egg - Fixed VirtualKeyCodeToKeyName
03/08/00 - Egg - Creation, partly based Silicon Commander code
[/list]</font>
}
unit Keyboard;

interface

{$i GLScene.inc}
{$IFDEF LINUX}{$Message Error 'Unit not supported'}{$ENDIF LINUX}

uses
Windows;

type

TVirtualKeyCode = Integer;

const
// pseudo wheel keys (we squat F23/F24), see KeyboardNotifyWheelMoved
VK_MOUSEWHEELUP = VK_F23;
VK_MOUSEWHEELDOWN = VK_F24;

{: Check if the key corresponding to the given Char is down.<p>
The character is mapped to the main keyboard only, and not to the
numeric keypad.<br>
The Shift/Ctrl/Alt state combinations that may be required to type the
character are ignored (ie. 'a' is equivalent to 'A', and on my french
keyboard, '5' = '(' = '[' since they all share the same physical key). }
function IsKeyDown(c : Char) : Boolean; overload;
{: Check if the given virtual key is down.<p>
This function is just a wrapper for GetAsyncKeyState. }
function IsKeyDown(vk : TVirtualKeyCode) : Boolean; overload;
{: Returns the first pressed key whose virtual key code is >= to minVkCode.<p>
If no key is pressed, the return value is -1, this function does NOT
wait for user input.<br>
If you don't care about multiple key presses, just don't use the parameter. }
function KeyPressed(minVkCode : TVirtualKeyCode = 0) : TVirtualKeyCode;

{: Converts a virtual key code to its name.<p>
The name is expressed using the locale windows options. }
function VirtualKeyCodeToKeyName(vk : TVirtualKeyCode) : String;
{: Converts a key name to its virtual key code.<p>
The comparison is case-sensitive, if no match is found, returns -1.<p>
The name is expressed using the locale windows options, except for mouse
buttons which are translated to 'LBUTTON', 'MBUTTON' and 'RBUTTON'. }
function KeyNameToVirtualKeyCode(const keyName : String) : TVirtualKeyCode;
{: Returns the virtual keycode corresponding to the given char.<p>
The returned code is untranslated, f.i. 'a' and 'A' will give the same
result. A return value of -1 means that the characted cannot be entered
using the keyboard. }
function CharToVirtualKeyCode(c : Char) : TVirtualKeyCode;

{: Use this procedure to notify a wheel movement and have it resurfaced as key stroke.<p>
Honoured by IsKeyDown and KeyPressed }
procedure KeyboardNotifyWheelMoved(wheelDelta : Integer);

var
vLastWheelDelta : Integer;

// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
implementation
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------

uses SysUtils;

const
cLBUTTON = 'LBUTTON';
cMBUTTON = 'MBUTTON';
cRBUTTON = 'RBUTTON';
cUP = 'UP';
cDOWN = 'DOWN';
cRIGHT = 'RIGHT';
cLEFT = 'LEFT';
cPAGEUP = 'PAGE UP';
cPAGEDOWN = 'PAGE DOWN';
cHOME = 'HOME';
cEND = 'END';
cMOUSEWHEELUP = 'MWHEEL UP';
cMOUSEWHEELDOWN = 'MWHEEL DOWN';

// IsKeyDown
//
function IsKeyDown(c : Char) : Boolean;
var
vk : Integer;
begin
// '$FF' filters out translators like Shift, Ctrl, Alt
vk:=VkKeyScan(c) and $FF;
if vk<>$FF then
Result:=(GetAsyncKeyState(vk)<0>0);
if Result then vLastWheelDelta:=0;
end;
VK_MOUSEWHEELDOWN : begin
Result:=(vLastWheelDelta<0);
if Result then vLastWheelDelta:=0;
end;
else
Result:=(GetAsyncKeyState(vk)<0>=0);
Result:=-1;
if GetKeyboardState(buf) then begin
for i:=minVkCode to High(buf) do begin
if (buf[i] and $80)<>0 then begin
Result:=i;
Exit;
end;
end;
end;
if vLastWheelDelta<0>0 then
Result:=VK_MOUSEWHEELUP
else Result:=VK_MOUSEWHEELDOWN;
vLastWheelDelta:=0;
end;
end;

// VirtualKeyCodeToKeyName
//
function VirtualKeyCodeToKeyName(vk : TVirtualKeyCode) : String;
var
nSize : Integer;
begin
// Win32 API can't translate mouse button virtual keys to string
case vk of
VK_LBUTTON : Result:=cLBUTTON;
VK_MBUTTON : Result:=cMBUTTON;
VK_RBUTTON : Result:=cRBUTTON;
VK_UP : Result:=cUP;
VK_DOWN : Result:=cDOWN;
VK_LEFT : Result:=cLEFT;
VK_RIGHT : Result:=cRIGHT;
VK_PRIOR : Result:=cPAGEUP;
VK_NEXT : Result:=cPAGEDOWN;
VK_HOME : Result:=cHOME;
VK_END : Result:=cEND;
VK_MOUSEWHEELUP : Result:=cMOUSEWHEELUP;
VK_MOUSEWHEELDOWN : Result:=cMOUSEWHEELDOWN;
else
nSize:=32; // should be enough
SetLength(Result, nSize);
vk:=MapVirtualKey(vk, 0);
nSize:=GetKeyNameText((vk and $FF) shl 16, PChar(Result), nSize);
SetLength(Result, nSize);
end;
end;

// KeyNameToVirtualKeyCode
//
function KeyNameToVirtualKeyCode(const keyName : String) : TVirtualKeyCode;
var
i : Integer;
begin
if keyName=cLBUTTON then
Result:=VK_LBUTTON
else if keyName=cMBUTTON then
Result:=VK_MBUTTON
else if keyName=cRBUTTON then
Result:=VK_RBUTTON
else if keyName=cMOUSEWHEELUP then
Result:=VK_MOUSEWHEELUP
else if keyName=cMOUSEWHEELDOWN then
Result:=VK_MOUSEWHEELDOWN
else begin
// ok, I admit this is plain ugly. 8)
Result:=-1;
for i:=0 to 255 do begin
if CompareText(VirtualKeyCodeToKeyName(i), keyName)=0 then begin
Result:=i;
Break;
end;
end;
end;
end;

// CharToVirtualKeyCode
//
function CharToVirtualKeyCode(c : Char) : TVirtualKeyCode;
begin
Result:=VkKeyScan(c) and $FF;
if Result=$FF then Result:=-1;
end;

// KeyboardNotifyWheelMoved
//
procedure KeyboardNotifyWheelMoved(wheelDelta : Integer);
begin
vLastWheelDelta:=wheelDelta;
end;

end.


In your procedure it could look like this:

if IsKeyDown('w') then movementw;
if ....

Wizard
04-05-2007, 11:28 AM
Good suggestion Angelo.
The key handling is done in the onMove procedure and the doMove is called in the statemachine and the statemachine is called in the timer procedure:


if &#40;isUp in form1.DXInput1.States&#41; then
Y &#58;= y + &#40;1 * updatespeed&#41; else
if not&#40;isUp in form1.DXInput1.States&#41; then
Y &#58;= Y + &#40;0.5 * updatespeed&#41;;

I disabled the procedure to check if the key is pressed or not and it is still jerky :-(

i.e. Y := y + (1 * updatespeed);

Thanks for your help so far :-)

Angelo
04-05-2007, 11:35 AM
No problem.

What if you try it like this (it's just a sketch):

Procedure Timer;
begin
If OnKeyPress then DoMove(KeyPressed);
// In this case OnKeyPress is a function that only returns whether a key is pushed or not (true and false)
// In this case KeyPressed is a function that returns the number/letter which is pressed
end;

Procedure DoMove(Key: string);
begin
If Key = 'w' then begin
end;
end;

Wizard
04-05-2007, 12:25 PM
I don't think we're moving in the right direction. Everything was working fine untill I introduced the following in the timer event:

dxtimer1.Interval:=1000 div 70;
NewTime := TimeGetTime;
UpdateSpeed := (NewTime - OldTime) * OneMSec
OldTime := NewTime;

The objects are moved as such:

TMan(MyMen[loop]).DoMove(1); in the statemachine.
and y := y + (1 * updatespeed); in the doMove Procedure.

I've run the game without any keyboard input and it's jerky so I don't think it has to do with input delays from the keyboard.

I'll keep testing with different values in: UpdateSpeed := (NewTime - OldTime) * OneMSec;

Angelo
04-05-2007, 12:50 PM
Ah I get it.
You know why it's jerky?

Because the timer interval is too much.
Your game is updated every 1000/70 milliseconds.
That's really slow.
My timer interval is 0,2 ms... which is much quicker.
Try for example 1000/200 milliseconds and you'll see it will move how you want it to.
BUT: Since you now enlarged the scale, the object will move quicker.
So movingspeed should be divided by 200/70...

Wizard
04-05-2007, 01:12 PM
Thanks alot for your help... brilliant :-)

It works like a charm :-)

Angelo
04-05-2007, 01:36 PM
No problem.

Caution:

Be aware that every XX milliseconds your game will be updated.
If the time that your procedure needs to do all your actions is larger then the timer interval, your procedure will not get executed fully.

In example:

The interval is 1ms.
The first 20 lines inside your procedure takes 1ms, the total (40 lines) will take 2ms.
Only the first 20 lines will get executed.

So be aware, that can be a horrible problem!

seiferalmasy
04-05-2007, 07:59 PM
also make sure you have deploed timebeginperiod(1) at the start of your code, otherwise it will not each anywhere near the desired interval on most OS's, esp not XP

Wizard
07-05-2007, 06:42 AM
Wow, it's testing, testing and even more testing :-) I've learned so much from you guys.

Keep up the good work 8)