Article Two - Sprites

Introduction

In the first article we spent awhile getting everything setup properly with our project and autotools. Now that is out of the way we can do something fun! In this article we will be creating a sprite class, a sprite manager, and a start of an application that will use our new classes to do a little graphical demonstration. I will be covering the new classes piece by piece so you may find it more convenient to download the source files for this article from here and follow along as it were in the article.

src/Makefile.am

Oh no, not more autotools stuff! Sadly, yes, there's more autotools stuff. Luckily this is a very simple change. We need to add two new files to our src/Makefile.am file in order for valac to compile them:

...
brickval_VALASOURCES = \
        brickval.vala \
        spritemanager.vala \
        sprite.vala
...

There that wasn't so bad! As I mentioned in the previous article once things are setup with autotools most of the work to do is as simple as adding a few filenames here and there.

src/brickval.vala

This is the main class of our game. So far it's pretty grim, although it is a world class Hello World generator. Lets start changing that, for now, just delete everything in the file but the using Glib line and the license information. Then add the following lines after the glib line:

using SDL;
using SDLImage;
using Gee;

Vala uses packages to interface with existing libraries. Rather than '#include'ing headers, in a Vala project you use a package. In this case we are interested in SDL, SDLImage, and Gee. Gee is a package written in Vala that provides some data structure types that utilize generics (which is a way of using the same data structure for any type of data). SDL is a very mature and well used library that provides a cross-platform solution for generating graphics, sound, and handling input. SDLImage is an auxiliary library for SDL that adds in robust image loading support which we will be using to load the data for our sprites.

Next we will define our class and the fields for the class:

public class Brickval : Object {
    private const int SCREEN_WIDTH = 640;
    private const int SCREEN_HEIGHT = 480;
    private const int SCREEN_BPP = 32;
    private const int DELAY = 10;

    public weak SDL.Screen screen;
    private GLib.Rand rand;
    private bool done;

