Recent

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

Handoko

  • Hero Member
  • *****
  • Posts: 5154
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #15 on: May 15, 2017, 06:39:15 pm »
You used generic, which is the thing I haven't try. I know how to do BoundingBox test but I haven't known how to solve the alpha issue. In just hours, you can write a working code that solves several advanced problems. I salute you.

I can start your SmileManager on Linux. :D
Unfortunately nothing more it can do. The add smile feature didn't work and the drag gif file didn't work too. You built it in 2005? Wow, you now must be a very experienced programmer.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #16 on: May 15, 2017, 07:07:39 pm »
You used generic, which is the thing I haven't try. I know how to do BoundingBox test but I haven't known how to solve the alpha issue. In just hours, you can write a working code that solves several advanced problems. I salute you.

I can start your SmileManager on Linux. :D
Unfortunately nothing more it can do. The add smile feature didn't work and the drag gif file didn't work too. You built it in 2005? Wow, you now must be a very experienced programmer.
No, 2005 - is version of QIP. Program was build around 2007, if I remember correctly.

Strange. Add smile is broken - I know. It uses some crazy code to support both Unicode and Ansi in dialogs at the same time. It was time, when Unicode wasn't directly supported by Delphi, so some other ways of achieving this goal, like using TNT controls, should have been used. Does Win emulator on Linux support WM_DROPFILES? Does it have some sort of Win9x/WinXP compatibility mode? Unfortunately I can't recompile this program to fix bugs with dialogs, cuz some controls are missing.

Code is old, but it's very good. It's actually multithreaded. It uses 3 threads: timer thread, render thread and main thread. Due to this reason component should be very responsive even under high loads and on very slow machines.
« Last Edit: May 15, 2017, 07:21:38 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?

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #17 on: May 15, 2017, 07:41:45 pm »
Ahhh. I've found test utility for this component. Here it is. Unfortunately one of crappy anti-viruses on VirusTotal treats one of GIF files as OMG virus  :o, so I can't send it to you with my smile pack, cuz file hosting deletes my file in this case. You need to download it separately. Here for example. Then copy contents of Animated folder to MyChat\Smilies folder of my program. Don't change structure of folders - it's hardcoded. Does Win emulator on Linux support "Layered windows" feature (implemented in XP)? Without it you won't see, how cool animated drag and drop feature works.
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 #18 on: May 16, 2017, 01:09:49 am »
Very impressive Mr.Madguy :)

I tried doing this myself but could not come up with anything as good as yours, graphics programming is not a strong point of mine but I can look at your code and gain some understanding from it.

There is a slow down with lots of sprites and then moving but you have already explained this. I'm curious as to how it could be optimised, the old Game Maker editor seems to pull it off with hundreds of sprites on screen and moving one causes no slowdown, and that is done using the internal bitmap canvas of a TImage. I wonder how it was able to efficiently do that?

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #19 on: May 16, 2017, 06:59:15 am »
Very impressive Mr.Madguy :)

I tried doing this myself but could not come up with anything as good as yours, graphics programming is not a strong point of mine but I can look at your code and gain some understanding from it.

