Recent

Author Topic: How to handle moving and deleting of objects drawn on a bitmap?  (Read 30553 times)

knuckles

  • Full Member
  • ***
  • Posts: 122
I have been looking at various different programs such as Tiled, RPG Maker, Game Maker etc to see how they draw tiles and sprites onto the levels etc. Most modern applications seem to utilize hardware acceleration either through OpenGL or DirectX.

I was looking closely at some older versions of Game Maker because they does not use hardware acceleration and I was interested to find out how it is able to draw lots of sprites on the levels (or in this case rooms) and move and delete them without too much slow down. Using DeDe and Olly it wasn't too hard to easily see that the room editor actually uses nothing more than a TImage and drawing directly onto its internal bitmap canvas, for example Image1.Canvas.Draw(X, Y, Sprite).

What stumped me though is how exactly does it achieve the moving and deleting of the drawn sprites from the internal bitmap of the level/room? You can hold the Ctrl key down and click and move one of the drawn sprites that is under the mouse, or delete the sprite under the mouse etc but how is everything handled here?

See if I was planning on creating a level editor I would have an array or pointer list storing sprite information such as X and Y position and then draw everything onto a canvas such as paintbox etc, then moving a sprite would simply be a case of changing the X and Y position of that sprite and invalidating.

Out of curiosity though and from a technical point of view, how do you think the level editor in old Game Maker versions achieves the drawing, moving and deleting of sprites onto a TImage canvas? See, the sprites are already drawn onto the internal bitmap so I don't see an obvious way of how the internal bitmap is updated when a sprite is moved or deleted?

Maybe I am missing the obvious and it is not really that technical at all but as a little exercise, it would be interesting to see how it is possible to draw directly onto a TImage canvas and then move/delete just like the Game Maker level (room) editor does without slowdown.

Ideas and suggestions welcome :)

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #1 on: May 13, 2017, 10:02:47 pm »
The technique used is based on regions and clipping, i.e. the objects are drawn in a paint event or a forced paintcall from background to foreground, where the smaller objects are painted within their own boundaries. Note that on moving the objects only the area that is affected by the movement needs to be repainted based on the region it occupies on the total canvas. This is highly efficient. There's a bit more to it, but that's the theory.
« Last Edit: May 13, 2017, 10:04:19 pm by Thaddy »
Specialize a type, not a var.

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #2 on: May 13, 2017, 10:44:48 pm »
The technique used is based on regions and clipping, i.e. the objects are drawn in a paint event or a forced paintcall from background to foreground, where the smaller objects are painted within their own boundaries. Note that on moving the objects only the area that is affected by the movement needs to be repainted based on the region it occupies on the total canvas. This is highly efficient. There's a bit more to it, but that's the theory.

Ok that's a great start, good insight.

I'm trying to understand the design choice, why would the developer decide to draw directly to a image canvas and not a paintbox for example, seems rather odd to me and that prompted this question of how can you move already painted objects on a bitmap around, it confused me somewhat.

Maybe I can attempt to write a short demo project to further understand this approach..

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #3 on: May 14, 2017, 12:24:28 am »
Ok here is the starting blocks of the test project...

For this test i chose to store sprites in an array, each sprite has its own bitmap and information regarding X and Y position which will be needed to move to a new position etc.

There are placeholders for moving and deleting which I will try and implement tomorrow as its getting late here.

