Delphi Sprite Engine – Part 7
The Delphi Sprite Engine “DelphiGlass,” which I’ve been building for this blog series, has seen several changes and updates recently. The animation is now smoother, the threading model is functioning, and it’s far more stable than it was. I’d like to share these updates with you.
If you’ve come across a previous post in this series, you’ll have been directed here to part 7. This is because the earlier posts, while interesting learning exercises, contain the mistakes that I’ve made along the way. A comment from ‘narcis75’ on Part-6 gave me the nudge I needed to go in and correct several of these mistakes….
- The threading model is working, and rendering is locked to a preset FPS rate (frames per second).
- Animation is now smooth (providing you have sufficient CPU to run it).
- There is no longer a crash as the application shuts down.
- Simulations are now accurately timed with the addition of a high resolution timer.
These corrections lead to several alterations to the engine code, so much so that it no longer resembles that which began back in the earlier posts. In fact, even part 6 of this series is out-of-date against the current code. If you were redirected here from an earlier post, it’s for this reason. From this post on, I’m going to be re-explaining the code, how it functions and how to enhance it. I’m going to squeeze this do-over into as few posts as possible, so that we can get on with a few enhancements and perhaps even finish this series!
The next part.
If you read part-6 and have checked out the latest revision of DelphiGlass, you may not need to read the remainder of this post. Having said that, there are several changes to the engine, which is why I decided to repeat the steps for obtaining and installing the enigne, and building the pigeon demo. If you decide to skip this part, be aware of the following changes before moving on to Part-8: (if you stay with us, I’ll point out the bulk of the post which you can skip)
- Fixed bug on shut-down of application, no longer wrecking the rendering context.
- Sprites are positioned from their center, rather than the top-left.
- Spites (and all scene objects) are positioned relative to their parent.
- Sprites get their width and height from the animation data (size of the image / frame)
- Sprites may be scaled using the ScaleX / ScaleY properties.
- Sprites have a ‘FramesPerSecond’ property to adjust their speed when animated.
- Timers are no longer required to progress animations, this is handled internally.
- The viewport is frame-rate locked using the ‘FramesPerSecond’ property.
- The scene runs a simulation thread, allowing for smoother movement and animation.
- TdgMovementSprite class has been added, to demonstrate motion, along with a second pigeon demo.
There are also still two known bugs:
- The resource package editor still doesn’t inform the IDE of changes, you’ll need to change some other property of the form in order to store resources within the form at design time.
- For some reason the IDE fails to close a project which uses the engine, instead it hangs and goes unresponsive. My guess is that one of the components is not releasing some resource. This needs further investigation.
For those of you that are new.
And as a reminder for those that aren’t.
These are the objectives of the sprite engine:
- Ability to display 2d images (sprites).
- Support transparency (transparent zones).
- Supported on Mac OSX, Windows, Android and iOS.
- Hierarchical scene composition
- Scrolling backdrop component.
- Scrolling tile map component.
- Reuse of image resources to reduce memory costs.
- Support for collision detection.
- Must perform sufficiently to run a simple game.
Those listed in green, the engine is already capable of. Those in Red are currently missing from the engine. The last, in blue, is achieved at present, but must be maintained as we complete the other parts.
DelphiGlass is available from my public subversion repository, and so to get your hands on it you’ll need a subversion client. There happens to be a subversion client built into the RadStudio IDE, however, I have been using a third party subversion client for several years, named TortoiseSVN. If you are unfamiliar with subversion, I’d suggest you start with TortoiseSVN as it integrates well into the windows explorer, go download and install it. If you’ve chosen another client, I’ll presume you know well enough how to use it to obtain the engine from the repository here: Edit: The subversion server has been decommissioned. Replaced svn link with downloadable zip file, download here: DelphiGlass (and you can skip ahead to “Installing the engine”)
First, you’ll need to create a new directory somewhere. You can place it anywhere you like, but I keep my subversion projects in a dedicated directory on the ‘c:\’ drive, “c:\subversion\public”, within which I’ve created a directory ‘DelphiGlass’.
Right click on your ‘DelphiGlass’ directory, and from the context menu, select “SVN Checkout” (if you’ve installed TortoiseSVN correctly, the option will be there in the menu).
In the dialog which appears, enter the repository URL:
Edit: The subversion server has been decommissioned. Replaced svn link with downloadable zip file, download here: DelphiGlass
and click “OK.”
A copy of the DelphiGlass project will promptly be downloaded from the repository into your DelphiGlass directory.
From this point on, if I tell you that there are updates to the engine, you can get them by right clicking this directory and selecting the “SVN Update” option.
(*note: I’ll try to only ever commit working versions of DelphiGlass to the repository. If you happen to check out a copy which is not working, please try updating it again a little later.*)
When you’ve checked out the engine, you should have the following directory structure.
Within the root of the directory is a project group named grpDelphiGlass.groupproj, double click it to open this project in your IDE.
Right click on each of the packages ‘pkgDelphiGlass.bpl’ and ‘pkgDelphiGlassDesign.bpl’ and select Install from the menu.
You now have DelphiGlass installed. You should find a new tab of controls under the tool palette for FMX applications..
Your new components.
You now have three new components in the component palette. Here’s a brief description of each of them…
- TdgResourcePackage – This component stores the graphical assets used by your application. Assets are stored in a tree structure, allowing you to provide some logical formatting to your assets for easier search and retrieval. This component has a component editor which we’ll see in a moment.
- TdgViewport – This is the component that actually performs the rendering of your application graphics. The TdgViewport hosts a list of primitives, or rendering instructions, which are executed several times per second in order to construct each frame of animation. The component actually contains two lists of instructions, one which is being rendered, and a second which is being constructed for the next frame. Several times per second, these two lists are swapped. Note: There is a FramesPerSecond property which defaults to 60, you can increase or reduce the frame-rate depending on the capabilities of your target CPU, however, you should never need to go higher than 60 FPS as this is more than enough for smooth animation, and any additional frames rendered will only cost CPU cycles that could be used on physics simulations, input handling, or other functionality. The simulation will run at a consistent speed regardless of frame-rates.
- TdgScene – This component is where your scene is constructed and your simulation is executed. Objects are created, moved, and modified by the application logic within the scene. This is a tree of objects, enabling child objects to be rendered with relative positioning to their parent. The TdgScene attempts to update the viewport at the same frame-rate at which the viewport is running, however, the scene it’s-self may refresh far more frequently.
The DelphiGlass repository contains a sample application (in the samples directory), named pigeon. It’s the animated pigeon that you may have seen in earlier blog posts. For the remainder of this post, we’ll construct that same application from scratch, so that you get a feel for working with DelphiGlass. If you’ve done this before (back in part-6), skip ahead to the section titled “Wiring Up.”
In the IDE start a new Multi-Device Delphi application, and select a 3D Application from the wizard. *Important, there must be a 3D form to host the viewport component, this is why we select 3D Application*
Now drop the following components onto your new form..
Now that you have the components on your form, select the scene component and set it’s ‘viewport’ property to ‘dgViewport1’. Also, select the viewport component and ensure that it’s ‘Form3D’ property has been correctly set to your main form ‘Form1’. Finally, make sure that the dgViewport1 and dgScene1 each have their enabled property set to ‘True’.
At this point, your form should have turned blue, because this is the default back-drop color for the dgViewport. If your form is not blue, go back and check that you got each of the above steps.
The resource package editor.
Right click on the Resource Package component and select “Show Editor”
This will open up the resource package editor window…
This window is a component editor plugin for the IDE which will enable you to create and edit resources for use in the DelphiGlass engine. Lets use this editor to create the flapping pigeon sprite that we’ll use in this application.
To the left of this window is the resource tree, which currently contains only a directory named “ROOT”. To the right is a preview / edit pane which we’ll use later. The two sides are separated by a divider control which will allow you to expand or retract the two panes.
Make sure the “ROOT” directory is selected and click the “+” button on the tool-bar, or use the keyboard short-cut [CTRL]+[A]. *note, I’ve tried to make the keyboard shortcuts useful for the repetitive tasks which must be performed in this editor, it’s worth learning them.
The dialog which is presented is used to create new resources. On the left is a drop-down list box which contains the resources which may be created under the currently selected node of the resource tree. For example, we have the ‘ROOT’ directory selected, which may parent additional “Directory”, or “Sprite Sheet” resources.
Select “Directory” from the drop down, and then type “Level 1” into the “Resource Name” field.
You may now click the “Okay” button to create the directory. (*note: The keyboard [RETURN] or [ENTER] keys may be used instead of the “Okay” button, and the [ESCAPE] key used instead of pressing the “Cancel” button.)
You now have a new directory named “Level 1” under the root node. Note that you can right-click on this node to see the “+” “-” “[UP]” and “[DOWN]” options. This allows you to add or remove resources to this directory, or to re-order sub-directories. The reordering option is more useful for arranging frames of animation, as we’ll see later.
Make sure you have the “Level 1” directory selected, and click “+” or [CTRL]+[A] to add a new resource. This time, add a sprite sheet named “Hero”…
The sprite sheet resource is the resource which we use to store image data for our sprites. You’ll notice the two new buttons on the editing pane to the right, these allow you to load a new image into the sprite sheet, or save out the image to a file. We’re going to load in the sprite sheet which we used previously for the pigeon demo. Download a copy here (or right click the image below, and save it to disk somewhere).
Now that we have the image loaded, we need to define the animation frames.
Make sure the ‘Hero’ sprite sheet is selected and add a new resource, this time, an animation resource named “flap”
With the ‘flap’ animation resource selected, you’ll see that the editor pane has now divided into two panes. The top one shows a preview of the sprite sheet image. The bottom pane is where we’ll edit the animation frames.
Add a new resource, this time a ‘frame’ resource named ‘0’ (zero)…
Repeat this process in order to add frames ‘1’ through ’19’. When you have 20 frames (0..19) you can begin setting the ‘Top’, ‘Left’, ‘Height’, and ‘Width’ properties of each frame. (*tip: The ‘TAB’ key will progress you through the edit boxes in the correct order). Here are the values for our sprite sheet…
Having loaded the coordinates of your animation frames into the editor, you should save a copy of this resource package (to prevent having to enter this information again should anything go wrong.)
In the main tool-bar, click the save icon and save this resource package as pigeon.rpk
You now have the resource package that you need for this application.
I’d like to take a moment now to tell you about some of the features which have been introduced with the resource package component. The most important of which are the loading options.
If you wish to deploy your application as a single binary file, you can do so by populating the resource package component, and then saving your form. The resource package data is converted into a binary stream and inserted into your form, which will be compiled into your binary at compile time. This is handy for simple deployments, however, it is not a preference for everyone, as it’ll significantly increase the size of the executable file if you package resources inside it. (*note: As a side note, there is a minor known bug in the component editor, changes may not be saved if you do not alter any other property of the form. Always save a copy of your package file, regardless of your chosen deployment method.)
As an alternative to packaging the resources up inside the executable, you could right click the Resource Package component and select “Clear Package” to empty the component. Assuming you have a copy of the resource package saved, you can call the .LoadFromFile() method at run-time to load the resource from disk.
Finally, if you wanted to deploy resources over the internet, you could download a resource file and load it at run-time, or even, stream your download into memory and use the .LoadFromStream() method to load it into the Resource Package component.
For simplicity of deployment, we’ll leave the Resource Package component populated with the sprite sheet, and have the resource bundled into our binary file. This may have a slowing effect on the IDE as it loads your form, but is a lot easier for us to use for demonstration purposes.
Lets ensure our resources are saved with the form. You can do this by altering the color property of your form. This change has no effect on the appearance of your form because the viewport component is overriding the rendering of the form to set it blue. What this does is to register a change to the form, which means the IDE will save our changes. (this is the known bug I mentioned earlier, editing the resource package does not indicate to the IDE that a change was made, I’ll resolve this soon.)
Now go ahead and save your project and associated form files.
We’re now at the point where we should wire the demo together and make it function. We’ll start with some initialization code. Double click on the ‘OnCreate’ event for your form and add this code…
with TdgSprite.Create(dgScene1.Root) do begin Resource := dgResourcePackage1.Resource['ROOT/Level 1/Hero/Fly']; Position := TPointF.Create(100,100); FramesPerSecond := 40; end;
The first thing this code does is to create a sprite component which is parented to the root node of dgScene1. We then set the resource property of the sprite using the dgResourcePackage1.Resource accessor. Note that we specify the animation for the sprite class using the path of the animation resource as constructed inside the resource package editor!
This is an important thing to notice. It means that specifying a resource can be done in a human readable way, but this comes at a cost. Anything human readable, is less machine readable, and as such will have a performance cost. You should therefore locate any resource that you need during the initialization of your application (or level), and keep references to them. This will prevent you having to do this look-up again during the rendering cycle, where every CPU tick counts.
An additional thing to notice is that the dgResourcePackage1.Resource accessor does not have a specific resource type. We selected a sprite sheet by it’s path, but had we selected some other resource the code would have functioned just as well. In fact, the TdgSprite class is able to render an animation, a single frame, or an image resource and so we could have selected any of these resource types. Had we specified an invalid type, such as a directory for example, the sprite class would generate an exception at run-time.
Having specified the resource, we set the initial position of the sprite that we’re rendering (in pixels). The position of the sprite is oriented from it’s center, so I’ve selected coordinates 100×100 to make room for the sprite against the edge of the screen. It’s width and height are determined from the animation resource, however, you could adjust the ScaleX and ScaleY properties to alter it’s size as required.
Finally, we set the FramesPerSecond property to 40. This is because our animation has 20 frames, and I’d like the pigeon to flap it’s wings twice per second, so 20*2=40. We can set any frame rate we like here, it’ll operate at this rate regardless of the rendering frame-rate of the viewport.
Note, at present it would be pointless to set the frame-rate higher than that of the viewport, because the sprite class will not function this way, but in future versions of the engine, you might actually want to set the frame-rate of an animation higher than that of the viewport. When? Well it’s a rare case, but suppose you had a very detailed animation with hundreds of frames, this would run very slowly at 60-FPS (a typical viewport framerate), so in order to make this animation run more quickly you’d need to set a high frame-rate and have the DelphiGlass engine select the appropriate frames. Watch this space, I’ll be adding this functionality soon.
At this point, go ahead and run the application, our pigeon demo is complete….
The DelphiGlass sprite engine now has a far smoother rendering loop. It’s rendering is frame-rate locked, allowing us to take advantage of remaining CPU cycles for our applications. Some known bugs still remain, but they are fewer than the previous revision. Most importantly, the DelphiGlass engine is now at a good point for enhancements, such that we might finally achieve the goals set out in part 1 of this series!
Thanks for Reading