There is a slow down with lots of sprites and then moving but you have already explained this. I'm curious as to how it could be optimised, the old Game Maker editor seems to pull it off with hundreds of sprites on screen and moving one causes no slowdown, and that is done using the internal bitmap canvas of a TImage. I wonder how it was able to efficiently do that?
Yeah, that's because I can't update just one sprite. When I do it - I also need to update all sprites, visible in this region. But they can't be clipped and therefore their order against sprites, not visible in this region, also breaks. But I actually know, how to solve this problem. If I won't be busy today, I'll improve my program a little bit. May be even implement same algorithm via new control.
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #20 on: May 16, 2017, 10:23:06 am »
Yeah, here it is. That's how it should be implemented in real situation. Good thing - implicit clipping is now supported. Bad thing - I've found out, that not only explicit clipping isn't supported by Canvas, but partial invalidation isn't supported either, so it's dead lock - WinAPI InvalidateRect has to be used to achieve desired performance. That means - no optimization for Linux.  :'( It's still possible to implement partial updating on Linux, but it has to be performed manually.

Hit detection is still "raw", i.e. designed for worst case scenario, where all sprites can have arbitrary positions. In order to further improve performance, hit detection should take specifics of specific task into account, such as whether some sprites can overlap (foreground sprites) or not (background ones). Also, if you have enough memory, you can use the same technique, that is used in modern 3D games - selection buffer.

Main program:
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, SpriteControl;
  9.  
  10. type
  11.  
  12.   { TSpriteTestForm }
  13.  
  14.   TSpriteTestForm = class(TForm)
  15.     procedure FormCreate(Sender: TObject);
  16.     procedure FormDestroy(Sender: TObject);
  17.     procedure SpriteControlMouseUp(Sender: TObject; Button: TMouseButton;
  18.       Shift: TShiftState; X, Y: Integer);
  19.   private
  20.     { private declarations }
  21.   public
  22.     { public declarations }
  23.     SpriteControl:TSpriteControl;
  24.     SpriteBitmap:TBitmap;
  25.     function CreateSprite(AIndex:Integer):TBitmap;
  26.   end;
  27.  
  28. var
  29.   SpriteTestForm: TSpriteTestForm;
  30.  
  31. implementation
  32.  
  33. {$R *.lfm}
  34.  
  35. { TSpriteTestForm }
  36.  
  37. procedure TSpriteTestForm.FormCreate(Sender: TObject);
  38. begin
  39.   Randomize;
  40.   SpriteBitmap := TBitmap.Create;
  41.   SpriteBitmap.LoadFromFile('Sprites.bmp');
  42.   SpriteControl := TSpriteControl.Create(Self);
  43.   with SpriteControl do begin
  44.     Parent := Self;
  45.     OnMouseUp := @SpriteControlMouseUp;
  46.     Align := alClient;
  47.     Bitmap.Width := 640;
  48.     Bitmap.Height := 480;
  49.     BackgroundColor := clGreen;
  50.   end;
  51. end;
  52.  
  53. procedure TSpriteTestForm.FormDestroy(Sender: TObject);
  54. begin
  55.   SpriteBitmap.Free;
  56. end;
  57.  
  58. procedure TSpriteTestForm.SpriteControlMouseUp(Sender: TObject; Button: TMouseButton;
  59.   Shift: TShiftState; X, Y: Integer);
  60. begin
  61.   if Button = mbRight then begin
  62.     SpriteControl.AddSprite(TSprite.Create(CreateSprite(Random(12 * 8 - 1)), TPoint.Create(X - 40, Y - 40)));
  63.   end;
  64. end;
  65.  
  66. function TSpriteTestForm.CreateSprite(AIndex:Integer):TBitmap;
  67.   var Src, Dest:TRect;
  68. begin
  69.   Result := nil;
  70.   if (AIndex >= 0) and (AIndex < 12 * 8) then begin
  71.     Result := TBitmap.Create;
  72.     with Result do begin
  73.       Width := 81;
  74.       Height := 81;
  75.       Dest := TRect.Create(TPoint.Create(0, 0), 81, 81);
  76.       Src := Dest;
  77.       Src.Offset(5 + 81 * (AIndex mod 12), 5 + 81 * (AIndex div 12));
  78.       Canvas.CopyRect(Dest, SpriteBitmap.Canvas, Src);
  79.       Transparent := True;
  80.       TransparentColor := clBlack;
  81.       Mask(clBlack);
  82.     end;
  83.   end;
  84. end;
  85.  
  86. end.
  87.  

And sprite control:
Code: Pascal  [Select][+][-]
  1. unit SpriteControl;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses {$ifdef windows}Windows,{$endif} Classes, Controls, Graphics, Fgl;
  8.  
  9. type
  10.   TSprite = class
  11.   protected
  12.     FBitmap:TBitmap;
  13.   public
  14.     Rect:TRect;
  15.     constructor Create(ABitmap:TBitmap;ACoord:TPoint);
  16.     destructor Destroy;override;
  17.     procedure Draw(ACanvas:TCanvas;ARect:TRect);
  18.     procedure Move(APoint:TPoint);
  19.     function HitTest(APoint:TPoint):Boolean;
  20.     property Bitmap:TBitmap read FBitmap;
  21.   end;
  22.  
  23.   TSpriteList = specialize TFPGObjectList<TSprite>;
  24.  
  25.   TSpriteControl = class(TCustomControl)
  26.   protected
  27.     FBitmap:TBitmap;
  28.     FSprites:TSpriteList;
  29.     FSpriteUnderCursor:TSprite;
  30.     FUpdateCount:Integer;
  31.     FClickedSprite:TSprite;
  32.     FClickedPoint:TPoint;
  33.     FClickedCoord:TPoint;
  34.     FDragging:Boolean;
  35.     procedure Paint;override;
  36.     procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer);override;
  37.     procedure MouseMove(Shift: TShiftState; X,Y: Integer);override;
  38.     procedure MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer);override;
  39.     procedure SetSpriteUnderCursor(ASprite:TSprite);
  40.   public
  41.     BackgroundColor:TColor;
  42.     constructor Create(AOwner: TComponent);override;
  43.     destructor Destroy;override;
  44.     procedure AddSprite(ASprite:TSprite);
  45.     procedure UpdateSprite(ASprite:TSprite);
  46.     property Bitmap:TBitmap read FBitmap;
  47.     property Sprites:TSpriteList read FSprites;
  48.     property SpriteUnderCursor:TSprite read FSpriteUnderCursor write SetSpriteUnderCursor;
  49.   end;
  50.  
  51.  
  52. implementation
  53.  
  54. uses SysUtils;
  55.  
  56. constructor TSprite.Create(ABitmap:TBitmap;ACoord:TPoint);
  57. begin
  58.   inherited Create;
  59.   FBitmap := ABitmap;
  60.   Rect := TRect.Create(ACoord, Bitmap.Width, Bitmap.Height);
  61. end;
  62.  
  63. destructor TSprite.Destroy;
  64. begin
  65.   FBitmap.Free;
  66.   inherited Destroy;
  67. end;
  68.  
  69. procedure TSprite.Draw(ACanvas:TCanvas;ARect:TRect);
  70. begin
  71.   if not TRect.Intersect(ARect, Rect).IsEmpty then begin
  72.     ACanvas.Draw(Rect.Left, Rect.Top, Bitmap);
  73.   end;
  74. end;
  75.  
  76. procedure TSprite.Move(APoint:TPoint);
  77. begin
  78.   Rect := TRect.Create(APoint, Bitmap.Width, Bitmap.Height);
  79. end;
  80.  
  81. function TSprite.HitTest(APoint:TPoint):Boolean;
  82. begin
  83.   Result := Rect.Contains(APoint);
  84.   if Result and Bitmap.Transparent then begin
  85.     APoint := APoint - Rect.TopLeft;
  86.     Result := Bitmap.Canvas.Pixels[APoint.X, APoint.Y] <> Bitmap.TransparentColor;
  87.   end;
  88. end;
  89.  
  90. constructor TSpriteControl.Create(AOwner: TComponent);
  91. begin
  92.   inherited Create(AOwner);
  93.   FBitmap := TBitmap.Create;
  94.   FSprites := TSpriteList.Create;
  95.   FUpdateCount := 1;
  96. end;
  97.  
  98. destructor TSpriteControl.Destroy;
  99. begin
  100.   FBitmap.Free;
  101.   FSprites.Free;
  102.   inherited Destroy;
  103. end;
  104.  
  105. procedure TSpriteControl.AddSprite(ASprite:TSprite);
  106. begin
  107.   Sprites.Insert(0, ASprite);
  108.   UpdateSprite(ASprite);
  109. end;
  110.  
  111. procedure TSpriteControl.UpdateSprite(ASprite:TSprite);
  112. begin
  113.   {$ifdef windows}
  114.     InvalidateRect(Handle, ASprite.Rect, False);
  115.   {$else}
  116.     Invalidate;
  117.   {$endif}
  118.   Inc(FUpdateCount);
  119. end;
  120.  
  121. procedure TSpriteControl.Paint;
  122.   var Rect, ClipRect, PaintRect:TRect;
  123.   I:Integer;Sprite:TSprite;
  124.   FocusSprite:TSprite;
  125. begin
  126.   inherited Paint;
  127.   Rect := TRect.Create(TPoint.Create(0, 0), Bitmap.Width, Bitmap.Height);
  128.   ClipRect := Canvas.ClipRect;
  129.   PaintRect := Rect * ClipRect;
  130.   if FUpdateCount > 0 then begin
  131.     with Bitmap.Canvas do begin
  132.       Brush.Color := BackgroundColor;
  133.       FillRect(PaintRect);
  134.     end;
  135.     FocusSprite := nil;
  136.     for I := Sprites.Count - 1 downto 0 do begin
  137.       Sprite := Sprites[I];
  138.       Sprite.Draw(Bitmap.Canvas, PaintRect);
  139.       if Sprite = SpriteUnderCursor then begin
  140.         FocusSprite := Sprite;
  141.       end;
  142.     end;
  143.     if Assigned(FocusSprite) then begin
  144.       with Bitmap.Canvas do begin
  145.         DrawFocusRect(FocusSprite.Rect);
  146.       end;
  147.     end;
  148.     FUpdateCount := 0;
  149.   end;
  150.   Canvas.CopyRect(PaintRect, Bitmap.Canvas, PaintRect);
  151.   if not PaintRect.Contains(ClipRect) then begin
  152.     Canvas.Brush.Color := Color;
  153.     Rect := ClientRect;
  154.     Rect.Left := Bitmap.Width;
  155.     Rect.Intersect(ClipRect);
  156.     Canvas.FillRect(Rect);
  157.     Rect := ClientRect;
  158.     Rect.Top := Bitmap.Height;
  159.     Rect.Width := Bitmap.Width;
  160.     Rect.Intersect(ClipRect);
  161.     Canvas.FillRect(Rect);
  162.   end;
  163. end;
  164.  
  165. procedure TSpriteControl.MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer);
  166.   var I:Integer;Sprite, HitSprite:TSprite;
  167. begin
  168.   inherited MouseDown(Button, Shift, X, Y);
  169.   if Button = mbLeft then begin
  170.     HitSprite := nil;
  171.     for I := 0 to Sprites.Count - 1 do begin
  172.       Sprite := Sprites[I];
  173.       if Sprite.HitTest(TPoint.Create(X, Y)) then begin
  174.         HitSprite := Sprite;
  175.         FClickedPoint := TPoint.Create(X, Y);
  176.         FClickedCoord := Sprite.Rect.TopLeft;
  177.         Break;
  178.       end;
  179.     end;
  180.     FClickedSprite := HitSprite;
  181.   end;
  182. end;
  183.  
  184. procedure TSpriteControl.MouseMove(Shift: TShiftState; X,Y: Integer);
  185.   var I:Integer;Sprite, HitSprite:TSprite;
  186. begin
  187.   inherited MouseMove(Shift, X, Y);
  188.   if Assigned(FClickedSprite) then begin
  189.     if not FDragging then begin
  190.       if (Abs(X - FClickedPoint.X) > 5) or (Abs(Y - FClickedPoint.Y) > 5) then begin
  191.         FDragging := True;
  192.       end;
  193.     end;
  194.     if FDragging then begin
  195.       UpdateSprite(FClickedSprite);
  196.       FClickedSprite.Move(FClickedCoord + TPoint.Create(X, Y) - FClickedPoint);
  197.       UpdateSprite(FClickedSprite);
  198.       SpriteUnderCursor := FClickedSprite;
  199.       Exit;
  200.     end;
  201.   end;
  202.   HitSprite := nil;
  203.   for I := 0 to Sprites.Count - 1 do begin
  204.     Sprite := Sprites[I];
  205.     if Sprite.HitTest(TPoint.Create(X, Y)) then begin
  206.       HitSprite := Sprite;
  207.     end;
  208.   end;
  209.   SpriteUnderCursor := HitSprite;
  210. end;
  211.  
  212. procedure TSpriteControl.MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer);
  213. begin
  214.   inherited MouseUp(Button, Shift, X, Y);
  215.   if Button = mbLeft then begin
  216.     if FDragging then begin
  217.       FDragging := False;
  218.     end
  219.     else begin
  220.       if Assigned(FClickedSprite) then begin
  221.         Sprites.Move(Sprites.IndexOf(FClickedSprite), 0);
  222.         UpdateSprite(FClickedSprite);
  223.       end;
  224.     end;
  225.     FClickedSprite := nil;
  226.   end;
  227. end;
  228.  
  229. procedure TSpriteControl.SetSpriteUnderCursor(ASprite:TSprite);
  230.   var OldSprite:TSprite;
  231. begin
  232.   if SpriteUnderCursor <> ASprite then begin
  233.     OldSprite := SpriteUnderCursor;
  234.     FSpriteUnderCursor := ASprite;
  235.     if Assigned(OldSprite) then begin
  236.       UpdateSprite(OldSprite);
  237.     end;
  238.     if Assigned(SpriteUnderCursor) then begin
  239.       UpdateSprite(SpriteUnderCursor);
  240.     end;
  241.   end;
  242. end;
  243.  
  244. end.
  245.  