Source:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  9.   StdCtrls;
  10.  
  11. type
  12.   TSprite = record
  13.     Bitmap: TBitmap;
  14.     X: Integer;
  15.     Y: Integer;
  16.   end;
  17.  
  18.   TForm1 = class(TForm)
  19.     imgCanvas: TImage;
  20.     imgSprite1: TImage;
  21.     imgSprite2: TImage;
  22.     RadioButton1: TRadioButton;
  23.     RadioButton2: TRadioButton;
  24.     procedure FormCreate(Sender: TObject);
  25.     procedure FormDestroy(Sender: TObject);
  26.     procedure imgCanvasMouseDown(Sender: TObject; Button: TMouseButton;
  27.       Shift: TShiftState; X, Y: Integer);
  28.     procedure imgCanvasMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  29.     procedure imgCanvasMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  30.   private
  31.     FDrawing: Boolean;
  32.     FMoving: Boolean;
  33.     FDeleting: Boolean;
  34.     FSprites: array of TSprite;
  35.     function GetSprite: TBitmap;
  36.     procedure DrawSprite(ASprite: TBitmap; AX, AY: Integer);
  37.   public
  38.     { public declarations }
  39.   end;
  40.  
  41. var
  42.   Form1: TForm1;
  43.  
  44. implementation
  45.  
  46. {$R *.lfm}
  47.  
  48. { TForm1 }
  49.  
  50. function TForm1.GetSprite: TBitmap;
  51. begin
  52.   if RadioButton1.Checked then
  53.     Result := imgSprite1.Picture.Bitmap
  54.   else if RadioButton2.Checked then
  55.     Result := imgSprite2.Picture.Bitmap
  56. end;
  57.  
  58. procedure TForm1.DrawSprite(ASprite: TBitmap; AX, AY: Integer);
  59. begin
  60.   SetLength(FSprites, Length(FSprites) + 1);
  61.   FSprites[High(FSprites)].Bitmap := TBitmap.Create;
  62.   FSprites[High(FSprites)].Bitmap.Assign(ASprite);
  63.   FSprites[High(FSprites)].X := AX;
  64.   FSprites[High(FSprites)].Y := AY;
  65.  
  66.   imgCanvas.Canvas.Draw(AX, AY, ASprite);
  67. end;
  68.  
  69. procedure TForm1.FormCreate(Sender: TObject);
  70. begin
  71.   SetLength(FSprites, 0);
  72. end;
  73.  
  74. procedure TForm1.FormDestroy(Sender: TObject);
  75. var
  76.   I: Integer;
  77. begin
  78.   for I := Low(FSprites) to High(FSprites) do
  79.     FSprites[I].Bitmap.Free;
  80.   SetLength(FSprites, 0);
  81. end;
  82.  
  83. procedure TForm1.imgCanvasMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  84. begin
  85.   FDrawing := Button = mbLeft;
  86.   FMoving := (ssCtrl in Shift) and (Button = mbLeft);
  87.   FDeleting := Button = mbRight;
  88.  
  89.   if FDrawing then
  90.   begin
  91.     DrawSprite(GetSprite, X, Y);
  92.   end;
  93.  
  94.   if FMoving then
  95.   begin
  96.     //
  97.   end;
  98.  
  99.   if FDeleting then
  100.   begin
  101.     //
  102.   end;
  103. end;
  104.  
  105. procedure TForm1.imgCanvasMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  106. begin
  107.   if FDrawing then
  108.   begin
  109.     DrawSprite(GetSprite, X, Y);
  110.   end;
  111.  
  112.   if FMoving then
  113.   begin
  114.     //
  115.   end;
  116.  
  117.   if FDeleting then
  118.   begin
  119.     //
  120.   end;
  121. end;
  122.  
  123. procedure TForm1.imgCanvasMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  124. begin
  125.   FDrawing := False;
  126.   FMoving := False;
  127.   FDeleting := False;
  128. end;
  129.  
  130. end.

