Professional Documents
Culture Documents
Delphi Sprite Engine Part 5 Streaming Resources
Delphi Sprite Engine Part 5 Streaming Resources
chapmanworld.com/2015/03/09/delphi-sprite-engine-part-5-streaming-resources/
In part 5 of this series, as promised, we’re going to add streaming capabilities to our
TStriteSheet class.
Adding streaming to the TSpriteSheet class will enable us to save sprite sheets in files, with
their animation and static image data. Or perhaps to save them into a database to be
fetched as required by a mobile device. When combined with some kind of streaming for
the scene classes, this could be used to package all of the data required to represent a game
level, so we’ll be able to load levels rather than hard code them.
Before anyone asks, I did give serious consideration to serializing our sprite sheet to a
common format such as XML or JSON. Using these formats have merit, however, doing so
increases code complexity a little, and requires more work. For the purposes of this series
of blog posts, I decided there was little benefit in targeting these formats. If you’d like to,
please consider that an exercise for the reader.
SaveToStream()
Save an identifying signature to identify this as a sprite sheet.
Save the source image.
Save the animation data.
Save the static image data.
LoadFromStream()
Load the identifying signature and confirm this is a sprite sheet.
Load the source image.
Load the animation data.
Load the static image data.
1/10
const
cSig = 'MAGIC_SPRITES';
Note the constant in the above code, this can be placed anywhere in the implementation
section, so long as it comes before the SaveToStream() and LoadFromStream() methods.
1. StringToStream(), The standard TStream class does not have a method for saving
strings to a stream, so here we supply our own.
2. BitmapToStream(), TBitmap does have a method enabling it to be saved to a stream,
however, it’s not suitable for our needs so we wrap it with our own method.
3. AnimationToStream(), Allows us to save our TAnimation class, and subsequently
TAnimationFrame classes to stream.
4. ImageToStream(), This method allows us to save our TStaticImage class to stream.
Each of these methods has a method which does the reverse and loads data from the
stream. We’re going to take a closer look at each of the saving methods, and then I’ll simply
include the loading methods for you to study. Lets start with StringToStream().
2/10
procedure TSpriteSheet.StringToStream( S: string; aStream: TStream );
var
l: int32;
idx: int32;
c: char;
begin
// write the length of the string in code points (characters)
l := length(S);
aStream.Write(l,sizeof(l));
// loop the code-points to write each one-at-a-time
for idx := 1 to l do begin
c := S[idx];
aStream.Write(c,sizeof(c));
end;
end;
This method is quite inefficient. The default string type when using modern Delphi
compilers is unicode UTF-16 Little Endian. Although UTF-16 has variable length code points,
the majority of languages will only ever use 16-bits, and those that require more space
always use 32-bits. In order to save the string, we’re saving each 16-bit code-point, or partial
code-point, one at a time within a loop. I’ve written it this way because it’s pretty clear what
it does, and, I’m lazy, this is the easy way. LoadFromStream() does the reverse.
3/10
procedure TSpriteSheet.BitmapToStream( aBitmap: TBitmap; aStream: TStream );
var
MS: TMemoryStream;
S: int32;
begin
// We can't simply use TBitmap.SaveToStream because it does nothing to
// mashal the size of the data. When loading back, it'll over-run. So we
// need an intermediate buffer.
MS := TMemoryStream.Create;
try
// Copy bitmap to memory stream
aBitmap.SaveToStream(MS);
MS.Position := 0;
// Write size of memory stream to target stream
if MS.Size>MaxInt then begin
raise Exception.Create('TSpriteSheet.BitmapToStream: Image data too large.');
end;
S := MS.Size;
aStream.Write(S,Sizeof(S));
// Write content of memory stream to target stream
MS.SaveToStream(aStream);
finally
MS.Free;
end;
end;
There’s nothing too special going on here. We’re simply saving the name of the image to the
stream, and then, we’re saving the pixel coordinates of the image. When we load this back,
we’ll create a new instance of TStaticImage and give these pixel coordinates to the
4/10
constructor so that the texture coordinates are recalculated.
AnimationToStream() starts by saving it’s name to the stream, and then it saves the number
of frames of animation, followed by the data for each animation frame. Again, we only save
the pixel coordinates because we can recalculate the texture coordinates when we load this
data back.
end;
Okay, we can now save our TSpriteSheet class to a stream and load it back. So if we create a
sprite sheet in code as we have done, we can save it to a file, and then load it back into the
engine from file rather than from the hard-coding.
Why is this important? Well, this gives us the ability to supply a sprite based application with
limited artwork, and to then supply additional artwork later without having to release an
updated application binary. If you extend this out to games, with a few additional streaming
functions we could save entire levels into streams (or files via file streams), and deliver those
levels as updates to the game, without having to actually deliver an updated game
application. This was not one of our original goals, but it’ll become useful later, consider it
an added extra for now.
Back in part-1 I promised that this sprite engine would be cross platform, but it isn’t! I tested
a proof of concept, and have since merely asserted that the sprite engine is cross platform,
when this is actually not true. A few issues have crept in which have broken the code for
cross platform deployment.
1) My use of TList for storing lists of classes has lead to the code being incompatible with
ARC (automatic reference counting) which is how all of the mobile compilers for Delphi
work.
9/10
The solution to the fist of these is to swap TList for a generic TList<> everywhere.
The second problem is also due to ARC. The FMX framework TMaterial class has a weak
reference to the texture which we’re assigning…
So I’ve gone ahead and fixed that problems in the code which you can download here – :
(Replaced, please skip ahead and take the sources from part 5.5: Delphi Sprite Engine part
5.5 )
10/10