« Last Edit: May 16, 2017, 12:40:01 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 #21 on: May 16, 2017, 03:15:38 pm »
Excellent work Mr.Madguy  ;D 8-)

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #22 on: May 16, 2017, 03:54:58 pm »
There was a time, when I was really interested in gamedev. And games - are even more complex, cuz there you have to also deal with timers, animations, controls, sounds and game logic. I even tried to make some games via GDI, but then realized, that it's way to slow for it.
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 #23 on: May 16, 2017, 04:09:15 pm »
Yes GDI is not great for graphics and game performance, hardware acceleration is the way to go, but this is why I was curious as to how Game Maker could draw and move sprites rather fast on a TImage canvas and of course not disturb the main image, I assume your techninque is not too different to how Game Maker achieved it?

Handoko

  • Hero Member
  • *****
  • Posts: 5154
  • My goal: build my own game engine using Lazarus
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #24 on: May 16, 2017, 04:40:02 pm »
I think those non-hardware accelerated 2D games use their own graphics engine. Those classic 2D game developers should had developed their own graphics library before OpenGL/DirectX become famous.

Have you consider about BGRABitmap or Graphics32? Here I quote their description:

Quote
BGRABitmap is a set of units designed to modify and create images with transparency (alpha channel). Direct pixel access allows fast image processing.

