As the title of this week’s post suggests, the changes this week are less for ts-tennis and more for ts-game-engine and giving it an ability to use sprites and sprite sheets. This was technically already semi-possible because the rendering code can display images just fine, but I wanted to be able to do some animating, and the idea of having a bunch of images and flipping between them manually made me break out in a cold sweat based purely on how crufty that seems.
This snowballed a tad bit, as touching engine code tends to do (I seem to be a natural born engineer), but various features tend to cross each others path with sprite sheets at the nexus. Well, in my brain they do. Your mileage may vary. Thus, besides sprite sheets, animations, origins and rotations are also in. All because what I really wanted to do was implement collision detection. What?
The first thing I did was add in a new set of blitting methods to the Renderer interface that allow you to blit a part of an image instead of the whole thing, which is core to the entire dynamic of a sprite sheet. This went pretty quickly although I did have a bit of a brief hangup when I didn’t pay enough attention to the documentation and assumed the parameters came in a different order. In any case, there is now a complement to all of the blitting functions, so you can blit a part centered, rotated, or both.
With that in place, the idea of a SpriteSheet class loading up an image and then treating it as multiple sub images is quite easy. Since preloading images always shares the same image tag, the class preloads the image itself so that it can set up a handler to know when the image is loaded; the dimensions of the image are a needed part of the process. The sheet can be split by indicating the size of each sprite or by specifying how many sprites across and down there are and letting the size be calculated.
Internally the class assumes that all sprites in a sprite sheet are the same size and are displayed as a grid, but I tried to give enough forethought to the method design that a simple subclass could implement something like a texture atlas with no problems. One last design issue is that there is no way to determine at game setup time the dimensions of sprites in the sprite sheet because until the preload happens, the sheet doesn’t (always) know. This would require a callback similar to what is used to indicate that an image preload is done, but I left that for implementation when I actually need it.
With this in place, I added some new properties to the Actor class; sheet and sprite. The first is a reference to a sprite sheet and the second is a number that indicates what frame to display. The default render() method uses this information (if it exists) to display the sprite. This should obviate the need for most uses of Actor to do its own blitting.
As I can’t leave well enough alone, I also implemented other properties, origin and angle. The origin property is an offset from the top left of the bounding rectangle that is used to indicate where the Actor position should be located, which allows for treating the position as the center of a ball or the feet of a bipedal character and so on. The other property, angle just specifies a rotation angle to apply. These were easy enough to apply and were the only things I could think of (besides animation) that would require custom rendering.
Lastly, I created another class, AnimationList, which is very simple; It takes a range of frames and assigns them to a textual name along with some other properties such as what FPS the animation should run at, whether it should loop, and whether it should ping-pong (play front to back to front). There is also an update() method which should be invoked on every game tick, which returns what the current animation frame should be for display. The Actor class update() method invokes this if there is an animation playing (and a sprite sheet to display with it).
With these changes in place, it is now possible to very easily get animations and rendering going on an actor without having to write so much custom code, which seems like a good way to go; any time the underlying code does what you want, the quicker you can prototype things, which is totally the point.
My next plans are very simple collision detection or (more likely) methods that could be used to do it (e.g. “does this rect overlap this rect” or “does this line intersect this line, and if so, where?”). I don’t currently have a clear idea in my head about how this would be automated, but in the spirit of not being bogged down in the minutiae of design work it will be enough to be able to do the collision detection at all, and then see how I end up fitting it in to see what the best way to go about this is.
In fact this is the code change that is actually required for the next steps in ts-tennis (better collision detection) but I managed to get there the long way by deciding that I wanted there to be a definite origin point to use as the basis for this, and if there’s going to be an origin, you should be able to render an image based on it, and if I’m going to do that, it should be a sprite, and I guess I would want to be able to rotate the image around and probably change it.
It’s just THAT easy to get side tracked when you love solving puzzles.