Forum > Games

Image inside Bitmap

(1/4) > >>

LeopardCoder:
I am currently programming a game in Lazarus and would like to display the image of my player. My player is a record and so far has values like health and damage as elements in the record. But to check for collisions I need a bitmap (is that right). So I tried to load the image into a bitmap somehow, but it didn't work. I hope you can help me with this.
So I am looking for the code:
Having a bitmap that displays an image with my player that is the same size as the image itself.

I would really appreciate any replies.

PS: I am fairly new to Lazarus/Pascal.

With kind regards :D

furious programming:

--- Quote from: LeopardCoder on January 22, 2023, 02:45:25 pm ---But to check for collisions I need a bitmap (is that right).
--- End quote ---

I you need to test collisions, you need hitboxes, not images. Collisions are not performed per pixel, because firstly, it is inefficient, and secondly, the end result is annoying and makes it difficult for players to play. Try using simple geometric figures to describe collision-sensitive space — hitboxes in a 2D world are usually rectangles or circles for simplicity of calculation (one or many per object).

If you're making a game using visual components from the LCL library, it's best to throw the code in the trash right away and start over — this time using a sensible game development library, such as SDL, Allegro, Raylib, etc. Playing with LCL is a waste of time.

LeopardCoder:
Thank you very much for your answer. Unfortunately, this project has to be done in Lazarus without a sensible game development library. I would now give each object a shape (rectangle) and use Left and Top to check if it collides with another shape in the form. Is the following code suitable:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---varshape1: TShape;i: Integer;beginshape1 := TShape.Create(Self); // Assign shape1 propertiesshape1.Shape := stRectangle;shape1.Width := 100;shape1.Height := 50;shape1.Left := 50;shape1.Top := 50; // Iterate through all shapes on the formfor i := 0 to Self.ComponentCount - 1 dobeginif Self.Components[i] is TShape thenbegin// Check for collision with shape1if (shape1.Left + shape1.Width >= TShape(Self.Components[i]).Left) and (shape1.Left <= TShape(Self.Components[i]).Left + TShape(Self.Components[i]).Width) and(shape1.Top + shape1.Height >= TShape(Self.Components[i]).Top) and (shape1.Top <= TShape(Self.Components[i]).Top + TShape(Self.Components[i]).Height) thenbeginShowMessage('Collision detected with shape ' + TShape(Self.Components[i]).Name + '!');end;end;end; 

furious programming:

--- Quote from: LeopardCoder on January 22, 2023, 08:40:58 pm ---Unfortunately, this project has to be done in Lazarus without a sensible game development library.

--- End quote ---

Could you tell me what is the reason for that?

Also, using a library like SDL doesn't mean you have to use a foreign engine. Absolutely — SDL is just a set of functions for different things, and you'll have to create all the data structures and all the logic yourself anyway. The difference, however, is that SDL contains a lot of functionality, including convenient rendering (including using the GPU), support for a sound mixer (as many sounds played simultaneously as you want), support for input devices, etc., so you can create any program (not just games) and use your hardware to the full, using just simple functions.

I also used to make a game using only classes from LCL (check out the Deep Platformer game prototype) and the performance of this solution is very poor. So it's a waste of time for such games, it's better to spend it on a sensible API. Take this as good advice and save yourself time.


Answering your question, you are making few mistakes. First, this code:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---for i := 0 to Self.ComponentCount - 1 dobegin  if Self.Components[i] is TShape then  begin    // Check for collision with shape1    if (shape1.Left + shape1.Width >= TShape(Self.Components[i]).Left) and (shape1.Left <= TShape(Self.Components[i]).Left + TShape(Self.Components[i]).Width) and       (shape1.Top + shape1.Height >= TShape(Self.Components[i]).Top) and (shape1.Top <= TShape(Self.Components[i]).Top + TShape(Self.Components[i]).Height) then    begin      {...}
is buggy and horribly inefficient. Not only does it test collisions with all objects on the form (including itself, which is a bug), but it also does not cache references, so each expression in the condition means extracting the object of the tested control from the form's components list.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---var  NewShape:   TShape;  Shape:      TShape;  Index:      Integer;  CommonPart: TRect;begin  NewShape        := TShape.Create(Self);  NewShape.Parent := Self;  NewShape.Shape  := stRectangle;  NewShape.Width  := 100;  NewShape.Height := 50;  NewShape.Left   := 50;  NewShape.Top    := 50;   // iterate through all components  for Index := 0 to Self.ComponentCount - 1 do    if Self.Components[Index] is TShape then    begin      Shape := Self.Components[Index] as TShape;       // do not test collision with self      if Shape <> NewShape then        // check collision 
Secondly, if all the shapes are rectangles, instead of checking each edge of two rectangles manually, use a structure with its area, such as Shape.BoundsRect and for testing the intersection of rectangles there is already a function — IntersectRect from the Types module. So your code can be simplified:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---var  NewShape:   TShape;  Shape:      TShape;  Index:      Integer;  CommonPart: TRect;begin  NewShape        := TShape.Create(Self);  NewShape.Parent := Self;  NewShape.Shape  := stRectangle;  NewShape.Width  := 100;  NewShape.Height := 50;  NewShape.Left   := 50;  NewShape.Top    := 50;   // iterate through all components  for Index := 0 to Self.ComponentCount - 1 do    if Self.Components[Index] is TShape then    begin      Shape := Self.Components[Index] as TShape;       // do not test collision with self      if Shape <> NewShape then        // check collision        if IntersectRect(CommonPart, Shape.BoundsRect, NewShape.BoundsRect) then        begin          // show message and use "CommonPart" or not          ShowMessage('Collision detected with ' + Shape.Name + '!');          Exit;        end;    end; 
This code can be further optimized, but using LCL classes to represent game objects and to render it on the screen is still a waste of CPU time, so who cares. 8)