Quote
Graphics32 is a graphics library for Delphi and Lazarus. Optimized for 32-bit pixel formats, it provides fast operations with pixels and graphic primitives. In most cases Graphics32 considerably outperforms the standard TBitmap/TCanvas methods.

They both mentioned using fast/direct pixel access.

I wrote simple games for my 8088 machine. Using BIOS graphics functions were too slow. So I wrote my own graphics module using direct mapping to the video buffer. Not very fast (because that cpu is extremely slow) but much better than Turbo Basic default drawing functions and BIOS graphics functions.
« Last Edit: May 16, 2017, 04:47:34 pm by Handoko »

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #25 on: May 16, 2017, 06:29:01 pm »
Yes GDI is not great for graphics and game performance, hardware acceleration is the way to go, but this is why I was curious as to how Game Maker could draw and move sprites rather fast on a TImage canvas and of course not disturb the main image, I assume your techninque is not too different to how Game Maker achieved it?
Only consoles, like NES, had hardware sprite support. I.e. when you could move sprites completely independently from background. All, you needed to do - to set up some PPU registers. No manual drawing at all. That's why consoles with 8bit 1Mhz processors had better games, than IBM PC. Because CPU driven graphics on 4-plane 1 bit per pixel EGA video adapters - was just complete nightmare.

