📜 ⬆️ ⬇️

Lazarus - write component for sprite animation

Instead of the preface

In the Odessa school, students of the 8th grade at computer science lessons use the free Lazarus cross-platform development environment ( official website ), which outwardly and internally resembles the favorite by many Delphi, using the version of Object Pascal called Free Pascal and in fact greatly simplifies the process of entering programming.

But children are not interested in writing a program for calculating the force of gravity using the formula F = mg, which is not yet understood by them. Almost all the children I tried to teach programming, from the first lesson they want to write a game. Fortunately, Lazarus is also great for writing simple games.

However, to create animated sprites, I needed a component that represents an arbitrary image fragment (which shows several different projections of the same character in different phases of the movement), but there is no such component in the standard package. It turned out to be quite easy to write it myself, and I want to tell about this technology in this article.

For displaying fun graphic content, instead of a dry business set of standard components, Lazarus (like Delphi) has 3 components on the Additional tab:
- TImage (display images from an arbitrary file);
- TShape (display of one of several predefined graphics primitives);
- TPaintBox (display canvas, on which you can draw programmatically).

The most spectacular for the student is to load a small sprite into a TImage and write a program to move it around the screen - on mouse / keyboard events, automatically in a loop, or automatically on an event from the timer.

As soon as it starts to work, the student has the following legitimate question: is it possible to make the character move? And is it possible to make it so that he does not constantly look at us, but turn in the direction coinciding with the direction of movement?

On the Web, you can find a large number of ready-made images for use in game development. And many characters are pre-designed in several projections and several animation frames (as, for example, here on this site ).

Here is an example of an image where the sprites are arranged in a table, in which each row corresponds to a certain projection, and each column represents a specific phase of the animation:


Why so many pictures?
To display such a sprite, it is enough to place a simple component on the screen that displays not the entire image, but only one fragment of it; and then, by changing the offset of the selected fragment horizontally and vertically, you can force the character to turn in different directions and make cyclical movements (for example, flapping wings or steps with legs). This technique is often used in web development: even simple icon sets for business graphics are often placed in one file and displayed in different places on the page with different offsets, giving the impression of different images.

Unfortunately, the TImage component included in the standard delivery of Lazarus (and Delphi) does not allow to show an arbitrary fragment of the image: by changing its properties, we can make it show only the entire image, the upper left corner or its central part. To display an arbitrary fragment of the image, given the displacement and dimensions along both axes, some other component is needed. But as it turned out, making it yourself in Lazarus is a snap!

Create a new component

As an instruction for creating components, I used the official manual .

Everything is written there in sufficient detail, duplicating does not make sense. I will only dwell on some points.

1. Standard Project Wizard does not offer us to create a package, and in order to somehow get access to the editor, select “New Project” (in the Russian version - “New Project”)


and then "Application" (in the Russian version - "Application"):


2. Acting further on the instructions, in the “Package” menu (in the Russian version - “Package”), select the top item “New Package ...” (in the Russian version - “New Package ...”), select the file name and path to save. I called my new package “Game” and placed it in a separate folder with the same name:


I created a separate Lazarus / Cmp folder with the expectation that I may have several different packages with components, and already in this folder I created the “Game” folder.

If everything is done correctly, a new (still empty) package window should appear on the screen.

3. Acting further again according to the instructions, to create a new component in the package window, click the “Add” button (in the Russian version - “Add”) and in the drop-down list select “New Component” (in the Russian version - “New Component”):


We specify TCustomImage as an ancestor class - this class is actually used to implement the TImage component, but differs from it in that it does not contain published properties and allows us to determine the set of properties that will be available in the designer for our component.

What is published properties?
For those who do not know this, I’ll clarify that published is a class section (like public) that describes new ones or simply specifies inherited properties that should be available in the visual property editor at the program development stage. Intermediate classes do not declare anything in this section, leaving the programmer himself to put in there what he deems necessary. So, the TImage class does not add any functionality, but only puts into the published section a number of properties inherited from the parent TCustomImage. We need to hide some of these properties, so we will also inherit our new component from TCustomImage and print only what does not contradict the logic of our component.