LFM:
Code: Pascal  [Select][+][-]
  1. object Form1: TForm1
  2.   Left = 369
  3.   Height = 529
  4.   Top = 130
  5.   Width = 634
  6.   Caption = 'Form1'
  7.   ClientHeight = 529
  8.   ClientWidth = 634
  9.   OnCreate = FormCreate
  10.   OnDestroy = FormDestroy
  11.   LCLVersion = '1.6.4.0'
  12.   object imgCanvas: TImage
  13.     Left = 8
  14.     Height = 450
  15.     Top = 9
  16.     Width = 618
  17.     OnMouseDown = imgCanvasMouseDown
  18.     OnMouseMove = imgCanvasMouseMove
  19.     OnMouseUp = imgCanvasMouseUp
  20.   end
  21.   object imgSprite1: TImage
  22.     Left = 32
  23.     Height = 24
  24.     Top = 472
  25.     Width = 24
  26.     Picture.Data = {
  27.       07544269746D6170F6060000424DF80600000000000036000000280000001800
  28.       000018000000010018000000000000000000120B0000120B0000000000000000
  29.       0000FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF797877787776
  30.       767675FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
  31.       FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF7B
  32.       7A7981807FC0C0BF9A9A99807F7E73737270706FFF00FFFF00FFFF00FFFF00FF
  33.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
  34.       FFFF00FF7D7B7A929191EDEDEDF2F2F2E0E0E0DADADAC1C1C19998987979786D
  35.       6D6C6C6B6AFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
  36.       FF00FFFF00FFFF00FF7F7E7D939392F6F6F6FFFFFFF4F4F4E5E5E5E0E0E0D9D9
  37.       D9D4D4D42380230E690D346A33516950686867676766FF00FFFF00FFFF00FFFF
  38.       00FFFF00FFFF00FFFF00FFFF00FF807F7EACACABF6F6F6FFFFFFFFFFFFF7F7F7
  39.       EAEAEAE5E5E5E0E0E0D9D9D9248124016801016801016801789D789696967574
  40.       74646463626260FF00FFFF00FFFF00FFFF00FF828180ADACACFFFFFFFFFFFFFF
  41.       FFFFFFFFFFFAFAFAEFEFEFEAEAEAE5E5E5DEDEDE258225016801016801016801
  42.       86AB86BFBFBFBDBDBDB1B1B160605FFF00FFFF00FFFF00FF838382CECECEFFFF
  43.       FFFFFFFFFFFFFFFFFFFFFFFFFFFCFCFCF4F4F4EFEFEFEAEAEAE5E5E526832601
  44.       680101680101680189AF89C4C4C4BFBFBFBDBDBD60605FFF00FFFF00FF868483
  45.       B7B7B6FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFAFAFAF4F4F4EFEF
  46.       EFEAEAEA2786270168010168010168018EB28EC9C9C9C4C4C4BFBFBF60605FFF
  47.       00FFFF00FF868483C0BFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
  48.       FEFEFEFAFAFAF4F4F4EFEFEF28872801680101680101680191B791CECECEC9C9
  49.       C9C4C4C460605FFF00FFFF00FF868483C0BFBFFFFFFFFFFFFFFFFFFFEAF0EAAF
  50.       C5AFAFC5AFFFFFFFFEFEFEFEFEFEFAFAFAF4F4F4288828016801016801016801
  51.       95BB95D4D4D4CECECEC9C9C960605FFF00FFFF00FF868483C0BFBFFFFFFFFFFF
  52.       FFAFC5AF114711003A00003A00467446FEFEFEFEFEFEFEFEFEFAFAFA29892901
  53.       680101680101680198BF98D9D9D9D3D3D3CECECE60605FFF00FFFF00FF868483
  54.       C0BFBFEAF0EA56815600360007660C139020004B00004100467446FEFEFEFEFE
  55.       FEFEFEFEFAFAFACEE2CE81BA81509E50ADCAADDEDEDED8D8D8D3D3D360605FFF
  56.       00FFFF00FF868483C0BFBF114711003F00148F232DD44E28CB44139320004B00
  57.       003F00467446FEFEFEFEFEFEFEFEFEFAFAFAF4F4F4EEEEEEE9E9E9E3E3E3DEDE
  58.       DED8D8D860605F003500FF00FF868483003500075F0C2BC44A33DC582DD44E28
  59.       CB4423C43B108F1C004B00003A00467446FEFEFEFEFEFEFEFEFEFAFAFAF3F3F3
  60.       EEEEEEE9E9E9E3E3E387A187063A06004100FF00FF003A001B992F3FED6C39E5
  61.       6333DC582DD44E28CB4423C43B1DBC330D8A17004B00003A00467446FEFEFEFE
  62.       FEFEFEFEFEFAFAFAF3F3F3EEEEEE4E784E003500004600FF00FFFF00FF056409
  63.       45F6773FED6C39E56333DC582DD44E28CB4423C43B1DBC3318B42A0B8612004B
  64.       00003A00467446FEFEFEFEFEFEFEFEFED0DCD01C4E1C003600004900FF00FFFF
  65.       00FFFF00FF05640934D1593FED6C39E56333DC582DD44E28CB4423C43B1DBC33
  66.       18B42A13AC2209810F004B00003A00467446FEFEFE9AB59A073A07003A00004B
  67.       00FF00FFFF00FFFF00FFFF00FFFF00FF0564092FCB5139E56333DC582DD44E28
  68.       CB4423C43B1DBC3318B42A13AC220FA41A067D0A004B00003A001C501C003000
  69.       004100004B00FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF05640934D9
  70.       5A33DC582DD44E28CB4423C43B1DBC3318B42A19B52C0DA1180B9D1304780700
  71.       4B00003C00004200004B00FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
  72.       FF00FFFF00FF086A0D2ED1502DD44E28CB4423C43B1DBC332CD14B34DD590187
  73.       0309991007960D027404004B00004B00FF00FFFF00FFFF00FFFF00FFFF00FFFF
  74.       00FFFF00FFFF00FFFF00FFFF00FFFF00FF07690C29CA4728CB4423C43B1DBC33
  75.       2DD34D34DD59008100008300058209005601FF00FFFF00FFFF00FFFF00FFFF00
  76.       FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF05640924
  77.       C23F23C43B1DBC332DD34D34DD59008100008100056409FF00FFFF00FFFF00FF
  78.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
  79.       FFFF00FFFF00FF0462071FBB351BB42E0564090F6919014F02016402056409FF
  80.       00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
  81.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FF056409056409FF00FF0048000042
  82.       00004A01FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF
  83.       00FF
  84.     }
  85.     Transparent = True
  86.   end
  87.   object RadioButton1: TRadioButton
  88.     Left = 16
  89.     Height = 19
  90.     Top = 496
  91.     Width = 59
  92.     Caption = 'Sprite 1'
  93.     Checked = True
  94.     TabOrder = 1
  95.     TabStop = True
  96.   end
  97.   object imgSprite2: TImage
  98.     Left = 112
  99.     Height = 24
  100.     Top = 472
  101.     Width = 24
  102.     Picture.Data = {
  103.       07544269746D6170F6060000424DF80600000000000036000000280000001800
  104.       000018000000010018000000000000000000120B0000120B0000000000000000
  105.       0000FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
  106.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
  107.       FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF
  108.       00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
  109.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFA47874A47874A478
  110.       74A47874A47874A47874A47874A47874A47874A47874A47874A47874A47874A4
  111.       7874A47874A47874A47874A47874A47874A47874A47874FF00FFFF00FFA47874
  112.       BE9281F0C39BFFD1A2FFD1A1FED1A1FFD1A2FFD1A2FFD1A1FFD1A1FFD1A2FFD1
  113.       A1FFD1A1FFD1A1FFD1A1FFD1A2FFD1A2FFD1A2FFD1A2FFD2A2F0C39ABE9281A4
  114.       7874FF00FFA47874E2BA9CB98E81F9D2ABFFD7ADFED7ADFFD7AEFFD7ADFFD7AE
  115.       FED7ADFFD6ADFED7AEFED7AEFFD7ADFFD7AEFFD7ADFED7ADFFD7AEFFD7ADFAD2
  116.       ABB98E81E2B99CA47874FF00FFA47874FEDEBCD0A996C9A191FFDEBBFFDEBCFF
  117.       DEBCFFDEBCFEDEBCFEDEBCFFDEBBFFDEBCFEDEBCFFDEBCFEDEBCFEDEBCFFDEBC
  118.       FFDEBCFFDEBCC8A191D0A996FEDEBCA47874FF00FFA47874FEE6CCFCE3CABE98
  119.       8EDCBCAAFEE6CBFFE5CBFFE6CBFEE6CBFEE5CCFFE5CCFFE5CCFFE6CBFEE5CCFE
  120.       E6CCFEE6CBFFE5CBFFE5CCDCBCAABE988DFDE3C9FFE6CBA47874FF00FFA47874
  121.       FEEDDBFFEDDBF4DFD0B58E87EED7C7FFEDDBFFEDDBFEEDDBFFEDDBFEEDDBFFED
  122.       DBFEEDDBFEEDDBFEEDDBFEEDDBFEEDDBEDD7C8B58E87F3DFCFFEEDDBFEEDDBA4
  123.       7874FF00FFA47874FEF4E9FEF4EAFEF4E9E5D2CAB6918CF8ECE3FFF4EAFEF4EA
  124.       FEF4EAFEF4E9FEF4EAFEF4E9FEF4EAFEF4E9FEF4E9F9ECE3B6918CE6D2C9FEF4
  125.       E9FEF4EAFEF4E9A47874FF00FFA47874FEFAF5FEFAF6FEFAF5FFF9F6D2BAB6C5
  126.       A7A3FEFAF6FEFAF6FEFAF5FEF9F5FEFAF6FEFAF5FEFAF6FEFAF5FFFAF6C5A7A3
  127.       D2BBB7FEF9F6FEFAF6FEFAF5FEFAF5A47874FF00FFA47874FEFEFEFEFEFEFEFE
  128.       FEFEFEFEFDFCFCBFA09ED8C5C4FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE
  129.       FEFED6C3C1C0A29FFDFCFCFEFEFEFEFEFEFEFEFEFEFEFEA47874FF00FFA47874
  130.       FEFEFEFEFEFEFEFEFEFDFCFCD4BFBEBE9F9CAD8582AD8582AD8582AD8582AD85
  131.       82AD8582AD8582AD8582AD8582D0B9B7FDFCFCFEFEFEFEFEFEFEFEFEFEFEFEA4
  132.       7874FF00FFA47874FEFEFEFEFEFEF4F0EFBFA09DC4A7A5F7F4F4FEFEFEFEFEFE
  133.       FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF7F4F4C4A7A5BFA09DF4F0EFFEFE
  134.       FEFEFEFEFEFEFEA47874FF00FFA47874FEFEFEE4D8D6B48F8CD9C6C5FEFEFEFE
  135.       FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE
  136.       D9C6C5B48F8CE4D8D6FEFEFEFEFEFEA47874FF00FFA47874D3BEBCB89693BEBE
  137.       BEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE
  138.       FEFEFEFEFEFEFEFEFEFEFEEEE6E5B89693D3BEBCFEFEFEA47874FF00FFA47874
  139.       B59D9EB8ECECBEBEBEFFFFFFFCFFFFFBFFFFF8FFFFF8FFFFF3FFFFF3FFFFF3FF
  140.       FFF5FFFFF5FFFFF7FFFFF8FFFFF7FFFFF3FFFFEDF8F8DCE8E8C5C7C7C3B1B1A4
  141.       7874FF00FFA47874B9AAADBAFEFFBEBEBEFEFFFFFCFFFFFCFFFFFCFFFFFFFFFF
  142.       FFFFFFFFFFFFFCFFFFF2F9F9DFE6E7CFD8D9C6CCCDBFC4C5BEBFBFBCBFC0B5C0
  143.       C4AFBEC3B6A6A8A47874FF00FFFF00FFA47874C8ABABBEBEBEFFFFFFFFFFFFFF
  144.       FFFFEEF0F0D9DFE0CCD2D3C2C9C9BCC0C1BCBEBFB6BFC1ACC0C5A0C0C996C0CE
  145.       8CC2D881C9E792BCD4B99BA0A47874FF00FFFF00FFFF00FFFF00FFA47874D3AB
  146.       AABFC4C5BDC9C9BEBEBEB8C6C8AFC9CBA5CDD299D5DA8FDCE387E1ED88E1F08A
  147.       DEF08BDCF189E0F987E0FDA7BFD0A47874A47874FF00FFFF00FFFF00FFFF00FF
  148.       FF00FFFF00FFA47874AC8D8BAEC6CA8BF4FC85F8FF8AEFFB8BF0FC8BF2FD8DF3
  149.       FF8DF4FF8EF1FE8FEDFD89F0FF90E3F7AFBBC6A47874FF00FFFF00FFFF00FFFF
  150.       00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFA47874C1AAAB9DDCE285FFFF
  151.       8BF8FF8DF3FF8DF3FF8DF3FF8AF7FF87FAFF9ED6E2C1A8ABA47874FF00FFFF00
  152.       FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFA4
  153.       7874A47874ADC9CD90F0F887FCFF87FCFF88FAFF8EF2FCADC9CDA47874A47874
  154.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
  155.       FFFF00FFFF00FFFF00FFFF00FFA47874A47874B1C0C3AACACFB2BFC1A47874A4
  156.       7874FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
  157.       FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFA47874A478
  158.       74A47874FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF
  159.       00FF
  160.     }
  161.     Transparent = True
  162.   end
  163.   object RadioButton2: TRadioButton
  164.     Left = 96
  165.     Height = 19
  166.     Top = 496
  167.     Width = 59
  168.     Caption = 'Sprite 2'
  169.     TabOrder = 0
  170.   end
  171. end

