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 sprite's center as the anchor instead of the upper right. This is highly
useful when dealing with rotated sprites. 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. - Sprite anti-aliasing, scaling and rotation! Optional separate X & Y scaling.
- Video 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; int32_t id;
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpadded" #pragma GCC diagnostic ignored "-Wpadded"
IMG_Animation *animation;
SDL_Surface *originalSurface; SDL_Surface *originalSurface;
SDL_Surface *surface; SDL_Surface *surface;
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
double angle; double angle;
double scaleX; double scaleX;
double scaleY; double scaleY;
int smooth; int32_t smooth;
int32_t currentFrame;
bool loop;
bool animating;
uint32_t lastTick;
uint32_t ticks;
UT_hash_handle hh; UT_hash_handle hh;
} SpriteT; } SpriteT;
@ -142,6 +148,7 @@ typedef struct VideoS {
bool wasPlayingBeforePause; bool wasPlayingBeforePause;
SDL_Texture *texture; SDL_Texture *texture;
SDL_Surface *surface; SDL_Surface *surface;
SDL_Surface *rotatedZoomedSurface;
double angle; double angle;
double scaleX; double scaleX;
double scaleY; double scaleY;
@ -389,13 +396,19 @@ int32_t apiSoundFullStop(lua_State *L);
int32_t apiSoundUnload(lua_State *L); int32_t apiSoundUnload(lua_State *L);
int32_t apiSpriteDraw(lua_State *L); int32_t apiSpriteDraw(lua_State *L);
int32_t apiSpriteGetFrame(lua_State *L);
int32_t apiSpriteGetHeight(lua_State *L); int32_t apiSpriteGetHeight(lua_State *L);
int32_t apiSpriteGetWidth(lua_State *L); int32_t apiSpriteGetWidth(lua_State *L);
int32_t apiSpriteIsPlaying(lua_State *L);
int32_t apiSpriteLoad(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 apiSpriteQuality(lua_State *L);
int32_t apiSpriteRotate(lua_State *L); int32_t apiSpriteRotate(lua_State *L);
int32_t apiSpriteRotateAndScale(lua_State *L); int32_t apiSpriteRotateAndScale(lua_State *L);
int32_t apiSpriteScale(lua_State *L); int32_t apiSpriteScale(lua_State *L);
int32_t apiSpriteSetFrame(lua_State *L);
int32_t apiSpriteUnload(lua_State *L); int32_t apiSpriteUnload(lua_State *L);
int32_t apiVideoDraw(lua_State *L); int32_t apiVideoDraw(lua_State *L);
@ -1932,11 +1945,12 @@ int32_t apiSoundUnload(lua_State *L) {
int32_t apiSpriteDraw(lua_State *L) { int32_t apiSpriteDraw(lua_State *L) {
int32_t n = lua_gettop(L); int32_t n = lua_gettop(L);
int32_t id = -1; int32_t id = -1;
double d = 0; double d = 0;
bool center = false; bool center = false;
SpriteT *sprite = NULL; bool newFrame = false;
SpriteT *sprite = NULL;
SDL_Rect dest; SDL_Rect dest;
int ox; int ox;
int oy; int oy;
@ -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); //utilSay("spriteDraw: x=%d y=%d c=%d id=%d", dest.x, dest.y, center, id);
HASH_FIND_INT(_global.spriteList, &id, sprite); HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteDraw", "No sprite at index %d in apiSpriteDraw.", id); 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)) { if ((n == 3) || (n == 4)) {
// No scaling, find width // No scaling, find width
dest.w = sprite->surface->w; 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 apiSpriteGetHeight(lua_State *L) {
int32_t n = lua_gettop(L); int32_t n = lua_gettop(L);
int32_t result = -1; 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 apiSpriteLoad(lua_State *L) {
int32_t n = lua_gettop(L); int32_t n = lua_gettop(L);
int32_t result = -1; int32_t result = -1;
const char *name = NULL; const char *name = NULL;
SpriteT *sprite = NULL; SpriteT *sprite = NULL;
int32_t x = 0;
if (n == 1) { if (n == 1) {
if (lua_isstring(L, 1)) { if (lua_isstring(L, 1)) {
sprite = (SpriteT *)calloc(1, sizeof(SpriteT)); sprite = (SpriteT *)calloc(1, sizeof(SpriteT));
if (!sprite) luaDie(L, "spriteLoad", "Unable to allocate new sprite."); if (!sprite) luaDie(L, "spriteLoad", "Unable to allocate new sprite.");
name = lua_tostring(L, 1); name = lua_tostring(L, 1);
sprite->originalSurface = IMG_Load(name); // 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()); if (!sprite->originalSurface) luaDie(L, "spriteLoad", "%s", IMG_GetError());
SDL_SetColorKey(sprite->originalSurface, true, 0); SDL_SetColorKey(sprite->originalSurface, true, 0);
sprite->surface = surfaceCopy(sprite->originalSurface); 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); luaTrace(L, "spriteLoad", "%d %s", result, name);
} else { } else {
luaDie(L, "spriteLoad", "Failed!"); 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 apiSpriteQuality(lua_State *L) {
int32_t n = lua_gettop(L); int32_t n = lua_gettop(L);
int32_t id = -1; 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 apiSpriteUnload(lua_State *L) {
int32_t n = lua_gettop(L); int32_t n = lua_gettop(L);
bool result = false; bool result = false;
@ -2268,8 +2504,9 @@ int32_t apiSpriteUnload(lua_State *L) {
HASH_FIND_INT(_global.spriteList, &id, sprite); HASH_FIND_INT(_global.spriteList, &id, sprite);
if (!sprite) luaDie(L, "spriteUnload", "No sprite at index %d in apiSpriteUnload.", id); if (!sprite) luaDie(L, "spriteUnload", "No sprite at index %d in apiSpriteUnload.", id);
HASH_DEL(_global.spriteList, sprite); HASH_DEL(_global.spriteList, sprite);
SDL_FreeSurface(sprite->surface); if (sprite->surface) SDL_FreeSurface(sprite->surface);
SDL_FreeSurface(sprite->originalSurface); if (sprite->originalSurface) SDL_FreeSurface(sprite->originalSurface);
if (sprite->animation) IMG_FreeAnimation(sprite->animation);
free(sprite); free(sprite);
result = true; result = true;
} }
@ -2296,7 +2533,6 @@ int32_t apiVideoDraw(lua_State *L) {
VideoT *video = NULL; VideoT *video = NULL;
bool center = false; bool center = false;
SDL_Rect dest; SDL_Rect dest;
SDL_Surface *surface = NULL;
// videoDraw(id, x1, y1, x2, y2) - Simple/Stretched draw // videoDraw(id, x1, y1, x2, y2) - Simple/Stretched draw
// videoDraw(id, x1, y1, c) - Scaled/Rotated draw // videoDraw(id, x1, y1, c) - Scaled/Rotated draw
@ -2340,17 +2576,23 @@ int32_t apiVideoDraw(lua_State *L) {
// Render frame into overlay // Render frame into overlay
if ((dest.w == 0) && (dest.h == 0)) { if ((dest.w == 0) && (dest.h == 0)) {
surface = rotozoomSurfaceXY(video->surface, 360 - video->angle, video->scaleX, video->scaleY, video->smooth); if (frame != video->lastFrame) {
dest.w = surface->w; if (video->rotatedZoomedSurface != NULL) {
dest.h = surface->h; 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 // Scaled/Rotated draw
if (center) { if (center) {
// Move video so the drawing coordinate is the center of the video // Move video so the drawing coordinate is the center of the video
dest.x -= dest.w * 0.5; dest.x -= dest.w * 0.5;
dest.y -= dest.h * 0.5; dest.y -= dest.h * 0.5;
} }
SDL_BlitSurface(surface, NULL, _global.overlay, &dest); SDL_BlitSurface(video->rotatedZoomedSurface, NULL, _global.overlay, &dest);
SDL_FreeSurface(surface);
} else { } else {
// Simple/Stretched draw // Simple/Stretched draw
if (SDL_BlitScaled(video->surface, NULL, _global.overlay, &dest) != 0) luaDie(L, "videoDraw", "%s", SDL_GetError()); 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); HASH_ADD_INT(_global.videoList, id, video);
// Select desired default audio track // Select desired default audio track
if (_global.conf->audioOutputTrack < videoGetAudioTracks(video->id)) { 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); HASH_DEL(_global.videoList, video);
videoUnload(video->handle); videoUnload(video->handle);
if (video->surface) SDL_FreeSurface(video->surface); if (video->surface) SDL_FreeSurface(video->surface);
if (video->rotatedZoomedSurface) SDL_FreeSurface(video->rotatedZoomedSurface);
free(video); free(video);
result = true; 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, "soundUnload", apiSoundUnload);
lua_register(_global.luaContext, "spriteDraw", apiSpriteDraw); lua_register(_global.luaContext, "spriteDraw", apiSpriteDraw);
lua_register(_global.luaContext, "spriteGetFrame", apiSpriteGetFrame);
lua_register(_global.luaContext, "spriteGetHeight", apiSpriteGetHeight); lua_register(_global.luaContext, "spriteGetHeight", apiSpriteGetHeight);
lua_register(_global.luaContext, "spriteGetWidth", apiSpriteGetWidth); lua_register(_global.luaContext, "spriteGetWidth", apiSpriteGetWidth);
lua_register(_global.luaContext, "spriteIsPlaying", apiSpriteIsPlaying);
lua_register(_global.luaContext, "spriteLoad", apiSpriteLoad); 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, "spriteQuality", apiSpriteQuality);
lua_register(_global.luaContext, "spriteRotate", apiSpriteRotate); lua_register(_global.luaContext, "spriteRotate", apiSpriteRotate);
lua_register(_global.luaContext, "spriteRotateAndScale", apiSpriteRotateAndScale); lua_register(_global.luaContext, "spriteRotateAndScale", apiSpriteRotateAndScale);
lua_register(_global.luaContext, "spriteScale", apiSpriteScale); lua_register(_global.luaContext, "spriteScale", apiSpriteScale);
lua_register(_global.luaContext, "spriteSetFrame", apiSpriteSetFrame);
lua_register(_global.luaContext, "spriteUnload", apiSpriteUnload); lua_register(_global.luaContext, "spriteUnload", apiSpriteUnload);
lua_register(_global.luaContext, "videoDraw", apiVideoDraw); 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); HASH_DEL(_global.spriteList, sprite);
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
progTrace("Unloading sprite handle %d", sprite->id); progTrace("Unloading sprite handle %d", sprite->id);
SDL_FreeSurface(sprite->surface); if (sprite->surface) SDL_FreeSurface(sprite->surface);
SDL_FreeSurface(sprite->originalSurface); if (sprite->originalSurface) SDL_FreeSurface(sprite->originalSurface);
if (sprite->animation) IMG_FreeAnimation(sprite->animation);
free(sprite); free(sprite);
} }
@ -4986,6 +5238,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) {
HASH_DEL(_global.videoList, video); HASH_DEL(_global.videoList, video);
progTrace("Unloading video handle %d", video->id); progTrace("Unloading video handle %d", video->id);
if (video->surface) SDL_FreeSurface(video->surface); if (video->surface) SDL_FreeSurface(video->surface);
if (video->rotatedZoomedSurface) SDL_FreeSurface(video->rotatedZoomedSurface);
free(video); free(video);
} }