PDA

View Full Version : Dynamic in-game object creation and manipulation



Tomi
12-12-2015, 04:30 PM
Hello everybody!
I'm still new here and the Lazarus is a bit new for me. I would like make a small game with Lazarus, in which the player can put down balls in the window on the mouse coordinates - but my code doesn't work. Here is:


unit Unit1;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, mouse;

type

{ TMainWindow }

TMainWindow = class(TForm)
procedure FormClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;

type TBall=class(TObject)
private
public
constructor makeit(xplace,yplace: integer; itspic: Tbitmap);
end;

var
MainWindow: TMainWindow;
ballpic: TBitmap;
aball: TBall;

implementation

{$R *.lfm}
constructor TBall.makeit(xplace,yplace: integer; itspic: Tbitmap);
begin
xplace:=getmousex;
yplace:=getmousey;
itspic:=ballpic;
end;
//end;

{ TMainWindow }

procedure TMainWindow.FormClick(Sender: TObject);
begin
aball:=TBall.makeit(getmousex,getmousey,ballpic);
MainWindow.Canvas.Draw(aball.xplace,aball.yplace,a ball.itspic);
end;

procedure TMainWindow.FormCreate(Sender: TObject);
begin
ballpic:=TBitmap.Create;
ballpic.LoadFromFile('ballbmp.bmp');
end;

end.

And the error messages:
Projekt fordítása, Cél: kepfutidoben.exe: Kilépési kód: 1, Hibák: 3, Tippek: 3
unit1.pas(26,24) Hint: Value parameter "xplace" is assigned but never used
unit1.pas(26,31) Hint: Value parameter "yplace" is assigned but never used
unit1.pas(26,48) Hint: Value parameter "itspic" is assigned but never used
unit1.pas(50,36) Error: identifier idents no member "xplace"
unit1.pas(50,49) Error: identifier idents no member "yplace"
unit1.pas(50,62) Error: identifier idents no member "itspic"

Somebody can help me to solving this problem? I would like placing ball objects at runtime.

Super Vegeta
14-12-2015, 07:46 AM
Your Ball does not have any fields inside. It does not store any information. What you do is make a constructor that takes three arguments, overwrites their value and discards them. What you want is to have your Ball store information about its position and picture. This is done via fields inside the class. Check out the FreePascal language guide: http://freepascal.org/docs-html/current/ref/refsu24.html
Also, you should use the
your pascal code here tags when pasting in code. Preferably make a new topic if you have more questions. (Or maybe a mod can move this post into a separate topic?)

Tomi
15-12-2015, 01:40 PM
I didn't know these things, Super Vegeta, and I didn't want to burden the forum with a new thread; sorry... But I have corrected my code according to your advice, and now it works properly; thank you for your help!
And now let's go programming! :)

Tomi
17-12-2015, 12:42 PM
if you want I can move your posts in a new thread inside a "My project" section of the forum which is not intended for specific topics.

Right!
(Although my first Lazarus game project will be (hopefully) a small space shooter, not that ball game, in which I only asked for information about creating objects in runtime.)

SilverWarior
18-12-2015, 04:55 PM
Right!
(Although my first Lazarus game project will be (hopefully) a small space shooter, not that ball game, in which I only asked for information about creating objects in runtime.)

You say you are interested in making a small space shooter. Do you need any guidance in specific areas like:
- defining classes for ingame units (entities)
- defining data structure for storing game level information
- performing collision detection between different ingame entities
If so I can provide you a few ideas for implementing these.

Tomi
19-12-2015, 09:44 AM
Thank you for summary, SilverWarrior.
And what is the best way of cleaning the object instances from the memory and it's picture from the screen?
Now, I'm using arrays to store the instances and the FreeAndNil(); to delete the instances from the array, and after I use Canvas.Clear; to wipe the screen.
...
for i:=1 to numofenemies-1 do FreeAndNil(enemies[i]);
...
After all, it's works, but is this the only and best solution? Because in this case I have to give the maximum amount of enemies, shoots, etc. at the declaration of these arrays ( var enemies: array [1..100] of Tenemy; ), and I would like keep open the possibility to add new instances to the arrays, then redimensioning these arrays - if it's possible.

Super Vegeta
19-12-2015, 11:00 AM
You can use dynamic arrays. They are declared pretty much the same as fixed-size arrays - just don't specify the size. e.g.

