SpriteSheet and Tiles

This commit is contained in:
Isuru Samarathunga
2025-11-02 15:49:47 +05:30
parent 9fafab5145
commit 7d69518bdc
5 changed files with 266 additions and 56 deletions

View File

@ -21,6 +21,35 @@
#define STB_IMAGE_IMPLEMENTATION
#include <Vendor/stb/stb_image.h>
namespace ia::iae
{
struct Resource
{
enum class EType
{
INVALID,
TEXTURE,
SPRITE_SHEET,
SOUND
};
EType Type{EType::INVALID};
};
struct Resource_Texture : public Resource
{
Handle Texture;
};
struct Resource_SpriteSheet : public Resource_Texture
{
Vector<INT32> FrameCounts;
};
Vector<Resource *> g_resources;
} // namespace ia::iae
namespace ia::iae
{
String g_gameName;
@ -98,38 +127,26 @@ namespace ia::iae
namespace ia::iae
{
Handle CreateTexture(IN PCCHAR path, IN INT32 tileWidth = -1, IN INT32 tileHeight = -1)
{
INT32 w, h, n;
auto pixels = stbi_load(path, &w, &h, &n, STBI_rgb_alpha);
const auto t = Renderer::CreateTexture(pixels, w, h, tileWidth == -1 ? 1 : w/tileWidth, tileHeight == -1 ? 1 : h/tileHeight);
stbi_image_free(pixels);
return t;
}
Handle textures[5];
VOID IAEngine::Initialize()
{
Renderer::Initialize(IVec2{g_designViewport.x, g_designViewport.y});
textures[0] = CreateTexture("Resources/Cute_Fantasy_Free/Tiles/Beach_Tile.png", 16, 16);
textures[1] = CreateTexture("Resources/Cute_Fantasy_Free/Tiles/Cliff_Tile.png", 16, 16);
Renderer::SetTextureAtlas({textures[0], textures[1]});
Game_OnInitialize();
}
VOID IAEngine::Terminate()
{
Game_OnTerminate();
Renderer::Terminate();
for (SIZE_T i = 0; i < g_resources.size(); i++)
DestroyResource(i);
}
VOID IAEngine::Update()
{
Renderer::DrawStaticSpriteTopLeft(textures[1], 0, {0.0f, 0.0f}, {1.0f, 1.0f}, 0.0f);
Renderer::DrawStaticSpriteTopLeft(textures[1], 1, {16.0f, 0.0f}, {1.0f, 1.0f}, 0.0f);
Renderer::DrawStaticSpriteTopLeft(textures[1], 2, {32.0f, 0.0f}, {1.0f, 1.0f}, 0.0f);
Game_OnUpdate(0.0f);
Renderer::Update();
}
@ -138,3 +155,139 @@ namespace ia::iae
const auto event = (SDL_Event *) _event;
}
} // namespace ia::iae
namespace ia::iae
{
Handle CreateTextureFromFile(IN PCCHAR path, IN INT32 tileWidth = -1, IN INT32 tileHeight = -1)
{
INT32 w, h, n;
auto pixels = stbi_load(path, &w, &h, &n, STBI_rgb_alpha);
const auto t = Renderer::CreateTexture(pixels, w, h, tileWidth == -1 ? 1 : w / tileWidth,
tileHeight == -1 ? 1 : h / tileHeight);
stbi_image_free(pixels);
return t;
}
Handle IAEngine::CreateSprite(IN CONST String &path)
{
const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE;
t->Texture = CreateTextureFromFile(path.c_str());
g_resources.pushBack(t);
return (Handle)g_resources.size() - 1;
}
Handle IAEngine::CreateSprite(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height)
{
const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE;
t->Texture = Renderer::CreateTexture(rgbaData, width, height);
g_resources.pushBack(t);
return (Handle)g_resources.size() - 1;
}
Handle IAEngine::CreateSpriteSheet(IN CONST Vector<Vector<Handle>> &animations)
{
THROW_NOT_IMPLEMENTED();
return INVALID_HANDLE;
}
Handle IAEngine::CreateSpriteSheet(IN CONST String &path, IN INT32 spriteWidth, IN INT32 spriteHeight,
IN CONST Vector<INT32> &frameCounts)
{
const auto t = new Resource_SpriteSheet();
t->Type = Resource::EType::SPRITE_SHEET;
t->FrameCounts = frameCounts;
t->Texture = CreateTextureFromFile(path.c_str(), spriteWidth, spriteHeight);
g_resources.pushBack(t);
return (Handle)g_resources.size() - 1;
}
Handle IAEngine::CreateTileSheet(IN CONST String &path, IN INT32 tileWidth, IN INT32 tileHeight)
{
const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE;
t->Texture = CreateTextureFromFile(path.c_str(), tileWidth, tileHeight);
g_resources.pushBack(t);
return (Handle)g_resources.size() - 1;
}
Handle IAEngine::CreateTileSheet(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileWidth,
IN INT32 tileHeight)
{
const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE;
t->Texture = Renderer::CreateTexture(rgbaData, width, height, width / tileWidth, height / tileHeight);
g_resources.pushBack(t);
return (Handle)g_resources.size() - 1;
}
VOID IAEngine::DestroyResource(IN Handle resource)
{
if(!g_resources[resource]) return;
switch (g_resources[resource]->Type)
{
case Resource::EType::INVALID:
break;
case Resource::EType::TEXTURE:
case Resource::EType::SPRITE_SHEET:
Renderer::DestroyTexture(static_cast<Resource_Texture *>(g_resources[resource])->Texture);
break;
case Resource::EType::SOUND:
break;
}
delete g_resources[resource];
g_resources[resource] = nullptr;
}
VOID IAEngine::LoadResources(IN CONST Vector<Handle> &resources)
{
Vector<Handle> atlasTextures;
for (const auto &handle : resources)
{
switch (g_resources[handle]->Type)
{
case Resource::EType::INVALID:
break;
case Resource::EType::TEXTURE:
case Resource::EType::SPRITE_SHEET:
atlasTextures.pushBack(static_cast<Resource_Texture *>(g_resources[handle])->Texture);
break;
case Resource::EType::SOUND:
break;
}
}
Renderer::BakeTextureAtlas(atlasTextures);
}
} // namespace ia::iae
namespace ia::iae
{
VOID IAEngine::DrawTile(IN Handle tileSheet, IN INT32 tileIndexX, IN INT32 tileIndexY, IN Vec2 position,
IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto t = static_cast<Resource_Texture*>(g_resources[tileSheet]);
Renderer::DrawStaticSpriteTopLeft(t->Texture, tileIndexX, tileIndexY, position, scale, rotation, flipH, flipV, uvOffset);
}
VOID IAEngine::DrawSprite(IN Handle sprite, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH,
IN BOOL flipV, IN Vec2 uvOffset)
{
const auto t = static_cast<Resource_Texture*>(g_resources[sprite]);
Renderer::DrawDynamicSpriteTopLeft(t->Texture, 0, 0, position, scale, rotation, flipH, flipV, uvOffset);
}
VOID IAEngine::DrawSprite(IN Handle spriteSheet, IN INT32 animationIndex, IN INT32 frameIndex, IN Vec2 position,
IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto t = static_cast<Resource_SpriteSheet*>(g_resources[spriteSheet]);
Renderer::DrawDynamicSpriteTopLeft(t->Texture, animationIndex, frameIndex, position, scale, rotation, flipH, flipV, uvOffset);
}
} // namespace ia::iae