And since then all graphic libraries, be it GDI, DirectDraw, Direct3D, OpenGL or even simply direct writing to video memory - work exactly the same way. What way? You have to redraw whole picture from scratch every time, you need to update it. And as every new sprite overwrites all previous data - you have to do it in back to front order. Remember Paint? PhotoShop or any other raster image editor? Any graphic application works exactly the same way. That's it. The only difference between different methods - is how you optimize this process to minimize redrawing to areas, that really need it.
I think those non-hardware accelerated 2D games use their own graphics engine. Those classic 2D game developers should had developed their own graphics library before OpenGL/DirectX become famous.

Have you consider about BGRABitmap or Graphics32? Here I quote their description:

Quote
BGRABitmap is a set of units designed to modify and create images with transparency (alpha channel). Direct pixel access allows fast image processing.

Quote
Graphics32 is a graphics library for Delphi and Lazarus. Optimized for 32-bit pixel formats, it provides fast operations with pixels and graphic primitives. In most cases Graphics32 considerably outperforms the standard TBitmap/TCanvas methods.

They both mentioned using fast/direct pixel access.

I wrote simple games for my 8088 machine. Using BIOS graphics functions were too slow. So I wrote my own graphics module using direct mapping to the video buffer. Not very fast (because that cpu is extremely slow) but much better than Turbo Basic default drawing functions and BIOS graphics functions.
I guess, this two libraries have software image processing? Mmm. You should understand, that software image processing isn't worth it. In all areas, where software image processing is faster, than GDI - it's better to use graphic libraries, like Direct3D or OpenGL.

GDI - is actually very high level abstraction layer around graphic driver. GDI is hardware accelerated. Bitmaps are stored in video memory for example. It's actually good for it's intended task - for UI. It's so slow just because it was designed with trying to be as hardware independent, as possible, in mind. Because, as you know, higher abstraction level is - higher it's overhead cost and therefore performance penalty. Remember Java?

Yeah. BIOS functions were slow just because they were altering just one bit in video memory (4-plane 1bpp EGA for example), but had to calculate it's address, bitmask to access it and set up video adapter registers for every single pixel. My library had to overcome this problem too. But still - horizontal moving of sprites and horizontal scrolling were limited to 8-pixel blocks only. It's actually very noticeable in some old DOS games - that horizontal scrolling is quantized.