Var Enemies: Array of TEnemy;
Then you can use the Length() function to check the current array size and SetLength() to change the size (on program start, the size is 0). Dynamic arrays are always indexed from 0 to Length(ArrayVar)-1.
One thing that should be noted, though, is that resizing the array is quite a complicated task - the program has to find a new, contiguous memory region of required size, copy-paste all the values from the old place in memory to the new one, and then free the old memory region. As such, it's best to only change the array size when necessary (so don't call SetLength() every time you add/remove an enemy), and to do it in some larger steps - so instead of increasing the array by 1 slot when you need to add a new enemy, you increase it by, for example, 16 slots, or maybe do something like NewLength := OldLength * 1.5. This way, the next time you need to add an enemy, you don't have to resize the array again, but can use one of the empty slots you got left from the previous resize.

Tomi
19-12-2015, 02:19 PM
Wow, thanks, Super Vegeta! I didn't know this technique about dynamic arrays. But using of it are not recommended because the resizing is complicated?

Tomi
19-12-2015, 04:51 PM
Is the right place for drawing objects the FormPaint event? I have put the following piece of code there, but the bullets aren't visible.:( Only the background and the player's spaceship are visible. What is wrong?


procedure TMain.FormPaint(Sender: TObject);
var i: integer;
begin
//Canvas.clear;
Canvas.StretchDraw(Rect(0, 0, ClientWidth, ClientHeight), backg);
if numofbullets>1 then
begin
for i:=1 to numofbullets-1 do
begin
if bullets[i]<>nil then
Canvas.Draw(bullets[i].xplace,bullets[i].yplace,bullets[i].picture);
end;
end;
end;

phibermon
19-12-2015, 06:49 PM
Firstly dynamic arrays start at 0 so you should do :



for I := 0 to numofbullets-1 do


Secondly your answer my lie in your checks - you've got an array of bullets and you're checking to see if a member is nil before you render - perhaps they are nil? please post your code for how you setup the array of bullets so we can check to see if that's a factor.

Thirdly you have a 'picture' property for each bullet - if each bullet has a different image then this is fine but I'm guessing you only have a single bullet texture - it's best to load it once and draw it over and over for each bullet - only storing the unique properties per bullet IE :



Canvas.Draw(bullets[i].xplace,bullets[i].yplace, FGlobalBulletPicture);


and lastly because dynamic arrays start at 0 you don't need :



if numofbullets>1 then


(but that should read numofbullets > 0 or numofbullets >= 1) because :



for I := 0 to numofbullets-1 do


when numofbullets = 0 equates to :



for 0 to -1 do


Which will not enter the loop (use 'downto' instead of 'to' if you want to use descending for loops)

SilverWarior
19-12-2015, 09:37 PM
or maybe do something like NewLength := OldLength * 1.5.

I strongly recommend against using of this approach. Why? Because it leads to exponential growth of the array and could therefore become a great memory hog.
Imagine next scenario. You have an arrays with 100k items. Each item is 10KB in size. Which means that your array would be roughly 1GB in size. So following your approach adding 1 additional item would increase its size from roughly 1GB to 1.5GB in size. Or in other hand you would be wasting about 500MB of memory just because you had to increase the size of the array to hold additional item.
So I would rather suggest increasing the array in fixed increments instead.


Wow, thanks, Super Vegeta! I didn't know this technique about dynamic arrays. But using of it are not recommended because the resizing is complicated?

While dynamic arrays do allow you to change their size at any time you don't have to do this. If you want you can set their size at the start based on your expected requirement and don't update their size later on.
One instance of this would be using dynamic arrays to store game map. You know the map dimensions upon level loading so you set the size of dynamic array to be big enough to hold that data and you don't resize them until you decide to load different level.
So unless you are concerned about your game to be using as little memory as possible the whole time you don't need to be to much concerned about resizing of dynamic arrays you would probably do it rarely.

phibermon
19-12-2015, 11:56 PM
It's just an example - exponential array growth is a *good* solution - if you're using dynamic arrays in such a way then it means you have no idea on the maximum number of elements you need - ergo an exponential growth is a good default mode of operation.

Take a parser that loaded lines of text - it might be asked to load 10 lines and then it might be asked to load 10 million lines - a fixed increment would have to be huge to make this close to efficient in terms of performance and it would then become inefficient in terms of storage for smaller numbers of lines.