I will take some time to think about what you said in your post Thaddy and hopefully I can fully understand it and try and come up with a solution tomorrow.

Thanks :)
« Last Edit: May 14, 2017, 12:26:20 am by knuckles »

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #4 on: May 14, 2017, 05:23:20 am »
I updated your code, but it's not finished yet. I'm busy at the moment.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.DeleteLastSprite;
  2. begin
  3.   if High(FSprites) < 0 then Exit;
  4.   with FSprites[High(FSprites)] do
  5.   begin
  6.     imgCanvas.Canvas.Brush.Color := clBlack;;
  7.     imgCanvas.Canvas.Rectangle(X, Y, X+W, Y+H);
  8.   end;
  9.   SetLength(FSprites, Length(FSprites) - 1);
  10.   UpdateInfoCount;
  11.  
  12.   //
  13.   //  Todo: Need to check and redraw other affected sprites
  14.   //
  15.  
  16. end;

You can download and test the attached code.

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #5 on: May 14, 2017, 03:51:33 pm »
Great Handoko thanks for helping out :)

I added a function to get the sprite underneath the mouse if it is of any use:

Code: Pascal  [Select][+][-]
  1. TSprite = record
  2.   ID: Integer;
  3.   ...


Code: Pascal  [Select][+][-]
  1. procedure TForm1.DrawSprite(ASprite: TBitmap; AX, AY: Integer);
  2. begin
  3.   ...
  4.   FSprites[High(FSprites)].ID := Length(FSprites);
  5.   ...
  6. end;


