Recent

Author Topic: Sprite demo example  (Read 10572 times)

Ñuño_Martínez

  • Hero Member
  • *****
  • Posts: 1209
    • Burdjia
Re: Sprite demo example
« Reply #15 on: May 11, 2018, 02:09:19 pm »
Doh!  I should take a look to the example projects.  I've never visit them.  :-[
Are you interested in game programming? Join the Pascal Game Development community!
Also visit the Game Development Portal

Zath

  • Sr. Member
  • ****
  • Posts: 391
Re: Sprite demo example
« Reply #16 on: May 11, 2018, 02:30:02 pm »
wp changed the example code after my initial question.
Thanks for the breakdown on the code Handoko, very interesting.

I wondered what the delta and the 24*60*60 were about.

The auto move makes a very smooth move but manual with arrow keys etc. becomes juddery.
 

Handoko

  • Hero Member
  • *****
  • Posts: 5439
  • My goal: build my own game engine using Lazarus
Re: Sprite demo example
« Reply #17 on: May 11, 2018, 02:46:37 pm »
24*60*60 => 24 hours, 60 minutes, 60 seconds.

My guess (I could be wrong):
By multiplying it, the value will become integer (although the type of that variable is still double). But I think you can change that number to anything you like, it just affects the movement speed.
Correct me if I'm wrong.

Delta time is a technique used in animation and game to achieve constant object movement if the program run on different hardware. It uses the time difference between each painting event as the input to calculate the distance of the movement.

For example if the computer need 16 milliseconds to do a refresh (16 millisecond per refresh is ± 60 fps), the calculation can be x := x + 16 * speed;

But if the same program runs on a slow computer, which need 32 milliseconds to perform a refresh (which is twice as slow as the previous one), the calculation becomes x := x + 32 * speed;

If you pay attention on calculations above, you will notice both gives the same results after a same period of time.
« Last Edit: May 11, 2018, 03:09:52 pm by Handoko »

Zath

  • Sr. Member
  • ****
  • Posts: 391
Re: Sprite demo example
« Reply #18 on: May 11, 2018, 03:42:53 pm »
Yes, I assume that number is time too.
Interesting ideas here.
Keeping the smoothness of scrolling with manual control would be nice.


Handoko

  • Hero Member
  • *****
  • Posts: 5439
  • My goal: build my own game engine using Lazarus
Re: Sprite demo example
« Reply #19 on: May 11, 2018, 05:58:40 pm »
I downloaded the demo from rev. 57802 and added some new features:
- arrow keys (smooth)
- arrow keys with inertia

The arrow keys (smooth) is similar to the older version of arrow keys, but it uses GetKeyState (unit LCLIntf) to avoid keyboard repeat delay. See line #222..#228. To make sure it works correctly I need to add line #132.

I added 2 new fields FXIntertia and FYIntertia for the inertia calculation. See line #51, #52, #229..#245.

Both arrow keys (smooth) and arrow keys with inertia require a timer to works properly, that's why I modified line #123.

For you information to make it easy to understand I do not use Delay Time in the calculations.

This is the modified version of the sprites demo:

Code: Pascal  [Select][+][-]
  1. unit PlayGround;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
  9.   LMessages, LCLType, LCLIntf, ExtCtrls, StdCtrls;
  10.  
  11. type
  12.  
  13.   { TPictureControl }
  14.  
  15.   TPictureControl = class(TCustomControl)
  16.     procedure PictureChanged(Sender: TObject);
  17.   private
  18.     FPicture: TPicture;
  19.     procedure SetPicture(const AValue: TPicture);
  20.     procedure WMEraseBkgnd(var Msg: TLMessage); message LM_ERASEBKGND;
  21.   protected
  22.   public
  23.     constructor Create(TheOwner: TComponent); override;
  24.     destructor Destroy; override;
  25.     procedure Paint; override;
  26.   published
  27.     property Picture: TPicture read FPicture write SetPicture;
  28.     property OnMouseMove;
  29.     property OnMouseDown;
  30.     property OnMouseUp;
  31.     property OnKeyDown;
  32.     property OnKeyUp;
  33.     property OnKeyPress;
  34.   end;
  35.  
  36.   { TPlayGroundForm }
  37.  
  38.   TPlayGroundForm = class(TForm)
  39.     ComboBox1: TComboBox;
  40.     Label1: TLabel;
  41.     Panel1: TPanel;
  42.     Panel2: TPanel;
  43.     Timer1: TTimer;
  44.     procedure ComboBox1Change(Sender: TObject);
  45.     procedure PlayGroundFormClose(Sender: TObject; var CloseAction: TCloseAction);
  46.     procedure PlayGroundFormCreate(Sender: TObject);
  47.     procedure PlayGroundFormDestroy(Sender: TObject);
  48.     procedure PictureControlKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  49.     procedure Timer1Timer(Sender: TObject);
  50.   private
  51.     FXIntertia: Double;
  52.     FYIntertia: Double;
  53.     FSpritePos: TPoint;
  54.     FSpritePosChange: TPoint;
  55.     FSpritePosInit: Boolean;
  56.     procedure UpdateImage;
  57.   public
  58.     PictureControl: TPictureControl;
  59.     SpriteImg: TCustomBitmap;
  60.     BackgroundImg: TCustomBitmap;
  61.     BufferImg: TCustomBitmap;
  62.   end;
  63.  
  64. var
  65.   PlayGroundForm: TPlayGroundForm;
  66.  
  67. implementation
  68.  
  69. {$R playground.lfm}
  70.  
  71. uses
  72.   Math;
  73.  
  74. { TPlayGroundForm }
  75.  
  76. procedure TPlayGroundForm.PlayGroundFormCreate(Sender: TObject);
  77. begin
  78.   PictureControl:=TPictureControl.Create(Self);
  79.   with PictureControl do begin
  80.     Parent:=Panel1; //Self;
  81.     Align:=alClient;
  82.     OnKeyDown := @PictureControlKeyDown;
  83.   end;
  84.  
  85.   SpriteImg:=TPortableNetworkGraphic.Create;
  86.   BackgroundImg:=TPortableNetworkGraphic.Create;
  87.   BufferImg:=TBitmap.Create;
  88.  
  89.   SpriteImg.LoadFromFile(SetDirSeparators('../../images/ide_icon48x48.png'));
  90.   BackgroundImg.LoadFromFile(SetDirSeparators('../../images/splash_logo.png'));
  91.   BufferImg.Width:=BackgroundImg.Width;
  92.   BufferImg.Height:=BackgroundImg.Height;
  93.  
  94.   FXIntertia := 0;
  95.   FYIntertia := 0;
  96.  
  97.   Timer1.Enabled := Combobox1.ItemIndex = 0;
  98.  
  99.   UpdateImage;
  100. end;
  101.  
  102. procedure TPlayGroundForm.PlayGroundFormClose(Sender: TObject;
  103.   var CloseAction: TCloseAction);
  104. begin
  105.   Timer1.Enabled:=false;
  106. end;
  107.  
  108. procedure TPlayGroundForm.PlayGroundFormDestroy(Sender: TObject);
  109. begin
  110.   SpriteImg.Free;
  111.   BackgroundImg.Free;
  112.   BufferImg.Free;
  113. end;
  114.  
  115. procedure TPlayGroundForm.Timer1Timer(Sender: TObject);
  116. begin
  117.   if csDestroying in ComponentState then exit;
  118.   UpdateImage;
  119. end;
  120.  
  121. procedure TPlayGroundForm.ComboBox1Change(Sender: TObject);
  122. begin
  123.   Timer1.Enabled := Combobox1.ItemIndex in [0, 2, 3, 4];
  124.   PictureControl.SetFocus;
  125. end;
  126.  
  127. procedure TPlayGroundForm.PictureControlKeyDown(Sender: TObject;
  128.   var Key: Word; Shift: TShiftState);
  129. const
  130.   DELTA = 20;
  131. begin
  132.   if ComboBox1.ItemIndex = 3 then Exit; // for smooth arrow key input
  133.   case Key of
  134.     VK_LEFT  : FSpritePosChange := Point(-DELTA, 0);
  135.     VK_RIGHT : FSpritePosChange := Point( DELTA, 0);
  136.     VK_UP    : FSpritePosChange := Point(0, -DELTA);
  137.     VK_DOWN  : FSpritePosChange := Point(0,  DELTA);
  138.   end;
  139.   UpdateImage;
  140. end;
  141.  
  142. function AdjustStep(p1, p2: Integer; Divisor: Integer): Integer;
  143. var
  144.   f: Double;
  145.   delta: Integer;
  146. begin
  147.   if p1 = p2 then
  148.     exit(0);
  149.  
  150.   delta := p2 - p1;
  151.   f := delta/Divisor;
  152.   if abs(f) < 1 then
  153.     Result := sign(f)
  154.   else
  155.     Result := round(f);
  156.   {
  157.     Result
  158.   if Delta > 0 then begin
  159.     if Delta < Divisor then
  160.       Result := 1
  161.     else
  162.       Result := Delta div Divisor;
  163.   end else
  164.   if Delta < 0 then begin
  165.     if -Delta < Divisor then
  166.       Result := -1
  167.     else
  168.       Result := Delta div Divisor;
  169.   end else
  170.     Result := 0;
  171.     }
  172. end;
  173.  
  174. procedure TPlayGroundForm.UpdateImage;
  175. const
  176.   SECONDS_PER_DAY = 24*60*60;
  177. var
  178.   DestImg: TBitmap;
  179.   t: Double;
  180.   CenterX: Integer;
  181.   CenterY: Integer;
  182.   dx, dy: Integer;
  183.   MousePos: TPoint;
  184. begin
  185.   // paint first on the buffer
  186.  
  187.   // paint background
  188.   BufferImg.Canvas.CopyRect(Rect(0,0,BufferImg.Width,BufferImg.Height),
  189.        BackgroundImg.Canvas,Rect(0,0,BackgroundImg.Width,BackgroundImg.Height));
  190.   // paint sprite
  191.   CenterX:=BufferImg.Width div 2;
  192.   CenterY:=BufferImg.Height div 2;
  193.   if not FSpritePosInit then begin
  194.     // SpritePos refers to the top/left corner of the sprite image.
  195.     FSpritePos := Point(CenterX - SpriteImg.Width div 2, CenterY - SpriteImg.Height div 2);
  196.     FSpritePosInit := true;
  197.   end;
  198.   case Combobox1.ItemIndex of
  199.     0: begin
  200.          // Movement of sprite by code along a calculated curve
  201.          t := Now * SECONDS_PER_DAY;
  202.          FSpritePos.X := CenterX + round(cos(t)*CenterX*2/3) - SpriteImg.Width div 2;
  203.          FSpritePos.Y := CenterY + round(sin(t*0.7)*CenterY*2/3) - SpriteImg.Height div 2;
  204.        end;
  205.     1: begin
  206.          // Movement of sprite by keyboard: UP/DOWN/LEFT/RIGHT arrows advance
  207.          // the sprite position by a given amount
  208.          FSpritePos.X := FSpritePos.X + FSpritePosChange.X;
  209.          FSpritePos.Y := FSpritePos.Y + FSpritePosChange.Y;
  210.        end;
  211.     2: begin
  212.          // Movement of sprite by mouse: the sprite follows the mouse
  213.          // Convert screen coordinates to images coordinates
  214.          MousePos := PictureControl.ScreenToClient(Mouse.CursorPos);
  215.          MousePos.X := round(MousePos.X / PictureControl.Width * PictureControl.Picture.Width);
  216.          MousePos.Y := round(MousePos.Y / PictureControl.Height * PictureControl.Picture.Height);
  217.          dx := AdjustStep(FSpritePos.X, MousePos.X, 5);
  218.          dy := AdjustStep(FSpritePos.Y, MousePos.Y, 5);
  219.          FSpritePos.X := FSpritePos.X + dx;
  220.          FSpritePos.Y := FSpritePos.Y + dy;
  221.        end;
  222.     3: begin
  223.          // Movement of sprite by keyboard: UP/DOWN/LEFT/RIGHT arrows advance smooth version
  224.          if (GetKeyState(VK_LEFT) < 0) then FSpritePos.X := FSpritePos.X - 10;
  225.          if (GetKeyState(VK_RIGHT) < 0) then FSpritePos.X := FSpritePos.X + 10;
  226.          if (GetKeyState(VK_UP) < 0) then FSpritePos.Y := FSpritePos.Y - 10;
  227.          if (GetKeyState(VK_DOWN) < 0) then FSpritePos.Y := FSpritePos.Y + 10;
  228.        end;
  229.     4: begin
  230.          // Movement of sprite by keyboard: UP/DOWN/LEFT/RIGHT arrows advance with inertia
  231.          if (GetKeyState(VK_LEFT) < 0) then FXIntertia := FXIntertia - 0.5;
  232.          if (GetKeyState(VK_RIGHT) < 0) then FXIntertia := FXIntertia + 0.5;
  233.          if (GetKeyState(VK_UP) < 0) then FYIntertia := FYIntertia - 0.5;
  234.          if (GetKeyState(VK_DOWN) < 0) then FYIntertia := FYIntertia + 0.5;
  235.          if (FXIntertia > 6) then FXIntertia := 6;
  236.          if (FXIntertia < -6) then FXIntertia := -6;
  237.          if (FYIntertia > 6) then FYIntertia := 6;
  238.          if (FYIntertia < -6) then FYIntertia := -6;
  239.          if (FXIntertia > 0) then FXIntertia := FXIntertia - 0.2;
  240.          if (FXIntertia < 0) then FXIntertia := FXIntertia + 0.2;
  241.          if (FYIntertia > 0) then FYIntertia := FYIntertia - 0.2;
  242.          if (FYIntertia < 0) then FYIntertia := FYIntertia + 0.2;
  243.          FSpritePos.X := FSpritePos.X + round(FXIntertia);
  244.          FSpritePos.Y := FSpritePos.Y + round(FYIntertia);
  245.        end;
  246.   end;
  247.  
  248.   // Make sure that the sprite does not leave the image.
  249.   FSpritePos.X := EnsureRange(FSpritePos.X, 0, BufferImg.Width - SpriteImg.Width);
  250.   FSpritePos.Y := EnsureRange(FSpritePos.Y, 0, BufferImg.Height - SpriteImg.Height);
  251.  
  252.   // Draw sprite at current position to buffer.
  253.   BufferImg.Canvas.Draw(FSpritePos.X, FSpritePos.Y, SpriteImg);
  254.  
  255.   // copy to image
  256.   DestImg:=PictureControl.Picture.Bitmap;
  257.   DestImg.Width:=BufferImg.Width;
  258.   DestImg.Height:=BufferImg.Height;
  259.   DestImg.Canvas.Draw(0,0,BufferImg);
  260. end;
  261.  
  262.  
  263. { TPictureControl }
  264.  
  265. procedure TPictureControl.SetPicture(const AValue: TPicture);
  266. begin
  267.   if FPicture=AValue then exit;
  268.   FPicture.Assign(AValue);
  269. end;
  270.  
  271. procedure TPictureControl.WMEraseBkgnd(var Msg: TLMessage);
  272. begin
  273.   Msg.Result := 1;
  274. end;
  275.  
  276. procedure TPictureControl.PictureChanged(Sender: TObject);
  277. begin
  278.   Invalidate;
  279. end;
  280.  
  281. constructor TPictureControl.Create(TheOwner: TComponent);
  282. begin
  283.   inherited Create(TheOwner);
  284.   FPicture:=TPicture.Create;
  285.   FPicture.OnChange:=@PictureChanged;
  286. end;
  287.  
  288. destructor TPictureControl.Destroy;
  289. begin
  290.   FreeAndNil(FPicture);
  291.   inherited Destroy;
  292. end;
  293.  
  294. procedure TPictureControl.Paint;
  295. begin
  296.   if Picture.Graphic<>nil then
  297.     // Canvas.Draw(0,0,Picture.Graphic); // copy is fast
  298.     Canvas.StretchDraw(Rect(0,0,Width,Height),Picture.Graphic); // stretch is slow
  299.   inherited Paint;
  300. end;
  301.  
  302. end.

The source code is provided at link below but I do not include the images (which can be found lazarus example folder).

Handoko

  • Hero Member
  • *****
  • Posts: 5439
  • My goal: build my own game engine using Lazarus
Re: Sprite demo example
« Reply #20 on: May 11, 2018, 06:06:26 pm »
Doh!  I should take a look to the example projects.  I've never visit them.  :-[

You should, you may find some hidden gems there.

wp

  • Hero Member
  • *****
  • Posts: 12869
Re: Sprite demo example
« Reply #21 on: May 11, 2018, 07:04:53 pm »
I downloaded the demo from rev. 57802 and added some new features:
- arrow keys (smooth)
- arrow keys with inertia
I committed you sample to trunk (with minor revisions). Thank you.

 

TinyPortal © 2005-2018