I haven't actually finished this game - I wasn't experienced enough to implement V-Sync back then. Without it game was flickering when pages were switched and copying pages was way to slow - it was taking whole CPU time. And after trying to develop games for NES... NES is heaven for game development. Just heaven. My DOS game program is very big and complex - memory manager, file manager, graphic library, timer IRQ, keyboard IRQ, etc. And this small C program for NES achieves exactly the same goal + much easier animation, music and sound. It took two days since downloading NES dev tools till achieving same and even better functionality of my program on NES (shooting wasn't implemented in DOS version). Yeah, it uses some runtime library, but this library - is only thin layer around accessing CPU, PPU and APU registers directly.

P.S. I actually was trying to make NES Battle City-like game with graphics, similar to Z game. I was a kid back then and I really loved this two games and wanted to make something similar.
Code: C  [Select][+][-]
  1. #include "neslib.h"
  2.  
  3. static unsigned char pal, pad, spr, dir, state, pressed, shell;
  4. static unsigned char tank_x;
  5. static unsigned char tank_y;
  6. static unsigned char shell_x;
  7. static unsigned char shell_y;
  8.  
  9. static unsigned char palSprites[16]={
  10.         0x0f,0x00,0x10,0x20,
  11.         0x0f,0x01,0x11,0x21,
  12.         0x0f,0x2A,0x10,0x20,
  13.         0x0f,0x2A,0x10,0x20
  14. };
  15.  
  16. const unsigned char metaTank[4][4*9+1]=
  17. {
  18.         {
  19.                 0,      0,      0x00,   0,
  20.                 8,      0,      0x01,   0,
  21.                 16,     0,      0x02,   0,
  22.                 0,      8,      0x10,   0,
  23.                 8,      8,      0x11,   0,
  24.                 16,     8,      0x12,   0,
  25.                 0,      16,     0x20,   0,
  26.                 8,      16,     0x21,   0,
  27.                 16,     16,     0x22,   0,
  28.                 128
  29.         },
  30.         {
  31.                 0,      0,      0x03,   0,
  32.                 8,      0,      0x04,   0,
  33.                 16,     0,      0x05,   0,
  34.                 0,      8,      0x13,   0,
  35.                 8,      8,      0x14,   0,
  36.                 16,     8,      0x15,   0,
  37.                 0,      16,     0x23,   0,
  38.                 8,      16,     0x24,   0,
  39.                 16,     16,     0x25,   0,
  40.                 128
  41.         },
  42.         {
  43.                 0,      0,      0x06,   0,
  44.                 8,      0,      0x07,   0,
  45.                 16,     0,      0x08,   0,
  46.                 0,      8,      0x16,   0,
  47.                 8,      8,      0x17,   0,
  48.                 16,     8,      0x18,   0,
  49.                 0,      16,     0x26,   0,
  50.                 8,      16,     0x27,   0,
  51.                 16,     16,     0x28,   0,
  52.                 128
  53.         },
  54.         {
  55.                 0,      0,      0x09,   0,
  56.                 8,      0,      0x0A,   0,
  57.                 16,     0,      0x0B,   0,
  58.                 0,      8,      0x19,   0,
  59.                 8,      8,      0x1A,   0,
  60.                 16,     8,      0x1B,   0,
  61.                 0,      16,     0x29,   0,
  62.                 8,      16,     0x2A,   0,
  63.                 16,     16,     0x2B,   0,
  64.                 128
  65.         }
  66. };
  67.  
  68. const unsigned char metaStripe[4][4*9+1]=
  69. {
  70.         {
  71.                 0,      0,      0x30,   1,
  72.                 8,      0,      0x31,   1,
  73.                 16,     0,      0x32,   1,
  74.                 0,      8,      0x40,   1,
  75.                 8,      8,      0x41,   1,
  76.                 16,     8,      0x42,   1,
  77.                 0,      16,     0x50,   1,
  78.                 8,      16,     0x51,   1,
  79.                 16,     16,     0x52,   1,
  80.                 128
  81.         },
  82.         {
  83.                 0,      0,      0x33,   1,
  84.                 8,      0,      0x34,   1,
  85.                 16,     0,      0x35,   1,
  86.                 0,      8,      0x43,   1,
  87.                 8,      8,      0x44,   1,
  88.                 16,     8,      0x45,   1,
  89.                 0,      16,     0x53,   1,
  90.                 8,      16,     0x54,   1,
  91.                 16,     16,     0x55,   1,
  92.                 128
  93.         },
  94.         {
  95.                 0,      0,      0x36,   1,
  96.                 8,      0,      0x37,   1,
  97.                 16,     0,      0x38,   1,
  98.                 0,      8,      0x46,   1,
  99.                 8,      8,      0x47,   1,
  100.                 16,     8,      0x48,   1,
  101.                 0,      16,     0x56,   1,
  102.                 8,      16,     0x57,   1,
  103.                 16,     16,     0x58,   1,
  104.                 128
  105.         },
  106.         {
  107.                 0,      0,      0x39,   1,
  108.                 8,      0,      0x3A,   1,
  109.                 16,     0,      0x3B,   1,
  110.                 0,      8,      0x49,   1,
  111.                 8,      8,      0x4A,   1,
  112.                 16,     8,      0x4B,   1,
  113.                 0,      16,     0x59,   1,
  114.                 8,      16,     0x5A,   1,
  115.                 16,     16,     0x5B,   1,
  116.                 128
  117.         }
  118. };
  119.  
  120. void update_pal(void)
  121. {
  122.         palSprites[5] = pal;
  123.         palSprites[6] = pal + 0x10;
  124.         palSprites[7] = pal + 0x20;
  125.         pal_spr(palSprites);
  126. };
  127.  
  128. void main(void)
  129. {
  130.         pal = 1;
  131.         update_pal();
  132.         pal_bg(palSprites);
  133.        
  134.         vram_adr(NAMETABLE_A);
  135.         vram_fill(0x0F, 30 * 32);
  136.         vram_fill(0xAA, 8 * 8);
  137.                
  138.         ppu_on_all();
  139.        
  140.         music_play(0);
  141.        
  142.         pressed = 0;
  143.         dir = 0;
  144.         tank_x = 122;
  145.         tank_y = 207;
  146.         shell = 4;
  147.  
  148.         while(1)
  149.         {      
  150.                 ppu_wait_frame();
  151.                
  152.                 spr = 0;
  153.                
  154.                 oam_clear();
  155.                
  156.                 if (shell < 4) {
  157.                         if ((shell == 1 && shell_x >= 232) || (shell == 3 && shell_x <= 24)) {
  158.                                 shell = 4;
  159.                         };
  160.                         if (shell < 4) {
  161.                                 spr = oam_spr(shell_x, shell_y, 0x70 + shell, 1, spr);
  162.                                 spr = oam_spr(shell_x, shell_y, 0x60 + shell, 0, spr);
  163.                         };
  164.                         switch (shell) {
  165.                                 case 0:
  166.                                         (shell_y > 7 && shell_y < 207)?shell_y-=8:shell = 4;
  167.                                         break;
  168.                                 case 1:
  169.                                         (shell_x > 0 && shell_x < 232)?shell_x-=8:shell = 4;
  170.                                         break;
  171.                                 case 2:
  172.                                         (shell_y < 223 && shell_y > 24)?shell_y+=8:shell = 4;
  173.                                         break;
  174.                                 case 3:
  175.                                         (shell_x < 248 && shell_x > 24)?shell_x+=8:shell = 4;
  176.                                         break;
  177.                         }
  178.                 };
  179.                
  180.                 spr = oam_meta_spr(tank_x, tank_y, spr, metaStripe[dir]);      
  181.                 spr = oam_meta_spr(tank_x, tank_y, spr, metaTank[dir]);
  182.                
  183.                 pad = pad_poll(0);
  184.                
  185.                 state = 4;
  186.  
  187.                 if (pad & PAD_LEFT) {
  188.             state == 4?state = 1:state = 5;                    
  189.                 }
  190.                 if (pad & PAD_RIGHT) {
  191.             state == 4?state = 3:state = 5;
  192.                 }
  193.                 if (pad & PAD_UP) {
  194.             state == 4?state = 0:state = 5;
  195.                 }
  196.                 if (pad & PAD_DOWN) {
  197.             state == 4?state = 2:state = 5;
  198.                 }
  199.                 if (state < 4 && dir != state) {
  200.                         sfx_play(3, 3);
  201.                         dir = state;
  202.                 }
  203.                 switch (state) {
  204.                         case 0:
  205.                                 tank_y > 7?tank_y--:state = 6;
  206.                                 break;
  207.                         case 1:
  208.                                 tank_x > 0?tank_x--:state = 6;
  209.                                 break;
  210.                         case 2:
  211.                                 tank_y < 207?tank_y++:state = 6;
  212.                                 break;
  213.                         case 3:
  214.                                 tank_x < 232?tank_x++:state = 6;
  215.                                 break;
  216.                 };     
  217.                 if (state == 6) sfx_play(0, 0);
  218.                 if (pad & PAD_A) {
  219.                         if (!(pressed & 0x01)) {
  220.                                 sfx_play(2, 2);
  221.                                 pal++;
  222.                                 if ((pal & 0x0F) == 0x0D) {
  223.                                         pal += 2;
  224.                                 } else {
  225.                                         pal &= 0x1F;
  226.                                 };
  227.                                 update_pal();
  228.                                 pressed |= 0x01;
  229.                         }
  230.                 } else {
  231.                         pressed &= 0xFE;
  232.                 };
  233.                 if (pad & PAD_B) {
  234.                         if (!(pressed & 0x02)) {
  235.                                 if (shell == 4) {
  236.                                         sfx_play(1, 1);
  237.                                         shell = dir;                           
  238.                                         switch (shell) {
  239.                                                 case 0:
  240.                                                         shell_x = tank_x + 8;
  241.                                                         shell_y = tank_y - 8;
  242.                                                         break;
  243.                                                 case 1:
  244.                                                         shell_x = tank_x - 8;
  245.                                                         shell_y = tank_y + 8;
  246.                                                         break;
  247.                                                 case 2:
  248.                                                         shell_x = tank_x + 8;
  249.                                                         shell_y = tank_y + 24;
  250.                                                         break;
  251.                                                 case 3:
  252.                                                         shell_x = tank_x + 24;
  253.                                                         shell_y = tank_y + 8;
  254.                                                         break;
  255.                                         };     
  256.                                 };
  257.                                 pressed |= 0x02;
  258.                         };
  259.                 } else {
  260.                         pressed &= 0xFD;
  261.                 }
  262.         }
  263. }
  264.  
« Last Edit: May 16, 2017, 06:51:33 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 #26 on: May 17, 2017, 12:08:28 am »
That stuff is really cool :)