    private SDL.Surface bg;
    private SpriteManager spriteManager;
    private ArrayList<Sprite> sprites;

Since vala doesn't compile to straight C, it compiles to 'Gobject C', to gain the benefit of many of Vala's features your class should inherit from Object. The ensures that when your class is compiled that it becomes a fully fledged GObject and able to integrate properly with that system. Vala has no built in preprocessor to handle definitions but it allows for a similar thing with const variables. These are variables that once set cannot be changed. The benefit to using const variables over proprocessor definitions is that since they are in fact real variables they do not bypass the type-checking mechanisms in the compiler and shut down an entire class of possible errors.

Another thing to notice is that Vala's generics syntax is nearly identical to C# or Java and should be familiar to users of those languages. Also, the weak modifier bears mentioning. Since Vala provides memory management via a system of reference counting when a class goes out of scope or is explicitly freed Vala will try to deallocate the memory used by that class. Since, primarily, Vala is a way to utilize a modern, object-orientated language without breaking C ABI compatibility there are times when where you are interfacing to libraries that aren't not themselves GObjects. Since reference counting will not work on these non-GObject fields we need to mark them explicitly so valac knows not to try to handle them. This is accomplished by declaring the field as a weak reference. These weak reference exist outside of Vala's memory management system and must be dealt with by the programmer.

Now that we have defined our class and the fields for it lets cover each of the methods in the class.

construct

construct {
    this.rand = new GLib.Rand ();
}

Since vala compiles down to 'GObject C' constructors are an area that may be a bit unfamiliar to those used to other languages that happen to look a bit like vala. A standard constructor like:

public MyClass(int foo, bool bar) {
...
}

...cannot be used in vala to do anything but set properties. This is due to the way GObject itself instantiates classes. To allow for constructors which do more than set properties Vala provides a number of different anonymous constructor types. These include construct (which is what you most likely will use most), which is run at every instantiation of a class, class construct which will only run at the first instantiation of a class, and static construct which is run once at class definition and never again.

In this case we simple create an instance of GLib.Rand so we have it around.

main

public static int main (string[] args) {
    SDL.init(InitFlag.EVERYTHING);

    var app = new Brickval();
    app.run();

    SDL.quit();

    return 0;
}

This is our main function and the function that is actually executed when we run our vala program. The definition of the static function will likely look very familiar to C# and Java programmers. Any arguments to the program are passed into the string array args, although currently we do not do anything with any arguments passed. To start with we initialize SDL with the InitFlag EVERYTHING which tells SDL to turn on all its various subsystems. Then we create an instance of our Brickval class and call the run method on it. The run method is the main loop of our game and we will look into that next. Once run returns that means its time to shut everything down so for now we simply call SDL.quit() to shut SDL down properly and return.

Lets take a look at our main loop now.

run

public void run () {
    this.init_video ();
    spriteManager = new SpriteManager();
    sprites = new ArrayList<Sprite>(direct_equal);

    InitSprites();
    while (!done) {
        this.update();
        this.draw ();
        this.process_events ();
        SDL.Timer.delay (DELAY);
    }
}

As you can see our main loop is very compact. We do some initialization and then while the done variable is true we loop through the various systems. Notice that currently we only delay a constant number of ticks before continuing with the main loop. In a future article we will be changing this to a method that will ensure a fixed number of frames per loop which will provide a constant update rate irregardless of the speed of the computer on which it is running.

Looking at the initialization functions briefly we see that we initialize the video first. Then we instantiate two classes: SpriteManager and ArrayList. The SpriteManager class manages all the sprites being displayed at any point and lets you preform various operations. We will explore this class a bit later. The ArrayList instantiation creates a list where we store all instances of the currently running sprites. The reason for this seeming doubling up of sprite lists will become clearer later but generally we do this because the sprite manager contains the set of all sprites and if we want to add further granularity to that set we need to provide the mechanism for doing so separately. This is a weakness in the current SpriteManager class that will be addressed in a future article.

InitSprites does what you think it might, namely, initialize the sprites for use. The remaining part is simply the loop and should be fairly self explanatory. We will be looking into each of these functions later. However, since it is first, lets look at init_video next.

init_video

private void init_video () {
    uint32 video_flags = SurfaceFlag.DOUBLEBUF
            | SurfaceFlag.HWACCEL
            | SurfaceFlag.HWSURFACE;

    this.screen = Screen.set_video_mode (SCREEN_WIDTH, SCREEN_HEIGHT,
        SCREEN_BPP, video_flags);
    if (this.screen == null) {
        GLib.error ("Could not set video mode.");
    }

        this.bg = SDLImage.load(Config.BACKGROUND_DIR + "/bg.png");

        SDL.WindowManager.set_caption ("Brickval " + Config.VERSION, "");
}

This is an interesting function because it showcases several things that we've set up previously. First off we define some flags that we OR together to form a 32-bit long bitfield. These turn on Double buffering, hardware acceleration, and hardware vram usage respectively. Then to create our output surface we call the class method set_video_mode from the SDL Screen class. If this function returns null then something went awry. We check for this condition and raise an error if it occurs. The error will quit the program after displaying the message on the console.

We use the SDLImage package to do all image loading for us. This is a great package that allows us to load pretty much any graphics format one might want to use in a game environment. The package includes a method load which uses some inbuilt heuristics to determine what kind of image is being loaded. It returns an initialized SDL.Surface object which we assign to our bg field.

The last call to the set_caption method of SDL.WindowManager simply sets the title of the window.

Notice our calls to Config.<FOO>. This is using the config.vapi package that we created earlier to pull the information from the autotools generated definitions. This is especially useful for image loading as there is no guarantee where the user will actually install the data. It could, for example, be in /usr/share/brickval/backgrounds or /usr/local/share..., /opt/usr/share, etc. Utilizing this method we don't have to know as programmers where the data is installed precisely.

InitSprites

private void InitSprites() {
        for(int i = 0; i < 100; ++i)
        {
                Sprite s = spriteManager.Get(spriteManager.Add(Config.SPRITE_DIR + "/bricks.png"));
                s.InitSpriteSheet(40, 20, 21, true);

                s.X = rand.int_range(0, screen.w);
                s.Y = rand.int_range(0, screen.h);
                s.XV = (rand.next_int() % 2 == 0) ? -1 : 1;
                s.YV = (rand.next_int() % 2 == 0) ? -1 : 1;

                s.SetCurrentCell(rand.int_range(0, s.CellCount));
                if(s.CurrentCell == 6) s.SetCurrentCell(7);

                sprites.add(s);
        }
}

The InitSprite function uses our SpriteManager class to create new sprite objects. In this case it will create 100 sprites, set them to a random location in the screen, give them a random starting direction, and sets the image they will use. We won't go over what precisely each of these Sprite methods is doing in this section, that will be saved for the discussion of the Sprite class later in the article.

update

private void update() {
        UpdateSprites();

        spriteManager.Update();
}

This is the update method that is called in the main loop. You see that we call a method UpdateSprites and then the Update() method of the SpriteManager. The reason for this dual update scheme involves the particulars of the SpriteManager class. We will cover this in more detail later in the article but for now just be aware that UpdateSprites() contains the logic for the behavior of the sprites (ie, what they do), and the SpriteManager is what deals with actually updating the sprites coordinates, displaying the sprite, etc.

UpdateSprites

private void UpdateSprites() {
        Iterator<Sprite> iter = sprites.iterator();
        while(iter.next())
        {
                Sprite s = iter.get();
                if(s.X < 0)
                        s.XV = 1;

                if( (s.X + s.Width) > screen.w)
                        s.XV = -1;

                if(s.Y < 0)
                        s.YV = 1;

                if( (s.Y + s.Height) > screen.h)
                   s.YV = -1;
        }
}

This is the logic that drives our sprites' behavior. We use a Gee.Iterator to iterate through the ArrayList of sprites we created in InitSprites. We check to see if an edge of the sprite is outside the screen and if so we reverse the velocity of that sprite so it will move in the opposite direction.

It would be nice, one might state, if the sprites could somehow contain the behavior they should do without having to manage a separate list. In a future article we will add this capability to our sprites when we explore Vala's use of delegate methods.

draw

private void draw () {
        Rect sr, dr;

        sr.x = 0;
        sr.y = 0;
        sr.w = (uint16)this.bg.w;
        sr.h = (uint16)this.bg.h;

        dr.x = 0;
        dr.y = 0;
        dr.w = sr.w;
        dr.h = sr.h;

        this.bg.blit(sr, screen, dr);

        spriteManager.Render(screen);

    this.screen.flip ();
}

This is the draw() method that is called in the main loop. After the logic for the sprites is dealt with we then can render the new frame. When using double buffering in SDL any blitting you do is done on an undisplayed backbuffer. Then once all blitting is finished you call flip() on the screen which replaces the displayed buffer with the newly drawn buffer. This ensures that all drawing is finished before the user ever sees an updated frame which makes for smoother animation.

To render the new frame we fill two Rects with data. The first, sr, is the Source rect and is the rectangle used to define where and how much of the graphic data in source surface to use. The dr rect, Destination Rect, is the same but defines where and how much to blit on the destination surface. We want to blit the entire background image to the screen so we set the source rect to start at 0,0 (the topleft corner of the background texture) and it's full width and height. Then we set the dest rect to the same so the background image fills the entire screen. After the rects are setup we can then blit() the background texture onto our screen.

After the background is drawn we then ask the SpriteManager to tell every sprite to render itself onto the screen.

process_events

private void process_events () {
    Event event;
    while (Event.poll (event) == 1) {
        switch (event.type) {
        case EventType.QUIT:
            this.done = true;
            break;
        case EventType.KEYDOWN:
            this.on_keyboard_event (event.key);
            break;
        }
    }
}

Finally we get to process_events. This is the final function call we haven't covered in the main loop. For now, this is a very simple call that only checks for two events: QUIT and KEYDOWN. If the QUIT event is received (when the window is closed) then we set done to true which will exit the main loop and shutdown the program. If it is a key press then pass the key that is pressed to the on_keyboard_event handler which will deal with the keypress.

on_keyboard_event

private void on_keyboard_event (KeyboardEvent event) {
        if(event.keysym.sym == KeySymbol.ESCAPE)
                this.done = true;
}

Since we are just getting things setup this is a very simple handler. We simply check if the Escape key was pressed and set done to true if so. Again, this will break us out of the main loop and quit the program.

And with that, we have gone over the entire main Brickval class. It is very simple for now, but it is a good base on which to build. It is more than enough to start doing some basic work with graphics and sprites which is what we have been working towards. The rest of this article will focus on the Sprite and SpriteManager classes in detail.

src/spritemanager.vala

The SpriteManager class is a very simple class for now (notice a trend?). It's purpose is to keep an array of all sprites and to execute functions on them as needed. This lets us deal with an arbitrary amount of sprites without having to worry about manually calling update and render on each and every sprite. Furthermore, later in the article series, the SpriteManager class will have much more functionality which will allow for sprite render ordering and collision detection between groups of sprites among other things.

With that said, lets start looking at the sprite manager class.

using GLib;
using SDL;
using Gee;

public class SpriteManager : Object {

