Creating a 2D Skeletal System
Fri 12 November 2021It's finally time. If you've read the Chocolate Game Engine article, you'll know that I talked briefly about the sprite animation I had in my first game engine. It was a simple system that cycled through the sprites on a spritesheet, and I animated a character to move around the screen. However, now it is much later, and I want to make it better. How about sprites that have smooth animations at a reasonably high resolution?
Returning to the Classics
As I've stated many times before, I take a lot of inspiration from the Epic Battle Fantasy series of games, and that is most certainly the case with sprite animation. I never paid it much attention as a kid, but now that I'm a slightly bigger kid, I realized it was really impressive for the time.
Alright, let's get to coding. Although it looks really nice, it can be broken down into a couple of components. I cheated a little bit by extracting the flash project and looking at all the sprites, but I probably could've figured out that it has a "master" sprite which is the parent for all the other subsprites, which can be attached to points on that master sprite. Those subsprites can then be rotated to make cool things happen.
Transforming
So, I need to be able to position sprites around the screen and scale them appropriately. Cool, my engine already supports that. I did however, have to implement rotation for 2D objects in my engine. I decided to do it in the shader for the time being, and I don't really see a reason it shouldn't be there. For bonus points, I made that roation function use an anchor point as well which will help out later. To be honest, I'm failing my Calculus teacher by not using some basic knowledge of trigonometry to figure this out myself, but Stack Overflow certainly speeds things up anyway, so that's where this came from.
Rotation Code
vec2 rotate(vec2 v, vec2 p, float a){
float s = sin(a);
float c = cos(a);
v.x -= p.x;
v.y -= p.y;
float newX = v.x * c - v.y * s;
float newY = v.x * s + v.y * c;
v.x = newX + p.x;
v.y = newY + p.y;
return v;
}
Keyframes
Now, time to implement time (haha.) This probably took the most effort to set up, but mostly because I was cleaning up a bunch of stuff, even though it still remains quite messy. I decided that keyframes would be the easiest way to manipulate the sprite locations, so that's exactly what I set up. I have an animation clock ticks 1000 times per second from 0, and it will look for the current sprite keyframe and interpolate the position from the current keyframe to the next.
Keyframe Interpolation Code
/* Won't need to account for position if sprite is a child. */
if ( !rSprite.aChild )
rSprite.apSprite->aTransform.aPos = glm::mix( pCurKeyFrame->aInitialTransform.aPos, pNextKeyFrame->aInitialTransform.aPos,
( float )( aAnimClock - pCurKeyFrame->aStart ) /
( pNextKeyFrame->aStart - pCurKeyFrame->aStart ) );
rSprite.apSprite->aTransform.aAng = glm::mix( pCurKeyFrame->aInitialTransform.aAng, pNextKeyFrame->aInitialTransform.aAng,
( float )( aAnimClock - pCurKeyFrame->aStart ) / ( pNextKeyFrame->aStart - pCurKeyFrame->aStart ) );
rSprite.apSprite->aTransform.aAnchor = glm::mix( pCurKeyFrame->aInitialTransform.aAnchor, pNextKeyFrame->aInitialTransform.aAnchor,
( float )( aAnimClock - pCurKeyFrame->aStart ) / ( pNextKeyFrame->aStart - pCurKeyFrame->aStart ) );
It would be impossible to make these sprites without an interface to edit keyframes, so with some simple ImGui windows, we were up and making sprites!
Sprite Editing Demo
Keyframe Demo
Conclusion
The keyframe demo is a little bit cheated. I looped the video and sped it up, but it still shows the type of animation that can be done in my engine now! (Of course, granted my animation work isn't sloppy like is rushed into the video.) I'm really happy with the progress I made these past two days, and I'm looking forward to writing some actual game code for Crosslight now.