Code: Pascal  [Select][+][-]
  1. function TForm1.GetSprite(AX, AY: Integer): TSprite;
  2. var
  3.   I: Integer;
  4.   SpriteRect: TRect;
  5. begin
  6.   for I := Length(FSprites) -1 downto 0 do
  7.   begin
  8.     SpriteRect.Left := FSprites[I].X;
  9.     SpriteRect.Top := FSprites[I].Y;
  10.     SpriteRect.Right := SpriteRect.Left + FSprites[I].Bitmap.Width;
  11.     SpriteRect.Bottom := SpriteRect.Top + FSprites[I].Bitmap.Height;
  12.     if PtInRect(SpriteRect, Point(AX, AY)) then
  13.     begin
  14.       Result := FSprites[I];
  15.       Break;
  16.     end;
  17.   end;
  18. end;

Example:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.imgCanvasMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  2. var
  3.   Sprite: TSprite;
  4. begin
  5.   Sprite := GetSprite(X, Y);
  6.   Caption := 'Sprite: ' + IntToStr(Sprite.ID);
  7.   ...
  8. end;

That code could also be updated if it is useful as I noticed you added W and H property to our sprite record so there is no longer any need to get the size information from the bitmap as I have done above.

EDIT: To avoid any confusion I had renamed the original GetSprite function to GetSpriteBitmap
« Last Edit: May 14, 2017, 05:14:02 pm by knuckles »

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #6 on: May 14, 2017, 11:15:37 pm »
Ok, im probably not doing a very good job here and maybe using a object list would have been better than an array (i am not as useful working with arrays)