View File

@ -204,26 +204,58 @@ namespace ia::iae
namespace ia::iae
{
VOID Renderer::DrawStaticSpriteTopLeft(IN Handle texture, IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale,
IN FLOAT32 rotation)
Vec2 Renderer::DrawStaticSpriteTopLeft(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].TileHeight};
Mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3{position.x, position.y, 0});
transform = glm::rotate(transform, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
transform = glm::scale(transform, glm::vec3{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].TileHeight, 1.0f});
transform = glm::scale(transform, glm::vec3(_s, 1.0f));
g_staticSprites.pushBack({.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndex),
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
VOID Renderer::DrawDynamicSpriteTopLeft(IN Handle texture, IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale,
IN FLOAT32 rotation)
Vec2 Renderer::DrawStaticSpriteCentered(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].TileHeight};
Mat4 transform =
glm::translate(glm::mat4(1.0f), glm::vec3{position.x - _s.x / 2.0f, position.y - _s.y / 2.0f, 0});
transform = glm::rotate(transform, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
transform = glm::scale(transform, glm::vec3(_s, 1.0f));
g_staticSprites.pushBack({.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Vec2 Renderer::DrawDynamicSpriteTopLeft(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].TileHeight};
Mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3{position.x, position.y, 0});
transform = glm::rotate(transform, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
transform = glm::scale(transform, glm::vec3{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].TileHeight, 1.0f});
transform = glm::scale(transform, glm::vec3{_s, 1.0f});
g_dynamicSprites.pushBack({.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndex),
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Vec2 Renderer::DrawDynamicSpriteCentered(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].TileHeight};
Mat4 transform =
glm::translate(glm::mat4(1.0f), glm::vec3{position.x - _s.x / 2.0f, position.y - _s.y / 2.0f, 0});
transform = glm::rotate(transform, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
transform = glm::scale(transform, glm::vec3{_s, 1.0f});
g_dynamicSprites.pushBack({.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Geometry *Renderer::CreateGeometry(IN CONST Vector<GeometryVertex> &vertices, IN CONST Vector<INT32> &indices)
@ -284,7 +316,7 @@ namespace ia::iae
t.Height, t.Width, t.Pixels);
}
VOID Renderer::SetTextureAtlas(IN CONST Vector<Handle> textures)
VOID Renderer::BakeTextureAtlas(IN CONST Vector<Handle> textures)
{
if (g_activeTextureAtlas && (g_activeTextureAtlas != g_defaultTexture))
DestroyTexture(g_activeTextureAtlas);
@ -320,14 +352,12 @@ namespace ia::iae
delete[] pixels;
}
Vec4 Renderer::GetTextureAtlasCoordinates(IN Handle texture, IN INT32 tileIndex)
Vec4 Renderer::GetTextureAtlasCoordinates(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto &d = g_texureData[texture];
const auto &t = g_activeTextureAtlasUVMap[texture];
const auto pX =
((INT32) (tileIndex % d.TileCountX) * ((FLOAT32) d.TileWidth)) * g_activeTextureAtlasInverseSize.x;
const auto pY =
((INT32) (tileIndex / d.TileCountX) * ((FLOAT32) d.TileHeight)) * g_activeTextureAtlasInverseSize.y;
const auto pX = (tileIndexX * ((FLOAT32) d.TileWidth)) * g_activeTextureAtlasInverseSize.x;
const auto pY = (tileIndexY * ((FLOAT32) d.TileHeight)) * g_activeTextureAtlasInverseSize.y;
return Vec4(t.x + pX, t.y + pY, d.TileWidth * g_activeTextureAtlasInverseSize.x,
d.TileHeight * g_activeTextureAtlasInverseSize.y);
}

View File

@ -66,23 +66,26 @@ namespace ia::iae
STATIC VOID SetCameraPosition(IN Vec2 position);
public:
STATIC VOID DrawStaticSpriteTopLeft(IN Handle texture, IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation);
STATIC INLINE VOID DrawStaticSpriteCentered(IN Handle texture, IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale,
IN FLOAT32 rotation);
STATIC Vec2 DrawStaticSpriteTopLeft(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Vec2 DrawStaticSpriteCentered(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC VOID DrawDynamicSpriteTopLeft(IN Handle texture, IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation);
STATIC INLINE VOID DrawDynamicSpriteCentered(IN Handle texture, IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale,
IN FLOAT32 rotation);
STATIC Vec2 DrawDynamicSpriteTopLeft(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Vec2 DrawDynamicSpriteCentered(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC VOID ResizeScreen(IN IVec2 newSize);
public:
STATIC Handle CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileCountX = 1, IN INT32 tileCountY = 1);
STATIC Handle CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileCountX = 1,
IN INT32 tileCountY = 1);
STATIC VOID DestroyTexture(IN Handle texture);
STATIC VOID BakeTexture(IN Handle texture);
STATIC VOID SetTextureAtlas(IN CONST Vector<Handle> textures);
STATIC VOID BakeTextureAtlas(IN CONST Vector<Handle> textures);
private:
STATIC SDL_GPUTexture *CreateTexture(IN SDL_GPUTextureUsageFlags usage, IN INT32 width, IN INT32 height,
@ -115,7 +118,7 @@ namespace ia::iae
STATIC VOID Render();
STATIC Vec4 GetTextureAtlasCoordinates(IN Handle texture, IN INT32 tileIndex);
STATIC Vec4 GetTextureAtlasCoordinates(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset);
private:
STATIC VOID Initialize(IN IVec2 screenExtent);
@ -124,14 +127,4 @@ namespace ia::iae
friend class IAEngine;
};
VOID Renderer::DrawStaticSpriteCentered(IN Handle texture,IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation)
{
DrawStaticSpriteTopLeft(texture, tileIndex, position - scale / 2.0f, scale, rotation);
}
VOID Renderer::DrawDynamicSpriteCentered(IN Handle texture,IN INT32 tileIndex, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation)
{
DrawDynamicSpriteTopLeft(texture, tileIndex, position - scale / 2.0f, scale, rotation);
}
} // namespace ia::iae

View File

@ -22,15 +22,40 @@ namespace ia::iae
{
class IAEngine
{
public:
public:
STATIC Handle CreateSprite(IN CONST String &path);
STATIC Handle CreateSprite(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height);
private:
STATIC Handle CreateSpriteSheet(IN CONST Vector<Vector<Handle>> &animations);
STATIC Handle CreateSpriteSheet(IN CONST String &path, IN INT32 spriteWidth, IN INT32 spriteHeight,
IN CONST Vector<INT32> &frameCounts);
STATIC Handle CreateTileSheet(IN CONST String &path, IN INT32 tileWidth, IN INT32 tileHeight);
STATIC Handle CreateTileSheet(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileWidth,
IN INT32 tileHeight);
STATIC VOID DestroyResource(IN Handle resource);
STATIC VOID LoadResources(IN CONST Vector<Handle> &resources);
public:
STATIC VOID DrawTile(IN Handle tileSheet, IN INT32 tileIndexX, IN INT32 tileIndexY, IN Vec2 position,
IN Vec2 scale = {1.0f, 1.0f}, IN FLOAT32 rotation = 0.0f, IN BOOL flipH = false,
IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC VOID DrawSprite(IN Handle sprite, IN Vec2 position, IN Vec2 scale = {1.0f, 1.0f},
IN FLOAT32 rotation = 0.0f, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC VOID DrawSprite(IN Handle spriteSheet, IN INT32 animationIndex, IN INT32 frameIndex, IN Vec2 position,
IN Vec2 scale = {1.0f, 1.0f}, IN FLOAT32 rotation = 0.0f, IN BOOL flipH = false,
IN BOOL flipV = false, IN Vec2 uvOffset = {});
private:
STATIC VOID Initialize();
STATIC VOID Terminate();
STATIC VOID Update();
STATIC VOID ProcessEvent(IN PVOID event);
friend INT32 Run(IN CONST String &name, IN CONST String &packageName, IN CONST String &developerName,
IN CONST String &publisherName, IN IA_VERSION_TYPE version);
IN CONST String &publisherName, IN IA_VERSION_TYPE version);
};
}
} // namespace ia::iae