What did you use for writing your code in C? I've always been interested in learning the C family, i tried C# but don't like the .Net stuff and not compiling to native, and C++ is way beyond me.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #27 on: May 17, 2017, 07:37:24 am »
That stuff is really cool :)

What did you use for writing your code in C? I've always been interested in learning the C family, i tried C# but don't like the .Net stuff and not compiling to native, and C++ is way beyond me.
I used cc65 and runtime libraries from shiru.

Yeah, NES has many restrictions, that are removed in SNES/Sega. Just 3 colors (plus one global backdrop color) per 16x16 background tile. Just 3 colors per sprite. Just 4 palettes for background and 4 palettes for sprites at the same time. Just 64 8x8 sprites. Just 8 sprites max on one scan line. But it's still just heaven in comparison with EGA programming. Yeah, there is ton of info about EGA/VGA programming in Internet (I had no access to Internet back then, but I had this amazing book, that covered literally everything about Intel processor architecture, all PC hardware, Asm programming and DOS), but there are no examples of real games. So I had to explore everything by myself. And some things, like V-Sync and flipping pages, aren't covered anywhere.

I.e. what do you have to do to scroll your level on PC? On modern computers you can redraw everything from scratch. Back in that old days CPUs were too weak to do it. You had to shift image via copying it. And even simple memory copying was too slow on my 12Mhz Intel 80286, just because you had to use movsb, that was twice slower, than movsw, just because video adapter latch register was just 8bit wide - it was taking whole CPU time in EGA 640x350 mode (that's why all old games usually used 320x200, besides of having compatibility with VGA 8bpp mode). And what do you have to do on NES? Just 5 instructions:
Code: [Select]
bit PPUSTATUS
lda cam_position_x
sta PPUSCROLL
lda cam_position_y
sta PPUSCROLL
I.e. you can do it almost for free - no CPU time is spent on it. Yeah, that's, how true hardware acceleration works.  8-)

P.S. This idea constantly comes into my mind. May be old versions of Game Maker were using software rendering? I.e. Game Maker was rendering to memory image and using GDI just to draw it to window? It's totally possible. If I won't be busy - I'll show you an example. There was a time, when I wanted to make cross-platform game, that would have one single exe, working both on DOS and Windows. This game should have used software rendering and should have used GDI only as "video driver". I haven't made game, but I've made 24bit BMP viewer with simple dithering support instead. I'm not at home now, so I don't have it with me, otherwise I would show it to you.
« Last Edit: May 17, 2017, 10:19:47 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?

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #28 on: May 17, 2017, 02:17:48 pm »
What a great insight, you're clearly very talented and knowledgeable  8-)

knuckles

  • Full Member
  • ***
  • Posts: 122
Re: How to handle moving and deleting of objects drawn on a bitmap?
« Reply #29 on: May 17, 2017, 11:48:24 pm »
I was wondering how to also delete a sprite under the mouse as I couldn't work it out? %)

Also I think I noticed a bug, if lots of sprites are added very close together, when you resize the form the main bitmap changes and then just moving your mouse over the sprites again causes them to change again?

 

TinyPortal © 2005-2018