Animated sprites!

This commit is contained in:
Scott Duensing 2023-12-03 20:04:31 -06:00
parent 89b8bb427d
commit e1a11f49d4
2 changed files with 277 additions and 22 deletions

View file

@ -12,6 +12,8 @@ New Features
sprite's center as the anchor instead of the upper right. This is highly
useful when dealing with rotated sprites.
- Animated sprites! Both animated GIF and WEBP images are supported.
- Sprite anti-aliasing, scaling and rotation! Optional separate X & Y scaling.
- Video anti-aliasing, scaling and rotation! Optional separate X & Y scaling.

View file

@ -107,13 +107,19 @@ typedef struct SpriteS {
int32_t id;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpadded"
IMG_Animation *animation;
SDL_Surface *originalSurface;
SDL_Surface *surface;
#pragma GCC diagnostic pop
double angle;
double scaleX;
double scaleY;
int smooth;
int32_t smooth;
int32_t currentFrame;
bool loop;
bool animating;
uint32_t lastTick;
uint32_t ticks;
UT_hash_handle hh;
} SpriteT;
@ -142,6 +148,7 @@ typedef struct VideoS {
bool wasPlayingBeforePause;
SDL_Texture *texture;
SDL_Surface *surface;
SDL_Surface *rotatedZoomedSurface;
double angle;
double scaleX;
double scaleY;
@ -389,13 +396,19 @@ int32_t apiSoundFullStop(lua_State *L);
int32_t apiSoundUnload(lua_State *L);
int32_t apiSpriteDraw(lua_State *L);
int32_t apiSpriteGetFrame(lua_State *L);
int32_t apiSpriteGetHeight(lua_State *L);
int32_t apiSpriteGetWidth(lua_State *L);
int32_t apiSpriteIsPlaying(lua_State *L);
int32_t apiSpriteLoad(lua_State *L);
int32_t apiSpriteLoop(lua_State *L);
int32_t apiSpritePause(lua_State *L);
int32_t apiSpritePlay(lua_State *L);
int32_t apiSpriteQuality(lua_State *L);
int32_t apiSpriteRotate(lua_State *L);
int32_t apiSpriteRotateAndScale(lua_State *L);
int32_t apiSpriteScale(lua_State *L);
int32_t apiSpriteSetFrame(lua_State *L);
int32_t apiSpriteUnload(lua_State *L);
int32_t apiVideoDraw(lua_State *L);
@ -1936,6 +1949,7 @@ int32_t apiSpriteDraw(lua_State *L) {
int32_t id = -1;
double d = 0;
bool center = false;
bool newFrame = false;
SpriteT *sprite = NULL;
SDL_Rect dest;
int ox;
@ -1985,6 +1999,36 @@ int32_t apiSpriteDraw(lua_State *L) {
//utilSay("spriteDraw: x=%d y=%d c=%d id=%d", dest.x, dest.y, center, id);
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteDraw", "No sprite at index %d in apiSpriteDraw.", id);
// Figure out animation frame, if needed
if (sprite->animation != NULL && sprite->animating) {
// Find time passed
sprite->ticks += SDL_GetTicks() - sprite->lastTick;
sprite->lastTick = SDL_GetTicks();
// Find desired frame
while (sprite->animating) {
if (sprite->animation->delays[sprite->currentFrame] < sprite->ticks) {
sprite->ticks -= sprite->animation->delays[sprite->currentFrame];
sprite->currentFrame++;
newFrame = true;
if (sprite->currentFrame >= sprite->animation->count) {
if (sprite->loop) {
sprite->currentFrame = 0;
} else {
sprite->currentFrame = sprite->animation->count - 1;
sprite->animating = false;
}
}
} else {
break;
}
}
if (newFrame) {
SDL_FreeSurface(sprite->originalSurface);
sprite->originalSurface = surfaceCopy(sprite->animation->frames[sprite->currentFrame]);
SDL_FreeSurface(sprite->surface);
sprite->surface = rotozoomSurfaceXY(sprite->originalSurface, 360 - sprite->angle, sprite->scaleX, sprite->scaleY, sprite->smooth);
}
}
if ((n == 3) || (n == 4)) {
// No scaling, find width
dest.w = sprite->surface->w;
@ -2016,6 +2060,34 @@ int32_t apiSpriteDraw(lua_State *L) {
}
int32_t apiSpriteGetFrame(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t result = -1;
int32_t id = -1;
double d;
SpriteT *sprite = NULL;
if (n == 1) {
if (lua_isstring(L, 1)) {
d = lua_tonumber(L, 1); id = (int32_t)d;
// Get our sprite structure
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteGetFrame", "No sprite at index %d in apiSpriteGetFrame.", id);
result = sprite->currentFrame;
}
}
if (result >= 0) {
luaTrace(L, "spriteGetFrame", "%d", result);
} else {
luaDie(L, "spriteGetFrame", "Failed!");
}
lua_pushinteger(L, result);
return 1;
}
int32_t apiSpriteGetHeight(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t result = -1;
@ -2072,18 +2144,67 @@ int32_t apiSpriteGetWidth(lua_State *L) {
}
int32_t apiSpriteIsPlaying(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t result = -1;
int32_t id = -1;
double d;
SpriteT *sprite = NULL;
if (n == 1) {
if (lua_isstring(L, 1)) {
d = lua_tonumber(L, 1); id = (int32_t)d;
// Get our sprite structure
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteIsPlaying", "No sprite at index %d in apiSpriteIsPlaying.", id);
result = sprite->animating;
}
}
if (result >= 0) {
luaTrace(L, "spriteIsPlaying", "%d", result);
} else {
luaDie(L, "spriteIsPlaying", "Failed!");
}
lua_pushboolean(L, result);
return 1;
}
int32_t apiSpriteLoad(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t result = -1;
const char *name = NULL;
SpriteT *sprite = NULL;
int32_t x = 0;
if (n == 1) {
if (lua_isstring(L, 1)) {
sprite = (SpriteT *)calloc(1, sizeof(SpriteT));
if (!sprite) luaDie(L, "spriteLoad", "Unable to allocate new sprite.");
name = lua_tostring(L, 1);
// Try to load requested file as an animation first
sprite->animation = IMG_LoadAnimation(name);
if (sprite->animation) {
// We got something
if (sprite->animation->count < 2) {
// Only one frame - meh
IMG_FreeAnimation(sprite->animation);
sprite->animation = NULL;
} else {
// Set up our transparency
for (x=0; x<sprite->animation->count; x++) {
SDL_SetColorKey(sprite->animation->frames[x], true, 0);
}
// Load first frame onto surface
sprite->originalSurface = surfaceCopy(sprite->animation->frames[0]);
}
}
if (sprite->animation == NULL) {
// Load as single image
sprite->originalSurface = IMG_Load(name);
}
if (!sprite->originalSurface) luaDie(L, "spriteLoad", "%s", IMG_GetError());
SDL_SetColorKey(sprite->originalSurface, true, 0);
sprite->surface = surfaceCopy(sprite->originalSurface);
@ -2095,7 +2216,7 @@ int32_t apiSpriteLoad(lua_State *L) {
}
}
if (sprite->surface) {
if (sprite->originalSurface) {
luaTrace(L, "spriteLoad", "%d %s", result, name);
} else {
luaDie(L, "spriteLoad", "Failed!");
@ -2106,6 +2227,89 @@ int32_t apiSpriteLoad(lua_State *L) {
}
int32_t apiSpriteLoop(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t id = -1;
int32_t l;
double d;
SpriteT *sprite = NULL;
if (n == 2) {
if (lua_isboolean(L, 1)) {
if (lua_isnumber(L, 2)) {
d = lua_toboolean(L, 1); l = (int32_t)d;
d = lua_tonumber(L, 2); id = (int32_t)d;
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteLoop", "No sprite at index %d in apiSpriteLoop.", id);
sprite->loop = l;
}
}
}
if (id >= 0) {
luaTrace(L, "spriteLoop", "%d %d", id, sprite->loop);
} else {
luaDie(L, "spriteLoop", "Failed!");
}
return 0;
}
int32_t apiSpritePause(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t id = -1;
double d;
SpriteT *sprite = NULL;
if (n == 1) {
if (lua_isnumber(L, 1)) {
d = lua_tonumber(L, 1); id = (int32_t)d;
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spritePause", "No sprite at index %d in apiSpritePause.", id);
sprite->animating = false;
}
}
if (id >= 0) {
luaTrace(L, "spritePause", "%d %d", id, sprite->animating);
} else {
luaDie(L, "spritePause", "Failed!");
}
return 0;
}
int32_t apiSpritePlay(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t id = -1;
double d;
SpriteT *sprite = NULL;
if (n == 1) {
if (lua_isnumber(L, 1)) {
d = lua_tonumber(L, 1); id = (int32_t)d;
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spritePlay", "No sprite at index %d in apiSpritePlay.", id);
// Do we need to reset the tick counter?
if (sprite->animating == false) {
sprite->lastTick = SDL_GetTicks();
}
sprite->animating = true;
}
}
if (id >= 0) {
luaTrace(L, "spritePlay", "%d %d", id, sprite->animating);
} else {
luaDie(L, "spritePlay", "Failed!");
}
return 0;
}
int32_t apiSpriteQuality(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t id = -1;
@ -2254,6 +2458,38 @@ int32_t apiSpriteScale(lua_State *L) {
}
int32_t apiSpriteSetFrame(lua_State *L) {
int32_t n = lua_gettop(L);
int32_t id = -1;
int32_t f;
double d;
SpriteT *sprite = NULL;
if (n == 2) {
if (lua_isnumber(L, 1)) {
if (lua_isnumber(L, 2)) {
d = lua_tonumber(L, 1); f = (int32_t)d;
d = lua_tonumber(L, 2); id = (int32_t)d;
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteSetFrame", "No sprite at index %d in apiSpriteSetFrame.", id);
if ((f >= 0) && (f < sprite->animation->count)) {
sprite->currentFrame = f;
sprite->ticks = 0;
}
}
}
}
if (id >= 0) {
luaTrace(L, "spriteSetFrame", "%d %d", id, sprite->currentFrame);
} else {
luaDie(L, "spriteSetFrame", "Failed!");
}
return 0;
}
int32_t apiSpriteUnload(lua_State *L) {
int32_t n = lua_gettop(L);
bool result = false;
@ -2268,8 +2504,9 @@ int32_t apiSpriteUnload(lua_State *L) {
HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteUnload", "No sprite at index %d in apiSpriteUnload.", id);
HASH_DEL(_global.spriteList, sprite);
SDL_FreeSurface(sprite->surface);
SDL_FreeSurface(sprite->originalSurface);
if (sprite->surface) SDL_FreeSurface(sprite->surface);
if (sprite->originalSurface) SDL_FreeSurface(sprite->originalSurface);
if (sprite->animation) IMG_FreeAnimation(sprite->animation);
free(sprite);
result = true;
}
@ -2296,7 +2533,6 @@ int32_t apiVideoDraw(lua_State *L) {
VideoT *video = NULL;
bool center = false;
SDL_Rect dest;
SDL_Surface *surface = NULL;
// videoDraw(id, x1, y1, x2, y2) - Simple/Stretched draw
// videoDraw(id, x1, y1, c) - Scaled/Rotated draw
@ -2340,17 +2576,23 @@ int32_t apiVideoDraw(lua_State *L) {
// Render frame into overlay
if ((dest.w == 0) && (dest.h == 0)) {
surface = rotozoomSurfaceXY(video->surface, 360 - video->angle, video->scaleX, video->scaleY, video->smooth);
dest.w = surface->w;
dest.h = surface->h;
if (frame != video->lastFrame) {
if (video->rotatedZoomedSurface != NULL) {
SDL_FreeSurface(video->rotatedZoomedSurface);
video->rotatedZoomedSurface = NULL;
}
video->rotatedZoomedSurface = rotozoomSurfaceXY(video->surface, 360 - video->angle, video->scaleX, video->scaleY, video->smooth);
SDL_SetColorKey(video->rotatedZoomedSurface, true, 0);
}
dest.w = video->rotatedZoomedSurface->w;
dest.h = video->rotatedZoomedSurface->h;
// Scaled/Rotated draw
if (center) {
// Move video so the drawing coordinate is the center of the video
dest.x -= dest.w * 0.5;
dest.y -= dest.h * 0.5;
}
SDL_BlitSurface(surface, NULL, _global.overlay, &dest);
SDL_FreeSurface(surface);
SDL_BlitSurface(video->rotatedZoomedSurface, NULL, _global.overlay, &dest);
} else {
// Simple/Stretched draw
if (SDL_BlitScaled(video->surface, NULL, _global.overlay, &dest) != 0) luaDie(L, "videoDraw", "%s", SDL_GetError());
@ -2646,8 +2888,10 @@ int32_t apiVideoLoad(lua_State *L) {
HASH_ADD_INT(_global.videoList, id, video);
// Select desired default audio track
if (_global.conf->audioOutputTrack < videoGetAudioTracks(video->id)) {
videoSetAudioTrack(video->id, _global.conf->audioOutputTrack);
videoSetAudioTrack(video->handle, _global.conf->audioOutputTrack);
}
// Set default volume
videoSetVolume(video->handle, _global.conf->volumeNonVldp, _global.conf->volumeNonVldp);
}
}
@ -2983,6 +3227,7 @@ int32_t apiVideoUnload(lua_State *L) {
HASH_DEL(_global.videoList, video);
videoUnload(video->handle);
if (video->surface) SDL_FreeSurface(video->surface);
if (video->rotatedZoomedSurface) SDL_FreeSurface(video->rotatedZoomedSurface);
free(video);
result = true;
}
@ -4388,13 +4633,19 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) {
lua_register(_global.luaContext, "soundUnload", apiSoundUnload);
lua_register(_global.luaContext, "spriteDraw", apiSpriteDraw);
lua_register(_global.luaContext, "spriteGetFrame", apiSpriteGetFrame);
lua_register(_global.luaContext, "spriteGetHeight", apiSpriteGetHeight);
lua_register(_global.luaContext, "spriteGetWidth", apiSpriteGetWidth);
lua_register(_global.luaContext, "spriteIsPlaying", apiSpriteIsPlaying);
lua_register(_global.luaContext, "spriteLoad", apiSpriteLoad);
lua_register(_global.luaContext, "spriteLoop", apiSpriteLoop);
lua_register(_global.luaContext, "spritePause", apiSpritePause);
lua_register(_global.luaContext, "spritePlay", apiSpritePlay);
lua_register(_global.luaContext, "spriteQuality", apiSpriteQuality);
lua_register(_global.luaContext, "spriteRotate", apiSpriteRotate);
lua_register(_global.luaContext, "spriteRotateAndScale", apiSpriteRotateAndScale);
lua_register(_global.luaContext, "spriteScale", apiSpriteScale);
lua_register(_global.luaContext, "spriteSetFrame", apiSpriteSetFrame);
lua_register(_global.luaContext, "spriteUnload", apiSpriteUnload);
lua_register(_global.luaContext, "videoDraw", apiVideoDraw);
@ -4976,8 +5227,9 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) {
HASH_DEL(_global.spriteList, sprite);
#pragma GCC diagnostic pop
progTrace("Unloading sprite handle %d", sprite->id);
SDL_FreeSurface(sprite->surface);
SDL_FreeSurface(sprite->originalSurface);
if (sprite->surface) SDL_FreeSurface(sprite->surface);
if (sprite->originalSurface) SDL_FreeSurface(sprite->originalSurface);
if (sprite->animation) IMG_FreeAnimation(sprite->animation);
free(sprite);
}
@ -4986,6 +5238,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) {
HASH_DEL(_global.videoList, video);
progTrace("Unloading video handle %d", video->id);
if (video->surface) SDL_FreeSurface(video->surface);
if (video->rotatedZoomedSurface) SDL_FreeSurface(video->rotatedZoomedSurface);
free(video);
}