I attempted to update your code Handoko, it now deletes the sprite under the mouse (although the elements need deleting from the FSprites array too, currently I am unsure how to solve that).

It is a small update but slowly we can try and bring everything together.

I assume to make the main bitmap undisturbed we can somehow use copyrect, maybe this is not the same as what Thaddy mentioned but perhaps the principle is the same maybe?

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #7 on: May 15, 2017, 09:17:50 am »
My suggestion is like this:

For example, the sprite #2 will be deleted, so:

1. Detect which sprites are affected. Using bounding box trick, the result is #1 and #3.
2. Draw sprite #1 and #3 on a virtual screen.
3. Copy the virtual screen exactly the bounding box size and position back to the screen.
4. Because sprite #4 is outside the bounding box, so it will be ignored.

This trick should has less performance impact when the sprites count increasing a lot. Because usually overlapping sprites are not much.

We should make sure the drawing on the virtual screen and copying back process are optimized for performance.
« Last Edit: May 15, 2017, 09:26:56 am by Handoko »

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #8 on: May 15, 2017, 10:55:50 am »
Such programs simply simulate how Windows UI itself works. It's not that hard. Something like:

1) Just make list of sprites, stored in Z order.
2) When user clicks - test all sprites in this list till hit.
3) To determine hit - first perform bounding box test. Something like "if Sprite.Rect.Contains(ClickPoint) then...".
4) If necessary, perform some extra hit test - such alpha test in order to exclude transparent pixels. Something like "if Sprite.HitTest(ClickPoint)".
5) If it's hit - perform some actions.
6) If you need to redraw canvas - just draw sprites in reversed order. You may do it in direct order, but you'd need Z Buffer then.
« Last Edit: May 15, 2017, 11:04:51 am by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #9 on: May 15, 2017, 11:11:12 am »
knuckles has implemented your point 1 and 2. Now he should continue to the step 3 - the bounding box test.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #10 on: May 15, 2017, 03:05:09 pm »
My example:
Code: Pascal  [Select][+][-]
  1. unit TestMain;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, fgl;
  9.  
  10. type
  11.  
  12.   { TSpriteTestForm }
  13.  
  14.   TSprite = class
  15.   protected
  16.     FBitmap:TBitmap;
  17.   public
  18.     Rect:TRect;
  19.     Highlighted:Boolean;
  20.     constructor Create(ABitmap:TBitmap;AIndex:Integer;ARect:TRect);
  21.     destructor Destroy;override;
  22.     procedure Draw(ACanvas:TCanvas);
  23.     function HitTest(APoint:TPoint):Boolean;
  24.     property Bitmap:TBitmap read FBitmap;
  25.   end;
  26.  
  27.   TSpriteList = specialize TFPGList<TSprite>;
  28.  
  29.   TSpriteTestForm = class(TForm)
  30.     SpriteImage: TImage;
  31.     procedure FormCreate(Sender: TObject);
  32.     procedure FormDestroy(Sender: TObject);
  33.     procedure SpriteImageMouseDown(Sender: TObject; Button: TMouseButton;
  34.       Shift: TShiftState; X, Y: Integer);
  35.     procedure SpriteImageMouseMove(Sender: TObject; Shift: TShiftState; X,
  36.       Y: Integer);
  37.     procedure SpriteImageMouseUp(Sender: TObject; Button: TMouseButton;
  38.       Shift: TShiftState; X, Y: Integer);
  39.   private
  40.     { private declarations }
  41.   public
  42.     { public declarations }
  43.     Drag:Boolean;
  44.     DragSprite:TSprite;
  45.     DragStart:TPoint;
  46.     SpriteStart:TRect;
  47.     SpriteBitmap:TBitmap;
  48.     SpriteList:TSpriteList;
  49.     procedure UpdateRect;
  50.   end;
  51.  
  52. var
  53.   SpriteTestForm: TSpriteTestForm;
  54.  
  55. implementation
  56.  
  57. {$R *.lfm}
  58.  
  59. { TSpriteTestForm }
  60.  
  61.  
  62. procedure TSpriteTestForm.FormCreate(Sender: TObject);
  63.   var Bitmap:TBitmap;
  64. begin
  65.   Randomize;
  66.   Bitmap := TBitmap.Create;
  67.   Bitmap.Width := 640;
  68.   Bitmap.Height := 480;
  69.   Bitmap.Canvas.Brush.Color := clGreen;
  70.   Bitmap.Canvas.FillRect(0, 0, 640, 480);
  71.   SpriteImage.Picture.Bitmap := Bitmap;
  72.   SpriteBitmap := TBitmap.Create;
  73.   SpriteBitmap.LoadFromFile('MS.bmp');
  74.   SpriteList := TSpriteList.Create;
  75. end;
  76.  
  77. procedure TSpriteTestForm.FormDestroy(Sender: TObject);
  78.   var I:Integer;
  79. begin
  80.   SpriteBitmap.Free;
  81.   for I := 0 to SpriteList.Count - 1 do begin
  82.     SpriteList[I].Free;
  83.   end;
  84.   SpriteList.Free;
  85. end;
  86.  
  87. procedure TSpriteTestForm.SpriteImageMouseDown(Sender: TObject;
  88.   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  89.   var Sprite:TSprite;I:Integer;
  90. begin
  91.   if Button = mbRight then begin
  92.     Sprite := TSprite.Create(SpriteBitmap, Random((12 * 8) - 1), TRect.Create(TPoint.Create(X - 40, Y - 40), 81, 81));
  93.     SpriteList.Insert(0, Sprite);
  94.     SpriteImageMouseMove(Sender, Shift, X, Y);
  95.   end;
  96.   DragSprite := nil;
  97.   Drag := False;
  98.   if Button = mbLeft then begin
  99.     for I := 0 to SpriteList.Count - 1 do begin
  100.       if SpriteList[I].HitTest(TPoint.Create(X, Y)) then begin
  101.         DragSprite := SpriteList[I];
  102.         DragStart := TPoint.Create(X, Y);
  103.         SpriteStart := SpriteList[I].Rect;
  104.         Break;
  105.       end;
  106.     end;
  107.   end;
  108. end;
  109.  
  110. procedure TSpriteTestForm.SpriteImageMouseMove(Sender: TObject;
  111.   Shift: TShiftState; X, Y: Integer);
  112.   var I:Integer;Found:Boolean;
  113.   Rect:TRect;Dist:TPoint;
  114. begin
  115.   Rect := TRect.Create(0, 0, 0, 0);
  116.   if Assigned(DragSprite) then begin
  117.     Dist := TPoint.Create(X, Y) - DragStart;
  118.     if not Drag then begin
  119.       if (Abs(Dist.X) > 5) or (Abs(Dist.Y) > 5) then begin
  120.         Drag := True;
  121.       end;
  122.     end
  123.     else begin
  124.       Rect.Union(DragSprite.Rect);
  125.       DragSprite.Rect := SpriteStart;
  126.       DragSprite.Rect.Offset(Dist);
  127.       Rect.Union(DragSprite.Rect);
  128.     end;
  129.   end;
  130.   Found := False;
  131.   for I := 0 to SpriteList.Count - 1 do begin
  132.     if Found then begin
  133.       if SpriteList[I].Highlighted then begin
  134.         SpriteList[I].Highlighted := False;
  135.         Rect.Union(SpriteList[I].Rect);
  136.       end;
  137.     end
  138.     else begin
  139.       if SpriteList[I].HitTest(TPoint.Create(X, Y)) then begin
  140.         if not SpriteList[I].Highlighted then begin
  141.           SpriteList[I].Highlighted := True;
  142.           Rect.Union(SpriteList[I].Rect);
  143.         end;
  144.         Found := True;
  145.       end
  146.       else begin
  147.         if SpriteList[I].Highlighted then begin
  148.           SpriteList[I].Highlighted := False;
  149.           Rect.Union(SpriteList[I].Rect);
  150.         end;
  151.       end;
  152.     end;
  153.   end;
  154.   UpdateRect;
  155. end;
  156.  
  157. procedure TSpriteTestForm.SpriteImageMouseUp(Sender: TObject;
  158.   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  159.   var I:Integer;
  160. begin
  161.   if Button = mbLeft then begin
  162.     if not Drag then begin
  163.       for I := 0 to SpriteList.Count - 1 do begin
  164.         if SpriteList[I].HitTest(TPoint.Create(X, Y)) then begin
  165.           SpriteList.Move(I, 0);
  166.         end;
  167.       end;
  168.       SpriteImageMouseMove(Sender, Shift, X, Y);
  169.     end;
  170.     DragSprite := nil;
  171.   end;
  172. end;
  173.  
  174. procedure TSpriteTestForm.UpdateRect;
  175.   var I:Integer;
  176. begin
  177.   with SpriteImage.Picture.Bitmap.Canvas do begin
  178.     Brush.Color := clGreen;
  179.     FillRect(ClipRect);
  180.   end;
  181.   for I := SpriteList.Count - 1 downto 0 do begin
  182.     SpriteList[I].Draw(SpriteImage.Picture.Bitmap.Canvas);
  183.   end;
  184. end;
  185.  
  186. constructor TSprite.Create(ABitmap:TBitmap;AIndex:Integer;ARect:TRect);
  187.   var Dest, Src:TRect;
  188. begin
  189.   Rect := ARect;
  190.   FBitmap := TBitmap.Create;
  191.   FBitmap.Width := 81;
  192.   FBitmap.Height := 81;
  193.   Src := TRect.Create(TPoint.Create(0, 0), 81, 81);
  194.   Dest := Src;
  195.   Src.Offset(5 + 81 * (AIndex mod 12), 5 + 81 * (AIndex div 12));
  196.   FBitmap.Canvas.CopyRect(Dest, ABitmap.Canvas, Src);
  197.   FBitmap.Mask(clBlack);
  198. end;
  199.  
  200. destructor TSprite.Destroy;
  201. begin
  202.   Bitmap.Free;
  203.   inherited Destroy;
  204. end;
  205.  
  206. procedure TSprite.Draw(ACanvas:TCanvas);
  207. begin
  208.   ACanvas.Draw(Rect.Left, Rect.Top, Bitmap);
  209.   if Highlighted then begin
  210.     ACanvas.DrawFocusRect(Rect);
  211.   end;
  212. end;
  213.  
  214. function TSprite.HitTest(APoint:TPoint):Boolean;
  215. begin
  216.   Result := False;
  217.   if Rect.Contains(APoint) then begin
  218.     APoint := APoint - Rect.TopLeft;
  219.     Result := Bitmap.Canvas.Pixels[APoint.X, APoint.Y] <> clBlack;
  220.   end;
  221. end;
  222.  
  223. end.
  224.  
Controls:
1) Mouse move - topmost sprite is highlighted
2) Left mouse click - bring sprite under cursor to front
3) Left click and drag >5 pixels - drag sprite under cursor
4) Right click - add new random sprite at mouse cursor