        private ArrayList<Sprite> sprites;

        construct {
                sprites = new ArrayList<Sprite>(direct_equal);
        }

This should now look familiar to you. We declare the packages we are using for the class and then the class itself. Again, we derive from Object so we can take advantage of Vala's object features. We only have a single field which is an ArrayList that holds the managed sprites and we initialize this array in our construct method.

Add

public int Add(string file) {
        Sprite newSprite = new Sprite(file);

        if(!sprites.add(newSprite))
        {
                GLib.warning("Could not add new sprite %s", file);
                return -1;
        }

        return sprites.index_of(newSprite);
}

The Add() method takes a string that represents the path to the graphics file that will be used for the sprite. We then instantiate a new Sprite object and try to add it to our ArrayList. If this fails, then we issue a warning and return -1. Upon a successful addition of a new sprite to the ArrayList we return the index of that sprite.

Remove

        public void Remove(int index) {
                if(index < 0 || index > sprites.size - 1)
                {
                        GLib.warning("SpriteManager: Index out of bounds (Index %d)", index);
                        return;
                }

                sprites.remove_at(index);
        }

The Remove() method does the inverse of the Add() method and removes a sprite from the ArrayList. We check to make sure the passed index is in bounds and if so we remove the sprite. This method should call a Dispose() method on the Sprite so the sprite can do any necessary cleanup, but since the Sprite class doesn't currently have such a method we cannot call it. At this point in the article series we are not removing sprites so this isn't a huge deal but I would not use this article's implementation of these classes in any real project yet because of limitations like this.

Get

public Sprite Get(int index) {
        if(index < 0 || index > sprites.size - 1)
        {
                GLib.warning("SpriteManager: Index out of bounds (Index: %d)", index);
                return (Sprite)null;
        }

        return sprites.get(index);
}

Get simply takes an index, checks if it is inbounds, and returns the instance of the Sprite that is stored in the array if so. If it is not inbounds we issue a warning and return null. Note that we have to cast null to a Sprite type. Vala is stronger typed than C in some areas and this is one of them.

Update

public void Update() {
        Iterator<Sprite> iter = sprites.iterator();

        while(iter.next())
                iter.get().Update();
}

The Update() method simply iterates through the ArrayList of Sprites and calls each sprite's Update method. Since we are using Gee.ArrayList as our sprite list we have access to the nice generic Iterator class. Since the iterator 'knows' what type it contains we can do things like call instance methods directly from get() without having to cast it to another type first.

Render

public void Render(SDL.Surface render_surface) {
        Iterator<Sprite> iter = sprites.iterator();

        while(iter.next())
                iter.get().Render(render_surface);
}

The Render() method does essentially the exact same thing as Update() but instead calls Render() on each sprite. This is the method on the sprite that tells it to draw itself. We pass our screen to this method which is then passed to every sprite and each sprite renders itself directly onto the screen.

And that finishes up the SpriteManager class. I told you it was simple so far! This gives us a lot of power even in its current state: for example we can have an arbitrary number of sprites and do not have to deal with updating and rendering each of them singly. However like it was mentioned earlier this class will grow in power as the article series progresses.

src/sprite.vala

The Sprite class is the heart of our demonstration application. This class encapsulates all necessary information a sprite needs to function and display itself. Much of this class is simply providing a way to get and set information and Vala provides an extremely easy to use and powerful way to define properties like this.

As we have done for the rest of the article we will begin with the included packages, fields, and constructor.

using GLib;
using SDL;
using SDLImage;

public class Sprite : Object {
        /* Private Fields */
        private SDL.Surface Image;
        private int Delay;

