Delphi Sprite Engine – Part 6 – DelphiGlass
[If you’re new to this series, you may wish to skip ahead to part-7, where I’ve done a partial ‘start-over’]
It’s been a long time since I posted an update to our Delphi Sprite Engine. Regretfully, work, and other commitments, took precedence over my time, and I’d like to do something to make up for that, and so I’d like to introduce DelphiGlass.
[Edit: Library renamed to DelphiGlass ]
A short while ago, I decided that the sprite engine could use a bit of a redesign. What we had, worked. It was only ever intended to be an example of how to render 2D graphics with FireMonkey, but the sprite sheet was difficult to construct, because it had to be constructed in-code first. There was also no means of separation between the rendering loop and the scene logic.
In order to remedy this, I started over and wrote ‘DelphiGlass’ my new sprite engine for Delphi. DelphiGlass is still in the early stages, possibly too early to share, but having just returned from a long vacation on top of the other delays, I’ve decided to share this with you. This is the project status…
- Implemented resource management.
Sprites, images and animations so far, but it will also include backdrops, tile-maps, and audio resources.
- Implemented component editor.
The resource management has component editors which install into the IDE, making editing of resources far easier. This also allows you to include resources within your project file, embedded into the form if you chose to do so, and this makes deployment far easier.
- Separate Logic and Render threads.
Although I’ve only permitted a single thread for logic at this point, the game and animation logic is running in it’s own task, and therefore it’s own thread if the task pool sees fit. This means that rendering can progress at a set frame rate, while the logic which controls physics for example will run independently. More work is needed in this area, but the initial code is in place.
- Lacking source code documentation!
Lacking documentation for the source code is about as close as something gets to being a sin in my view. The code needs documentation and will get it, but I omitted this step in order to get the engine started before I left for my vacation. Watch this space.
- Comparable to the previous engine.
In terms of capabilities, this engine is comparable to what we had at the end of part 5.5, in that it currently supports little more than animated sprites. There is still no scrolling backdrop and there is still no tile map system. I can’t apologize enough that they aren’t ready yet, but like I said in my introduction, I think some of the features of this new rework will compensate for the delay. Keep reading 🙂
What I have made certain of this time, is that the engine can be deployed out to mobile devices. Well, actually I’ve tested only on Android so far as I’m lacking an iOS device right now, but it does deploy and function on Windows and Android as expected. That’s about as far as I’ve gone with testing the engine. Many more tests will be required, but as I said, time has been short.
- XE7 / XE8 or higher
The code currently requires XE7 or higher, and has been written using XE8. There is nothing in the code thus far which prevents you using it in older FMX enabled versions. I expect it’ll work just fine with XE6. Minor code changes may be required for XE5 (some classes moved around within the FMX units). At a later time, I’ll get this code working on older versions using conditional defines, but until then you’ll require at least XE7 or XE8 to make it run.
Download source code here: delphiglass
I recently began a subversion repository to accompany my blog. DelphiGlass is inside that 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, and there is also a great third party option for windows named TortoiseSVN. If you’re unfamiliar with using SVN, please go read up on how to check-out a project with your chosen client. Go ahead and check out a copy of DelphiGlass, and for the remainder of this post I’ll show you how to install and make use of the engine. In future posts, I plan to enhance the engine and share the enhancements with you.
(*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 a little later.*)
Once checked out, you should have the following directory structure.
Within the root of the directory is a project group named grpDelphiGlass.groupproj 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..
Having installed these new components, you might like to restart your IDE at this point. It seems that sometimes the IDE becomes unstable after installing these components. I don’t know if this is a fault in the components or the IDE and I’ll investigate further, however, the simple solution is to restart the IDE before making use of the components.
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 is similar to the sprite sheet class from our previous engine, in that it 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 tree of primitives (TdgPrimitive) which are the objects that actually get rendered. At present, only a single primitive is available, but it’s a very useful one, the TdgTextureRect, which enables you to paste a texture onto your screen.
- TdgScene – This component is where your scene is constructed, objects are created, moved, and modified by the application logic. This is another tree of objects, which connect to the viewport to create and manage viewport primitives. Every time a frame of animation is to be rendered by the viewport, your TdgScene will be called upon to update the primitives which it has placed in the viewport, and those primitives will then be rendered.
The DelphiGlass repository contains a sample application (in the samples directory) which you’ll recognize from the previous sprite engine. It’s the animated pigeon. For the remainder of this post, we’ll construct that same application from scratch, so that you get a feel for working with DelphiGlass.
In the IDE start a new Multi-Device Delphi application, and select a 3D Application from the wizard.
Now drop the following components onto your new form..
Be sure to drop the TdgViewport onto your form BEFORE you drop on the TdgScene. The threads were the most recently added feature to DelphiGlass, and thus-far, a graceful application shutdown will only occur if the viewport is disposed before the scene. For that reason, you must add the viewport before the scene. This is a known bug which will be resolved soon. Note also, as you drop the viewport component, your form should turn blue (this is the default backdrop color).
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’.
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 recreate the flapping pigeon sprite that we’ve used previously.
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 select, 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…
procedure TForm1.Form3DCreate(Sender: TObject); begin with TdgSprite.Create(dgScene1.Root) do begin Resource := dgResourcePackage1.Resource['ROOT/Level 1/Hero/flap']; Width := 150; Height := 150; end; dgScene1.Enabled := True; Timer1.Enabled := True; 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 width and height of the sprite we’re rendering (in pixels), and then enable both the dgScene and Timer components.
Our dgScene, once enabled, will begin rendering repeatedly within it’s own thread. Our timer is then used to perform any game / scene logic. This is a temporary arrangement for the purposes of this demo. In later revisions we’ll remove the timer and place game logic inside custom scene components. For now though, the timer works well.
Completing the wiring.
Drop back to your form and set the timer interval to ’10’, it’s enabled property to ‘FALSE’, and then add the following code to the timer OnTimer event.
procedure TForm1.Timer1Timer(Sender: TObject); begin TdgSprite(dgScene1.Root.Children).Frame := succ(TdgSprite(dgScene1.Root.Children).Frame); end;
With this code we’re locating the sprite that we created earlier and updating it’s frame counter to display the next frame of animation. Note that we’re locating the sprite component here by addressing the scene component’s tree. To do this more correctly, we should keep a class-global reference to the sprite, which we created during initialization, but as we currently only have a single sprite in our demonstration, this technique will suffice.
Finally, we need to add a single line to the form OnDestroy event…
procedure TForm1.Form3DDestroy(Sender: TObject); begin dgScene1.Enabled := False; end;
That’s it. Our pigeon demo is complete.
Go ahead and try running it…
Our sprite engine “DelphiGlass” is functioning as well as the previous engine, but is now far better structured. There are some known bugs, and a lack of code documentation, which I’ll address in pending posts, but I feel we now have a far better basis to start from.
I don’t like imposing licenses, and have no commercial aspirations for this code.
As it stands today (1 June 2015, at revision 93), this engine is not exactly of commercial quality and I’m not imposing any licensing restrictions on it. You can consider it public domain and use it for any purpose you see fit, so long as you understand that I do not provide it as fit for any particular purpose, and I can’t be held responsible for it’s use or misuse. If you do correct a bug, or create an enhancement, I’d appreciate you sharing it with me but this is not a requirement.
I may add a license for future versions, in which case it will be an open source license under similar conditions as above. Depending on contributions from others a small fee may be added for commercial use of subsequent versions, but this revision remains free as it is right now.
Thanks for Reading