Icon for component
It would be a good style to draw a personalized icon for each new component, but since our task is to show how simple it is, we will leave this field blank, which will result in the standard icon used in Lazarus / Delphi for all self-made components displayed on the toolbar. .
By the way, the above mentioned instruction contains a separate section dedicated to creating icons for components - this is for those who are not satisfied with the “default” icon.

Having filled in all the fields, we press the button “Create New Component” (in the Russian version - “Create a new component”).

Add code to new component

Immediately after creating a new component, its source code is approximately as follows:

unit ImageFragment; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs; type TImageFragment = class(TCustomImage) private protected public published end; procedure Register; implementation procedure Register; begin RegisterComponents('Game', [TImageFragment]); end; end. 

As expected, the class declaration is completely empty, and there is no implementation at all. All that is - the registration function of the component on the tab "Game".

We need to add several inherited published properties, create two of our own and override one virtual function. Let's get started!

0. In the import section, we will need two additional modules: ExtCtrls and LCLProc - we add them to the uses section:

 uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls, LCLProc; 

1. Add the list of published properties, completely similar to the TImage component, with the exception of several properties that allow you to change the scale and position of the image:

  published property AntialiasingMode; property Align; property Anchors; //property AutoSize; property BorderSpacing; //property Center; //property KeepOriginXWhenClipped; //property KeepOriginYWhenClipped; property Constraints; property DragCursor; property DragMode; property Enabled; property OnChangeBounds; property OnClick; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnMouseDown; property OnMouseEnter; property OnMouseLeave; property OnMouseMove; property OnMouseUp; property OnMouseWheel; property OnMouseWheelDown; property OnMouseWheelUp; property OnPaint; property OnPictureChanged; property OnPaintBackground; property OnResize; property OnStartDrag; property ParentShowHint; property Picture; property PopupMenu; //property Proportional; property ShowHint; //property Stretch; //property StretchOutEnabled; //property StretchInEnabled; property Transparent; property Visible; end; 

For greater certainty, I did not delete, but commented out the properties that are in the TImage component, but will interfere in our new TImageFragment component.

2. Add two new properties to the class declaration to set the image displacement horizontally and vertically:

  private FOffsetX: Integer; FOffsetY: Integer; procedure SetOffsetX(AValue: Integer); procedure SetOffsetY(AValue: Integer); published property OffsetX: Integer read FOffsetX write SetOffsetX default 0; property OffsetY: Integer read FOffsetY write SetOffsetY default 0; 

and do not forget to add two declared procedures to the implementation of the class:
 implementation procedure TImageFragment.SetOffsetX(AValue: Integer); begin if FOffsetX = AValue then exit; FOffsetX := AValue; PictureChanged(Self); end; procedure TImageFragment.SetOffsetY(AValue: Integer); begin if FOffsetY = AValue then exit; FOffsetY := AValue; PictureChanged(Self); end; 

3. Override the DestRect virtual function:

  public function DestRect: TRect; override; 

and add its implementation to the class implementation:

 function TImageFragment.DestRect: TRect; begin Result := inherited DestRect(); if (FOffsetX <> 0) or (FOffsetY <> 0) then LCLProc.OffsetRect(Result, -FOffsetX, -FOffsetY); end; 

Compile the package and reassemble Lazarus

1. In the package window, click the “Compile” button (in the Russian version - “Compile”). If everything is done correctly, a green inscription on successful compilation will appear in the message window; if not, the inscription will be yellow or red.

2. In the same window, click on the “Use” button (in the Russian version - “Use”) and select the second “Install” item in the drop-down menu (in the Russian version - “Install”). The program will offer to rebuild and restart IDE - we agree:



3. After restarting, a new “Game” tab will appear on the toolbar, and an icon for our new component will appear on it.

Instead of an afterword

In the following Lazarus article - simple animation using the TImageFragment component, I described how such a component can be used - in 5 minutes to create a window in which the animated character moves in different directions and turns in the direction of the movement.

If the topic turns out to be interesting to readers, I can supplement this cycle with an article about how, having spent a little more time, you can do, for example, a football field with a pair of players, controlled from the keyboard.

And if there is enough time and desire - I will try to write different algorithms for controlling characters (for example, football players) and arrange competitions between them!

Source: https://habr.com/ru/post/438828/