Exponential growth is the best trade-off you can make between performance and storage in a number of scenarios - the detriment remains roughly equal across both domains.

In fact a better solution is to have an affordable but reasonably large initial size (say 4096 elements) and then grow exponentially from that - you lose a little bit of storage space for lots of small collections but you minimize the number of resizes as they're all bunched at the lower end with exponential growth.

if you *know* you're storing 1GB of data in memory then you *never* want to be resizing an array of that size anyway - that's just silly - you'd pick a more efficient access pattern in the first place - one that doesn't rely on continuous blocks across the entire set.

Memory is plentiful, you can afford to waste some for the sake of performance.

SilverWarior
20-12-2015, 01:46 AM
Take a parser that loaded lines of text - it might be asked to load 10 lines and then it might be asked to load 10 million lines - a fixed increment would have to be huge to make this close to efficient in terms of performance and it would then become inefficient in terms of storage for smaller numbers of lines.

You could combine both approaches. In fact I have seen this somewhere in Delphi code. I'm not sure whether this was in a TList or one of it descendants or perhaps in TMemoryStream. But I do know I have seen approach where initially size is increased exponentially to some point. From then on it is increased in fixed increments.


if you *know* you're storing 1GB of data in memory then you *never* want to be resizing an array of that size anyway - that's just silly - you'd pick a more efficient access pattern in the first place - one that doesn't rely on continuous blocks across the entire set.

That is true. I have been exaggerating a bit with my example to make the point more obvious.

In my projects I'm generally trying to avoid any array that would exceed 100 MB in size. If needed I split such arrays into multiple parts and then envelop them with class which then still allows me accessing to their data as it would still be stored in a single array. This way I lose a bit of performance but I avoid most problems that may arise due to memory fragmentation (not being able to find big enough continuous block of memory).

Also I'm starting to use classes and list more and more. One reason is to avoid having large arrays. Second reason is much faster data soring since you are moving less data in the array.
I even have a special list that provide multiple indexes and therefore allows me to have my data sorted by different criteria at all times.
Of course this list is also designed in a way that when I'm adding new data or removing data from it all indexes are updated right away (sorted inserts).

Tomi
20-12-2015, 09:03 AM
please post your code for how you setup the array of bullets
I'm using fixed length arrays:

bullets: array [0..100] of TBullet;
Then in a Timer event I create, move and destroy the bullets:


if (plshoot=true) and (plcanshoot=true) then
begin
abullet:=TBullet.create(Player.Left,Player.Top,bul letpic);
bullets[numofbullets]:=abullet;
inc(numofbullets);
end;


if numofbullets>=1 then
begin
for i:=0 to numofbullets-1 do
begin
if bullets[i]<>nil then
begin
bullets[i].yplace:=bullets[i].yplace-4;
if bullets[i].yplace<10 then
begin
freeandnil(bullets[i]);
if i=numofbullets-1 then
dec(numofbullets)
else
begin
for j:=i+1 to numofbullets-1 do
bullets[j-1]:=bullets[j];
dec(numofbullets);
end;
end;
end;
end;
end;

(I have already corrected the first variable of the loops from 1 to 0, as you wrote.)
When I press the fire button, the bullets are only flash at the top of the player. :-/

SilverWarior
20-12-2015, 12:26 PM
Is your numofbullets global variable or is it declared inside Timers OnEvent method.
If it is declared in an OnTimer event method that means that it is cleared after ending of each Timer cycle and then recreated in another cycle with default value which is 0.

Tomi
20-12-2015, 12:42 PM
The numofbullets is a global variable; only the i and j variables are declared in the timer.

SilverWarior
20-12-2015, 04:49 PM
Then I'm afraid I can't help you out further without seeing more code. Best thing would be access to your whole game so I can try to debug it.

phibermon
20-12-2015, 05:16 PM
Yep - whatever is going wrong doesn't appear to be connected (at least entirely) with what you've posted so far - I will note however that :



freeandnil(bullets[i]);
if i=numofbullets-1 then
dec(numofbullets)
else
begin
for j:=i+1 to numofbullets-1 do
bullets[j-1]:=bullets[j];
dec(numofbullets);
end;


is convoluted and a mess - why free the bullet at all? just fill your array with created bullet objects and just have a new boolean property of TBullet called 'active'. Then you can simply do :




{go through the array and create a TBullet object with 'active' set to false for all objects as part of your game initialization}