        /* Properties */

        public Rect SourceRect { public get; private set; }
        public Rect DestRect { public get; private set; }

        public bool HasSheet { public get; private set; }
        public bool IsHorizLayout { public get; private set; }
        public bool Visible { public get; public set; }
        public bool Animated { public get; private set; }

        public int X { public get; public set; }
        public int Y { public get; public set; }
        public float XV { public get; public set; }
        public float YV { public get; public set; }
        public float XVMax { public get; public set; }
        public float YVMax { public get; public set; }
        public float XVMin { public get; public set; }
        public float YVMin { public get; public set; }
        public float XAccel { public get; public set; }
        public float YAccel { public get; public set; }
        public float XAccelMax { public get; public set; }
        public float YAccelMax { public get; public set; }
        public float XAccelMin { public get; public set; }
        public float YAccelMin { public get; public set; }

        public int Width { public get; private set; }
        public int Height { public get; private set; }
        public int CellCount { public get; private set; }
        public int CurrentCell { public get; private set; }

        public uint AnimateDelay { public get; public set; }
        public uint AnimateStartCell { public get; public set; }
        public uint AnimateEndCell { public get; public set; }

        public string GraphicsFile { public get; private set construct; }

        /* Constructor */
        construct {
                X = Y = XV = YV = 0;
                XVMin = YVMin = -10;
                XVMax = YVMax = 10;
                XAccel = YAccel = 0;
                XAccelMax = YAccelMax = 5;
                XAccelMin = XAccelMin = -5;

                Delay = 0;
                Visible = true;
                HasSheet = false;
                IsHorizLayout = true;

                this.Image = SDLImage.load(GraphicsFile);

                if(Image == null)
                        GLib.warning("Could not load image data for sprite.");
                else {
                        Height = Image.h;
                        Width = Image.w;
                        CellCount = 1;
                        CurrentCell = 0;
                }
        }

