Forum > Games
Image inside Bitmap
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