if (plshoot=true) and (plcanshoot=true) then
begin
I := 0;
while Bullet[I].active do
I := I + 1;
if I > high(Bullet) then exit;
Bullet[I].active := true;
Bullet[I].Xplace := Player.Left;
Bullet[I].Yplace := Player.Top;
end;


for I := 0 to numofbullets-1 do
if bullets[I].active then
begin
bullet[I].YPlace := bullet[I].YPlace-4;
if bullet[I].YPlace < 10 then
bullet[I].Active := false;
end;



Then go through the array and only render 'active' bullets - yes you'll be skipping over 'inactive' bullets but that's still loads faster than constantly creating/destroying objects.

An optimization of this would to be to treat the array like a ring-buffer - then by its very nature the start index is the oldest active bullet and the end index is the newest bullet - only iterate over these - still you'll itterate over some inactive bullets but it'll be much faster than constantly keeping the array tightly packed - you could also use a linked list but really unless you've got thousands of bullets there's no need to care performance wise.

There are various reasons why you'd want to keep an array tightly packed - perhaps it needs to be kept sorted (but there are better structures such as linked lists) or perhaps you have a need to copy a continuous block of memory for some reason (uploading vertex data to a GPU) but you don't need either of these.

Also bare in mind that there's no reason you can't have two container structures - a flat array and a linked list - scan the flat array for an inactive bullet - set it to active and add a reference to a linked list. If the bullet is to be 'destroyed' set it to inactive and remove it from the linked list - this way you have a flat array for 'storage' and the linked list for optimal iteration over active elements (linked lists are slow to index but are very fast for deletions/insertions - especially in your case where you delete from anywhere in the list but only add to the end)

Even better have two linked lists - an active one and an inactive one - then you no longer have to scan for an active bullet or create one - you just remove one from the inactive list and add it to the active list and visa versa - a slow object creating, array shuffling operation becomes a case of setting a few pointers.

--

But : these are only suggestions to think about - it's overkill at this stage - as SilverWarrior suggests we'd need to see basically all of your code to tell you what's wrong.

Tomi
21-12-2015, 08:48 AM
Maybe I find what is wrong in my code: I have to put the drawings in the Timer event, not in Formpaint. Ehhh... I need more experience with Lazarus.

Tomi
31-12-2015, 01:52 PM
Hello and happy new year, everybody!


At last, my first game with Lazarus is done: a small space shooter, with some enemies, one boss and power-ups.
It's name is "Space Defender" and you can download from here:
http://www.programozzunk.ucoz.hu/lazjatekok/spacedef.zip
Unfortunately it has some bugs:
- the appearance of bullets of enemies sometimes happens in wrong place,
- the sprites of the spacehips are flashing,
- sometimes the game throw you out, when the player is rebirth at the boss and you press buttons.
In spite of all this, I hope that you will enjoy this game, but if you will find the solutions for these bugs, please tell me.

User137
08-01-2016, 08:57 AM
Does the order really matter for bullets and possibly glowing particles and the like? I find it redundant to, say if index 10 is removed from list of 100 bullets you would move all remaining 90 or so of them 1 backwards... It is simpler to move the last index in place of the removed one. 1 operation vs 90 operations. In order for this to work, you need to use a "downto" in the for loop, going from the last item to 0.


for b:=bullets-1 downto 0 do
with bullet[b] do begin
Move;
dec(AliveTime);
if AliveTime < 0 then begin
dec(bullets);
//bullet[b].Free; // Free it here if you must. Not needed if using records
bullet[b]:=bullet[bullets]; // Replace current bullet with last one.
end;
end;
New bullets will then always be added to the end.

Tomi
10-01-2016, 09:02 AM
Hello User137!


My main problem the appearance of enemy's bullets, which happening at wrong place. Its may has to do with moving and deleting of the bullets?
I do it as follows:



if numofbullets>0 then
begin
i:=0;
while i<numofbullets-1 do
begin
if bulletarray[i]<>nil then
begin
if (bulletarray[i].xplace>ClientWidth) or (bulletarray[i].xplace<1) or (bulletarray[i].yplace>ClientHeight) or (bulletarray[i].yplace<1) or (collwithplayer(bulletarray[i].xplace,bulletarray[i].yplace)=1) then
begin
freeandnil(bulletarray[i]);
if i=numofbullets-1 then
dec(numofbullets)
else
begin
for j:=i+1 to numofbullets-1 do
bulletarray[j-1]:=bulletarray[j];
dec(numofbullets);
dec(i);
end;
end else
begin
bulletarray[i].yplace:=bulletarray[i].yplace+4
MainWindow.Canvas.Draw(bulletarray[i].xplace,bulletarray[i].yplace,bulletarray[i].itsimage);
end;
end;
inc(i);
end;
end;