Clipping isn't supported, so drawing isn't as optimal, as I wanted it to be.  :( I wanted to implement explicit clipping, but canvas doesn't support it and now I don't have time to change my code.
« Last Edit: May 15, 2017, 03:17:39 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #11 on: May 15, 2017, 04:21:24 pm »
I will see if I can extend our test demo later on Handoko because if I am able to do it myself (with the help already provided) I will understand the concept and everything a bit more better.

Mr.Madguy thanks for contributing, I will use your pointers to also assist me and I will check out your solution later on.

Thanks all :)

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #12 on: May 15, 2017, 04:24:57 pm »
@Mr.Madguy

Your code works. You're really a master in graphics programming. I found a lot of new things to learn inside your code.

Thank you.

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #13 on: May 15, 2017, 04:31:48 pm »
There are a lot of things we can learn from Mr.Madguy's code. I managed to make it works but with some modification to load 32 x 32 image that I found from here:

https://opengameart.org/content/dungeon-crawl-32x32-tiles

Anyone who want to try the code, can download it here (the spritesheet image is included).

Controls:
1) Mouse move - topmost sprite is highlighted
2) Left mouse click - bring sprite under cursor to front
3) Left click and drag >5 pixels - drag sprite under cursor
4) Right click - add new random sprite at mouse cursor
« Last Edit: May 15, 2017, 04:48:09 pm by Handoko »

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #14 on: May 15, 2017, 05:37:17 pm »
Code is actually very clunky, because I had no time to create new control from scratch. In real situation new control should be created and all painting should be performed in it's Paint method. All updating should be performed indirectly - via invalidating regions. In this case Windows will handle all visibility detection. Another bad thing - Canvas supports implicit clipping only, i.e. setting ClipRect has no effect (and in Delphi it's documented as read-only property). I.e. no such things, as IntersectClipRect. That's why I have to update whole picture every time - I tried to use smart updating, but order of sprites was sometimes braking due to lack of explicit clipping. Whole sprites were update instead of only needed parts of them. As the result - some other sprites were affected too.

Graphics - is actually my specialization. Here is my best program: SmileManager. It uses my own component, that can animate unlimited (limited by amount of RAM only) amount of GIF files. I wanted to use idea of this component to create universal control framework, but it was too complex task and I gave up at some point...  %) But you should know, that it's very old - it was designed for WinXP, but actually aimed at still supporting Win9x.  ::) And due to this fact some things are broken on Vista+. For example "Add smile" dialog doesn't work. You need to drag GIF files from explorer directly to program window. Also quality of my code wasn't as good, as it's now - it was very clunky and ugly.  :D
« Last Edit: May 15, 2017, 05:54:15 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

 

TinyPortal © 2005-2018