Also remember that keeping all objects in one list is the worst possible solution, because every time you want to check if an object collides with another, you will iterate through the whole list. The complexity of such a solution is dire, basically O(n) where n is the number of objects in the list, and if you have m objects to move and detecting collisions, instead of iterating the list n times, you'll have to iterate the list n*m times. With 100 moving objects on the screen, checking their collisions will require 100*100 tests, or 10,000 — the increase is exponential.

LeopardCoder:
Yes, of course. I have to do this project for school. We are supposed to program a game/app in Lazarus. I have decided to reprogram "Archero" in a simplified form: I am controlling the player with a timer that is activated every 10 milliseconds, so the player moves smoothly. In addition, there are about 5 enemies per level that move towards the player and then cause damage or shoot at the player. The player only shoots when he stops, and then always aims at the nearest enemy. The whole game is only implemented in 2D.
I could, however, ask again whether we can also use sensible game development libraries. There are no great expectations for the game itself.  However, I have already spent a lot of time doing all the calculations and everything myself. We only have a few weeks until the deadline. Do you think it's still worth switching to SDL? How long would it take to understand SDL well enough to use it?

Btw: When I use your Code, I get the Error: "Identifier not found "IntersectRect". Is there anything i need to add (in the uses for example)?
Here is the code I already have:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, LCLtype, StdCtrls, Math; //Player recordtype  TPlayer = record    Image:       TImage;    Health:      integer;    Damage:      integer;    FireRate:    integer;    Tag:         integer;  end;type  TArrow = record    Image:        TImage;    Damage:       integer;    Tag:          integer;  end; //Enemy1 recordtype  TEnemy = record    Image:       TImage;    Health:      integer;    Damage:      integer;    Tag:         integer;  end;type   { TForm1 }  TForm1 = class(TForm)  Timer1: TTimer;  MoveArrow: TTimer;  ShootArrow: TTimer;procedure MoveArrowTimer;procedure FormActivate(Sender: TObject);procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);procedure ShootArrowTimer(Sender: TObject);procedure Timer1Timer(Sender: TObject);procedure Upgrade1Click(Sender: TObject);procedure Upgrade2Click(Sender: TObject);procedure Upgrade3Click(Sender: TObject);procedure ShowUpgrades;   private   public   end; var  Form1                                  : TForm1;  Player                                 : TPlayer;  MoveUp, MoveDown, MoveLeft, MoveRight  : boolean;  ArrayEnemies                           : array of TEnemy;  //Legt fest, welcher Pfeil bewegt wird  PictureOfArrowToShoot                  : Timage;  //X und Y abstände zwischen Gegner und Gegner, der am nähesten ist  xDiff                                  : integer;  yDiff                                  : integer;  canShoot                               : boolean;  PlayerCollision                        : boolean; const  //Anzahl an Gegnern im Level  AmountWilly = 5;  AmountKlaus = 5;  AmountFreddy = 5; implementation {$R *.lfm}//Beliebiges Bild ladenprocedure LoadImage(ImageToLoad: TImage; FileName: string);begin   ImageToLoad.Picture.LoadFromFile(FileName); end; //Enemies Spawnen procedure SpawnEnemies(const NumEnemies: Integer);var  k: Integer;begin   SetLength(ArrayEnemies, NumEnemies);  for k := 0 to NumEnemies - 1 do  begin    ArrayEnemies[k].Image := TImage.Create(nil);    ArrayEnemies[k].Image.Parent := Form1;    ArrayEnemies[k].Image.Left := 50 + (k*10);    ArrayEnemies[k].Image.Top := 50 +(k*10);    ArrayEnemies[k].Image.Width := 100;    ArrayEnemies[k].Image.Height := 100;    ArrayEnemies[k].Image.Stretch := true;    LoadImage(ArrayEnemies[k].Image, 'Bilder/BulletTest.jpg');    ArrayEnemies[k].Health := 100;  end; end; //Position eines Enemys ändernprocedure SetEnemyPosition(const EnemyIndex: Integer;  NewLeft, NewTop: Integer);begin   ArrayEnemies[EnemyIndex].Image.Left := NewLeft;  ArrayEnemies[EnemyIndex].Image.Top := NewTop; end; procedure TForm1.FormActivate(Sender: TObject);var//Für Collision - weg machenX, Y, i: Integer;begin   //Bild des zu schießenden Pfeil muss vorher erstmal erschaffen werden und dient solange als Platzhalter  PictureOfArrowToShoot := TImage.Create(nil);   //Bild des Spieler Laden  Player.Image := TImage.Create(nil);  LoadImage(Player.Image, 'Bilder/UserTest.jpg');  Player.Image.Parent := Form1;  Player.Image.Left := 10;  Player.Image.Top := 10;  Player.Image.Width := 100;  Player.Image.Height := 100;  Player.Image.Stretch := true;  Player.FireRate := 1000;   //Position des Spielers zu Beginn festlegen  Player.Image.Left := (Self.ClientWidth - Player.Image.Width) div 2;  Player.Image.Top := ((Self.ClientHeight - Player.Image.Height) div 2) {+ 800};   //Startwerte Spieler festlegen  Player.Health := 50;  Player.Damage := 10;   //Enemies spawnen  SpawnEnemies(AmountWilly);   //Enemies platzieren  SetEnemyPosition(0, 10, 10);  SetEnemyPosition(1, 220, 10);  SetEnemyPosition(2, 430, 10);  SetEnemyPosition(3, 640, 10);  SetEnemyPosition(4, 850, 10);    //Upgrades zum test am anfang zeigen  ShowUpgrades;   //Auf collision testen  {  for X := 0 to Player.Image.Width - 1 do      begin        for Y := 0 to Player.Image.Height - 1 do        begin          if (Player.Image.Canvas.Pixels[X, Y] = ArrayEnemies[i].Canvas.Pixels[X + Player.Image.Left, Y + Player.Image.Top]) then          begin            PlayerCollision := True;            Break;          end;        end;        if PlayerCollision then Break;      end;      if PlayerCollision then Break;    end;   }end; //Nähesten Gegner ermittlen    (welcher Gegner wird anvisiert)function GetClosestEnemy(Image: TImage) : integer;var  k:                                      integer;  Distance, MinDistance:                  double;  DeltaX, DeltaY:                         integer;  ClosestEnemy:                           integer;begin   ClosestEnemy := 0;  MinDistance := MaxInt; //sehr große Zahl   for k := 0 to 4 do    //Später noch zu AmountWilly ändern  begin    DeltaX := ArrayEnemies[k].Image.Left + (ArrayEnemies[k].Image.Width div 2) - (Image.Left + (Image.Width div 2));    DeltaY := ArrayEnemies[k].Image.Top + (ArrayEnemies[k].Image.Height div 2) - (Image.Top + (Image.Height div 2));    Distance := Sqrt(DeltaX * DeltaX + DeltaY * DeltaY);    if Distance < MinDistance then    begin      MinDistance := Distance;      ClosestEnemy := k;    end;  end;   //Showmessage(Inttostr(ClosestEnemy) + '- Min k - ' + FloatToStr(MinDistance));  result := ClosestEnemy; end;  //Pfeil bewegenprocedure TForm1.MoveArrowTimer;begin    //Pfeil bewegt sich noch unterschiedlich schnell, je nachdem wie weit man vom gegner entfernt steht!!   //SpeedAdjuster := ClosestDistance;   PictureOfArrowToShoot.Top := PictureOfArrowToShoot.Top + Round(yDiff/100);   PictureOfArrowToShoot.Left := PictureOfArrowToShoot.Left + Round(xDiff/100); end; //Pfeil spawnenprocedure SpawnArrow;var  Arrow: TArrow;  ArrowCenterX: integer;  ArrowCenterY: integer;  //Closest Enemy  z : integer; begin   Arrow.Image := TImage.Create(nil);  Arrow.Image.Parent := Form1;  Arrow.Image.Width := 50;  Arrow.Image.Height := 50;  Arrow.Image.Stretch := true;   //Mitte des Pfeils ermitteln  ArrowCenterX := Arrow.Image.Width div 2;  ArrowCenterY := Arrow.Image.Height div 2;   //Pfeil in der Mitte des Spieler platzieren  Arrow.Image.Left := (Player.Image.Left - ArrowCenterX) + Player.Image.Width div 2;  Arrow.Image.Top := (Player.Image.Top - ArrowCenterY) + Player.Image.Height div 2;  LoadImage(Arrow.Image, 'Bilder/BulletTest.jpg');  PictureOfArrowToShoot := Arrow.Image;   //Abstand zwischen dem nähesten Gegner und Spieler berechnen  z := GetClosestEnemy(Player.Image);   xDiff := (ArrayEnemies[z].Image.Left + ArrayEnemies[z].Image.Width div 2) - (PictureOfArrowToShoot.Left + ArrayEnemies[z].Image.Width div 2);  yDiff := (ArrayEnemies[z].Image.Top + ArrayEnemies[z].Image.Height div 2) - (PictureOfArrowToShoot.Top + ArrayEnemies[z].Image.Height div 2);  //Showmessage('xDiff: ' + IntToStr(xDiff));  //Showmessage('yDiff: ' + IntToStr(yDiff)); end; //Feuerrateprocedure TForm1.ShootArrowTimer(Sender: TObject);begin   if canShoot = true then  begin  ShootArrow.Interval := Player.FireRate;  SpawnArrow;  end; end;  procedure TForm1.Timer1Timer(Sender: TObject);begin   //Hoch  if MoveUp = true then  begin    Player.Image.Top := Player.Image.Top - 10;    canShoot := false;  end  //Runter  else If MoveDown = true then  begin    Player.Image.Top := Player.Image.Top + 10;    canShoot := false;  end  //Links  else if MoveLeft = true then  begin    Player.Image.Left:= Player.Image.Left - 10;    canShoot := false;  end  //Rechts  else if MoveRight = true then  begin    Player.Image.Left := Player.Image.Left + 10;    canShoot := false;  end  else  canShoot := true; end;   //Erstes Upgrade geclicktprocedure TForm1.Upgrade1Click(Sender: TObject);begin  Showmessage('Upgrade 1 clicked');end; //Zweites Upgrade geclickprocedure TForm1.Upgrade2Click(Sender: TObject);begin  Showmessage('Upgrade 2 clicked');end; //drittes Upgrade geclicktprocedure TForm1.Upgrade3Click(Sender: TObject);begin  Showmessage('Upgrade 3 clicked');end; //Upgradesprocedure TForm1.ShowUpgrades;var  Upgrades : array[1..3] of TImage;  k : integer;begin  //Showmessage('Ugrades werden angezeigt');  // Create TImage components  for k := 1 to 3 do  begin    Upgrades[k] := TImage.Create(nil);    Upgrades[k].Parent := Form1;    Upgrades[k].Height := 100;    Upgrades[k].Width :=100;    Upgrades[k].Stretch := true;    Upgrades[k].Top:= 500;    Upgrades[k].Left := 50 + (k*200);  end;    // Load images for each TImage component  Upgrades[1].Picture.LoadFromFile('Bilder/BulletTest.jpg');  Upgrades[2].Picture.LoadFromFile('Bilder/UserTest.jpg');  Upgrades[3].Picture.LoadFromFile('Bilder/BulletTest.jpg');   //OnClick Events für jedes Bild  Upgrades[1].OnClick:= @Upgrade1Click;  Upgrades[2].OnClick:= @Upgrade2Click;  Upgrades[3].OnClick:= @Upgrade3Click;  end; procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);begin   //Hoch  if key = VK_UP then  begin    MoveUp:= true;  end;  //Runter  if key = VK_DOWN then  begin    MoveDown := true;  end;  //Links  if key = VK_LEFT then  begin    MoveLeft:= true;  end;  //Rechts  if key = VK_RIGHT then  begin    MoveRight := true;  end;   //Upgrades zeigen  if key = VK_Space then  begin     ShowUpgrades;  end;  end; procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);begin   //Hoch  if key = VK_UP then  begin    MoveUp := false;  end;  //Runter  if key = VK_DOWN then  begin    MoveDown := false;  end;  //Links  if key = VK_LEFT then  begin    MoveLeft:= false;  end;  //Rechts  if key = VK_RIGHT then  begin    MoveRight := false;  end;  end; end. 

Navigation

[0] Message Index

[#] Next page

Go to full version