Maybe it is wrong?

User137
10-01-2016, 10:46 AM
You don't have a semicolon at the end of

bulletarray[i].yplace:=bulletarray[i].yplace+4
And that's all i can see is wrong with the code. If you fire just 1 bullet, it starts from wrong place? Then it sounds like the mistake is the code that fires it.

Tomi
10-01-2016, 10:52 AM
The player's bullets starts from right place, but the enemy's bullets sometimes comes from wrong places, as you can see in my program which can be download from 20th post.

User137
10-01-2016, 04:27 PM
It's over 800 lines of code and not in english so it's difficult to read.

Here is something odd:

function TFoablak.pontirany(x1,y1,x2,y2: integer): word;
var szam: integer;
begin
szam:=round(arctan2(y2-y1,x2-x1));
if szam<0 then
szam:=round(szam+2*pi);
pontirany:=round(360-(szam*180)/pi);
end;
You should set szam: single; instead of integer, and not round it directly from arctan2(). There is too much precision lost for angle (if you have radians as integer there are only about 6 different angles in the whole 360 range.).

Also

if numofbullets>0 then // < This line is not needed.
begin
i:=0;
while i<numofbullets-1 do
Lets see what happens if numofbullets = 0:

i:=0;
while 0<-1 do
0 is not < -1 so it never enters the while.

Tomi
13-01-2016, 04:14 PM
"It's over 800 lines of code and not in english so it's difficult to read."