        public Sprite(string file) {
                this.GraphicsFile = file;
        }

Whoa! That's alot! Well yes but the sprite class does alot more on instantiation than our other classes have done up to this point. First we create two private fields, one holds the Image data for the sprite, and the other is used by the animation system.

Next we come to a whole lot of definitions that demonstrate a very powerful feature of Vala. These are automatic property generators that create a full GObject property for us without us having to define a field for them.

The following is a 'standard' way of constructing a property:

private int mInt;

public int MyProperty {
    get { return mInt; }
    set { mInt = value; }
}

In this case we have a private int field, mInt, and a public Property that wraps around this private field. You can use this syntax in Vala as well, but Vala provides the following way to do this automatically:

public int MyProperty{ get; set; }

This will produce the same code as the first version but is more compact and allows you to not have to maintain separate private fields and property fields. Furthermore, Vala allows getters and setters to have access modifiers. What if you wanted a property to be publicly gettable but only settable by the class itself? You would declare the property like this:

public bool Animated { public get; private set; }

This lets any external class get the value of Animated but only the Sprite class itself can set it's value.

Another thing to note in the property definitions is the definition of the GraphicsFile property:

public string GraphicsFile { public get; private set construct; }

The setter has an additional construct modifier. Since the Vala string type is a reference type (that is, it points to a reference of a string object) it is treated a bit differently than the other types when it comes to properties. Normally, construct is called after all the properties in the class constructor are set. However, with reference types this is not the case. Reference typed properties by default do not set themselves until after construct is called. Since we want to set the GraphicsFile property in the class' instance constructor and use that value in construct we use this modifier to disable this behavior and ensure that GraphicsFile is set during the construct invocation.

The construct method is fairly self explanatory. We set default values for various properties and try to load the graphics data. If the data cannot be loaded we issue a warning, otherwise we set some additional properties that depend upon the proper loading of the graphics data.

The properties in the sprite class define the behavior of the sprite and are described below:

  • SourceRect

    A rectangle used to define which piece of the sprite to render.

    DestRect

    The destination location of the sprite.

    HasSheet

    True if the sprite data is a spritesheet.

    IsHorizLayout

    True if the spritesheet is layed out horizontally

    Visible

    Sets the visibility of the sprite

    Animated

    Determines if the sprite uses multiple frames for animation.

    X

    The current X coordinate of the sprite.

    Y

    The current Y coordinate of the sprite.

    XV

    The current X Velocity of the sprite.

    YV

    The current Y Velocity of the sprite.

    XVMax

    The maximum allowed value of XV

    YVMax

    The maximum allowed value of YV

    XVMin

    The minimum allowed value of XV

    YVMin

    The minimum allowed value of YV

    XAccel

    The X Acceleration factor (applied every frame to XV)

    YAccel

    The Y Acceleration factor (applied every frame to YV)

    XAccelMax

    The maximum allowed value of XAccel

    YAccelMax

    The maximum allowed value of YAccel

    XAccelMin

    The minimum allowed value of XAccel

    YAccelMin

    The minimum allowed value of YAccel

    Width

    The width of a single frame of a sprite (Width == the graphic file width if not a spritesheet)

    Height

    The height of a single frame of a sprite (Height == the graphic file height if not a spritesheet)

    CellCount

    How many frames are in the spritesheet

    CurrentCell

    The currently selected frame in the spritesheet

    AnimateDelay

    The number of frames which must pass before the next frame in the animation cycle is selected.

    AnimateStartCell

    The frame in the spritesheet where the animation loop begins.

    AnimateEndCell

    The frame in the spritesheet where the animation loop stops.

    GraphicsFile

    A string containing the path to the image data.