I have written the program in Hungarian. (Maybe it's not an easy language, and you not saw it yet in full dress: with diacritics ;) )


The function "TFoablak.pontirany" is help to direct only the movement of the boss enemy and its bullets, which initial direction are towards the player.
The bullets of the other enemies are only goes downwards (line 533: if foellvan=0 then elllovedektomb[i].yhely:=elllovedektomb[i].yhely+4... ).
And in line 419 happening the creating of bullets of these enemies in the coordinates of actual enemy (xhely: xplace, yhely: yplace):
elllovedektomb[elllovedekdb]:=TJatekEleme.Letrehoz(ellensegtomb[i].xhely,ellensegtomb[i].yhely,elllovkepe);
But sometimes this coordinate totally different, and I don't know, why...

robert83
14-01-2016, 02:52 PM
I speak Hungarian too , but even I find it difficult to read code that is not in english.

Your english is pretty good, it would help everyone here if you would develop your code in english.

Tomi
15-01-2016, 04:53 PM
I speak Hungarian too , but even I find it difficult to read code that is not in english.
Your english is pretty good, it would help everyone here if you would develop your code in english.

Maybe I will so, but it is better for me writing in Hungarian so far, because I can separate my variables and other things better.

SilverWarior
15-01-2016, 09:50 PM
Maybe I will so, but it is better for me writing in Hungarian so far, because I can separate my variables and other things better.

I agree with Robert's suggestion.
Having your code written in English does have a great benefit that you can just simply copy and paste part of your code when you are seeking help from others.

I myself am not native English speaking person and used to write my code in Slovenian (my native language) but eventually I figured out that it is better if I write my code in English instead. Why?
When I have all of my code written in English it allows my brains to kind of switch into "English mode" where even most of my thought process is done in English. I know it sounds a bit weird.
But when I have parts of the code written in Slovenian my brain stays in "Slovenian mode" where I'm forced of constantly having to translate English parts of the code or documentation into Slovenian which I think it slows down the thought process a bit.
And sometime it might even happen that my brain would en up being in "English mode" and I would end up constantly translating my Slovenian code into English. And yes I know that this seems even weirder.

Tomi
16-01-2016, 09:42 AM
No, not so weird, SilverWarior, after all you are right: if I would like help from an English-speaking programmer community, the best is when I write my codes totally in English in order to better understand.
Therefore today I have made a small program, which is focusing the "bullet appearing" problem:


unit Unit1;


{$mode objfpc}{$H+}


interface


uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls;


type


{ TForm1 }


TForm1 = class(TForm)
Timer1: TTimer;
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;


type TGameObject=class(TObject)
private
public
xplace,yplace,celx,cely,movedir: integer;
itsimage: TBitmap;
canshoot: boolean;
shoottimer: byte;
constructor create(xplacehere,yplacehere: longint; imagehere: Tbitmap);
end;


var
Form1: TForm1;
backgroundimg,bulletimg,spaceshipimg: TBitMap;
spaceship: TGameObject;
bullet: array [0..100] of TGameObject;
numofbullets: integer;


implementation


{$R *.lfm}


constructor TGameObject.create(xplacehere,yplacehere: integer; imagehere: Tbitmap);
begin
xplace:=xplacehere;
yplace:=yplacehere;
itsimage:=imagehere;
end;


{ TForm1 }


procedure TForm1.FormCreate(Sender: TObject);
var initialmovedir: shortint;
begin
backgroundimg := TBitmap.Create;
backgroundimg.LoadFromFile('kepek/urhatter.bmp');
bulletimg:=TBitMap.Create;
bulletimg.LoadFromFile('kepek/ell_lov.bmp');
bulletimg.transparent:=true;
spaceshipimg:=TBitMap.Create;
spaceshipimg.LoadFromFile('kepek/ell1.bmp');
spaceshipimg.transparent:=true;
randomize;
if round(random(2))=1 then initialmovedir:=-2 else initialmovedir:=2;
spaceship:=TGameObject.create(Form1.ClientWidth div 2,spaceshipimg.height,spaceshipimg);
spaceship.movedir:=initialmovedir;
spaceship.shoottimer:=100;
spaceship.canshoot:=false;
numofbullets:=0;
end;


procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
backgroundimg.free;
bulletimg.free;
spaceshipimg.free;
end;


procedure TForm1.Timer1Timer(Sender: TObject);
var i,j: integer;
begin
Form1.Canvas.StretchDraw(Rect(0, 0, ClientWidth, ClientHeight), backgroundimg);
if spaceship.shoottimer>0 then dec(spaceship.shoottimer) else spaceship.canshoot:=true;
if (spaceship.xplace+spaceship.movedir>=ClientWidth-spaceshipimg.width) or (spaceship.xplace+spaceship.movedir<=1) then
begin
if spaceship.yplace+spaceshipimg.height<ClientHeight then
spaceship.yplace:=spaceship.yplace+spaceshipimg.he ight
else
spaceship.yplace:=spaceshipimg.height;
spaceship.movedir:=-spaceship.movedir;
end
else
begin
spaceship.xplace:=spaceship.xplace+spaceship.moved ir;
end;
Form1.Canvas.Draw(spaceship.xplace,spaceship.yplac e,spaceshipimg);
if (round(random(50))=1) and (spaceship.canshoot=true) then
begin
bullet[numofbullets]:=TGameObject.create(spaceship.xplace,spaceship.yp lace,bulletimg); {!!!WRONG!!!}
inc(numofbullets);
spaceship.canshoot:=false;
spaceship.shoottimer:=100;
end;


if numofbullets>0 then
begin
i:=0;
while i<numofbullets-1 do
begin
if bullet[i]<>nil then
begin
if bullet[i].yplace>ClientHeight then
begin
freeandnil(bullet[i]);
if i=numofbullets-1 then
dec(numofbullets)
else
begin
for j:=i+1 to numofbullets-1 do
bullet[j-1]:=bullet[j];
dec(numofbullets);
dec(i);
end;
end
else
begin
bullet[i].yplace:=bullet[i].yplace+4;
Form1.Canvas.Draw(bullet[i].xplace,bullet[i].yplace,bulletimg);
end;
end;
inc(i);
end;
end;
end;


end.

Maybe somebody can help me...

User137
02-02-2016, 03:15 PM
The last loop is still bothering me, it is needlessly complicated and could make errors. Like i suggested earlier, here's how it would look:

for i:=numofbullets-1 downto 0 do
begin
bullet[i].yplace:=bullet[i].yplace+4;
if bullet[i].yplace>ClientHeight then
begin
bullet[i].Free;
dec(numofbullets);
bullet[i]:=bullet[numofbullets];
else begin
Form1.Canvas.Draw(bullet[i].xplace, bullet[i].yplace, bulletimg);
end;
end;