    The methods in the Sprite class operate on these properties to complete their function. Lets start looking at some of the methods now.

InitSpriteSheet

public void InitSpriteSheet(int cell_width, int cell_height,
     int cell_count, bool horizontal) {
        Width = cell_width;
        Height = cell_height;
        CellCount = cell_count;
        IsHorizLayout = horizontal;
        HasSheet = true;
}

InitSpriteSheet simply sets some properties and HasSheet to true. But what does that mean? What is it actually doing? To answer this we need to discuss how sprites are layed out in an image file.

The following is a snippet of data/sprites/bricks.png: Sprite Sheet

As you can see the file contains, in a long strip, all possible brick types. Not only that but these are layed out horizontally and have no space in between them. When sprite data is bundled like this into a single file it is called a spritesheet. This is how many games store their sprite information. The spritesheet could just as easy be layed out in a vertical orientation, with each different sprite frame stacked on top of one another. And this is what IsHorizLayout tracks: are they stored side by side or on top of each other.

The other information this method sets is the CellCount, and Width and Height of a single frame. The CellCount is simple the total amount of frames in the sprite sheet (in the case of data/sprites/bricks.png the value is 21). Width and Height describe how big each frame is. Using these numbers we can, in effect, describe a 'window' into the spritesheet so we can pull individual frames out of it and only display them. How exactly we do this is shown in the discussion of the Render method later.

Finally, we set HasSheet to true to tell the Render method that the image data is a sprite sheet. If HasSheet is false, Render will simply render the entire sprite data to the screen.

SetCurrentCell

public void SetCurrentCell(int cell) {
        if(cell < 0)
        {
                GLib.warning("Tried to set a negative cell.");
                return;
        }

        CurrentCell = cell % CellCount;
}

This is a simple method that sets which sprite frame in the spritesheet is the currently displayed one. We do some bounds checking first to ensure we're getting values that make sense. We modulo the passed value by the CellCount to ensure that the passed value is within range of the number of sprite frames in the sheet. Modulo (%) is a mathematical operation that operates on integers, it gives you the remainder of a division. If the remainder is 0 then it divided cleanly, otherwise it returns whatever was left over.

IsCollidingWith

public bool IsCollidingWith(Sprite s) {
        return !(s.DestRect.x > DestRect.x + DestRect.w
                        || s.DestRect.x + s.DestRect.w < DestRect.x
                        || s.DestRect.y > DestRect.y + DestRect.h
                        || s.DestRect.y + s.DestRect.h < DestRect.h);
}

This is a very basic rectangle intersection method. It checks to see if the passed Sprite is touching the current sprite. This is about as simple as collision response gets and isn't the most reliable way of going about things. But it's good to have in the class to just provide some rudimentary capability. Collision response is really a huge topic and will get it's own article later in the series.

StartAnimate StopAnimate

public void StartAnimate(uint start_cell, uint end_cell, uint delay) {
        AnimateStartCell = start_cell;
        AnimateEndCell = end_cell;
        AnimateDelay = delay;

        Animated = true;
}

public void StopAnimate() {
        Animated = false;
}

I'm grouping these two methods together since StopAnimate is so simple. StartAnimate takes a range (from start_cell to end_cell) and a delay value. The delay is in number of frames and controls how many frames must pass before the sprite will switch to the next cell. Notice that for now this animation support is rather simple. We only support looping animations of contiguous sprite frames. Also there's no built in logic to 'group' animations into useful arrangments (like say, WalkingUp, WalkingDown, etc). We will be expanding this capability in later articles.

Update

public void Update() {
        if(XAccel > XAccelMax)
                XAccel = XAccelMax;

        if(YAccel > YAccelMax)
                YAccel = YAccelMax;

        if(XAccel < XAccelMin)
                XAccel = XAccelMin;

        if(YAccel < YAccelMin)
                YAccel = YAccelMin;

        XV += XAccel;
        YV += YAccel;

        if(XV > XVMax)
                XV = XVMax;

        if(YV > YVMax)
                YV = YVMax;

        if(XV < XVMin)
                XV = XVMin;

        if(XV < YVMin)
                YV = YVMin;

        X += XV;
        Y += YV;
}

Now we get to one of the core methods of the sprite class: Update. This updates the sprite's X and Y properties each frame based upon the current X and Y velocities. To make a sprite move using this system you simply set the velocities to the values you want and it moves on its own. We also have the concept of acceleration. This adds a value to each velocity per frame and lets you smoothly speed up or slow down a sprite's motion by a constant amount.

Right now, X and Y are integer values because that is what SDL requires for blitting. This is a major limitation since it limits the resolution of our velocities and accelerations. There is also no easy way to rotate or blend a sprite using the standard SDL graphics mode. These limitations will be overcome when we talk about using OpenGL with Vala and SDL.

Render

public void Render(SDL.Surface render_surface) {
        if(Image == null) {
                GLib.warning("Tried to render NULL sprite data.");
                return;
        }

        if(Animated)
                if(++Delay > AnimateDelay) {
                        Delay = 0;

                        if(++CurrentCell > (AnimateEndCell - 1) % CellCount)
                                CurrentCell = AnimateStartCell % CellCount;
                }

        if(IsHorizLayout) {
                SourceRect.x = (int16) (CurrentCell * Width);
                SourceRect.y = 0;
        } else {
                SourceRect.x = 0;
                SourceRect.y = (int16) (CurrentCell * Height);
        }

        SourceRect.w = (uint16) Width;
        SourceRect.h = (uint16) Height;

        DestRect.x = (int16) X;
        DestRect.y = (int16) Y;
        DestRect.w = SourceRect.w;
        DestRect.h = SourceRect.h;

        Image.blit(SourceRect, render_surface, DestRect);
}

This is the primary function of the sprite. It takes an SDL surface to blit to (in our case we pass it our screen surface) and then figures out which sprite frame to blit. If the Image data is null (because it did not load correctly) then we issue a warning and bail out. Next you see all that discussion about Animation reduced to 5 lines of code. We use our private Delay field as a counter and check it against AnimateDelay. Once it is larger than AnimateDelay we increment CurrentCell. If CurrentCell is larger than AnimateEndCell we set it back to AnimateStartCell and the animation loop begins anew.

You'll notice we modulo these values by CellCount again. This is to ensure that even if degenerate values are set for AnimateStartCell and AnimateEndCell the program will still animate the sprite (albeit probably not in a useful fashion.)

Next we check if the layout is horizontal or vertical. If the sprite isn't a sprite sheet then it is treated here as a horizontally layed out sprite sheet with one frame. The only difference between a horizontal and a vertical sprite sheet is which coordinate we use to move the 'window' to the correct sprite frame. Horizontal sheets use the X coordinate and Vertical use the y.

After our SourceRect's X and Y values are set to the correct spot we use our Width and Height properties to finish describe the sprite frame we want from the image data. Then we create a DestRect that describes a rectangle with the sprite's current X/Y coordinates and size. Finally, we blit that to the screen and our sprite is displayed.

Running the Program

If you haven't already tried to compile the source you might be wondering how we go about that. The first thing you need to do is run the autogen.sh script in the top-level directory of the project:

cd ~/dev/brickval
./autogen.sh

You can pass parameters to autogen.sh, most notably --prefix. --prefix lets you specify where you want the program and it's data to be installed. The default value of prefix is /usr/local. If you use the default value then the executable will be stored in /usr/local/bin and the data will be stored in /usr/share/brickbal/* where * is sprites or backgrounds respectively. You only need to run this script once, it does a bunch of autotools setup and gets the project ready to go.

Once this script is complete you simple type:

make

...and then

make install

...to install it. You must run make install at least once to get the graphics data stored into the right place. But if you haven't added more graphics files then during development you can feel free to run the executable directly from the src subdirectory. The reason for needing to install at least once is due to the way we lookup where our data is at in the application (config.vapi!). So if there is no initial install then that data doesn't exist in the spot the program thinks it does.

Once it's installed you can run the program by either typing

brickval

...if you installed it into your PATH. Or if you haven't,

./src/brickval

You should see our demo come up and look something like the following:

Sprite Demo

Now just imagine all those bricks moving around...

Conclusion

We've come pretty far and quickly. We can display, move, and animate an arbitrary number of sprites plus we've layed out the groundwork for some more advanced sprite manipulation in the future. Right now we are being limited in what we can do by SDL's default graphics mode, so in the next article we will be changing that to use OpenGL as our video 'engine'. This will let us do a ton more things with the bonus of being hardware accelerated (so long as you have an accelerated videocard).

Until then, play with the sprite class a bit! See what happens when you add the line

s.StartAnimate(0, 6, rand.int_range(1,10));

to InitSprites() in src/brickval.vala for starters.

Until next time!

Projects/Vala/GameDevelopmentSeries/Sprites (last edited 2013-11-22 16:48:31 by WilliamJonMcCann)