diff --git a/src/AssetManager.c b/src/AssetManager.c index 5bb4442..6318b9b 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -82,16 +82,16 @@ INTERNAL HashTableEntry *const getEntryFromHash(HashTable *const table, * Texture Operations ********************************* */ -INTERNAL Rect *getFreeAtlasSubTexSlot(TexAtlas *const atlas, - MemoryArena *const arena, - const char *const key) +INTERNAL SubTexture *getFreeAtlasSubTexSlot(TexAtlas *const atlas, + MemoryArena *const arena, + const char *const key) { HashTableEntry *entry = getFreeHashSlot(&atlas->subTex, arena, key); if (entry) { - entry->data = PLATFORM_MEM_ALLOC(arena, 1, Rect); - Rect *result = CAST(Rect *)entry->data; + entry->data = PLATFORM_MEM_ALLOC(arena, 1, SubTexture); + SubTexture *result = CAST(SubTexture *)entry->data; return result; } else @@ -100,15 +100,15 @@ INTERNAL Rect *getFreeAtlasSubTexSlot(TexAtlas *const atlas, } } -const Rect asset_getAtlasSubTex(TexAtlas *const atlas, const char *const key) +const SubTexture asset_getAtlasSubTex(TexAtlas *const atlas, const char *const key) { HashTableEntry *entry = getEntryFromHash(&atlas->subTex, key); - Rect result = {0}; + SubTexture result = {0}; if (entry) { - result = *(CAST(Rect *) entry->data); + result = *(CAST(SubTexture *) entry->data); return result; } @@ -638,7 +638,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, XmlAttribute *subTexAttrib = &atlasChildNode->attribute; char *key = NULL; - Rect subTex = {0}; + SubTexture subTex = {0}; while (subTexAttrib) { @@ -659,7 +659,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, i32 valueLen = common_strlen(value); i32 intValue = common_atoi(value, valueLen); - subTex.pos.x = CAST(f32) intValue; + subTex.rect.pos.x = CAST(f32) intValue; } else if (common_strcmp(subTexAttrib->name, "y") == 0) @@ -668,7 +668,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, i32 valueLen = common_strlen(value); i32 intValue = common_atoi(value, valueLen); - subTex.pos.y = CAST(f32) intValue; + subTex.rect.pos.y = CAST(f32) intValue; } else if (common_strcmp(subTexAttrib->name, "width") == 0) @@ -677,7 +677,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, i32 valueLen = common_strlen(value); i32 intValue = common_atoi(value, valueLen); - subTex.size.w = CAST(f32) intValue; + subTex.rect.size.w = CAST(f32) intValue; } else if (common_strcmp(subTexAttrib->name, "height") == 0) @@ -686,7 +686,25 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, i32 valueLen = common_strlen(value); i32 intValue = common_atoi(value, valueLen); - subTex.size.h = CAST(f32) intValue; + subTex.rect.size.h = CAST(f32) intValue; + } + else if (common_strcmp(subTexAttrib->name, + "hand_offset_x") == 0) + { + char *value = subTexAttrib->value; + i32 valueLen = common_strlen(value); + i32 intValue = common_atoi(value, valueLen); + + subTex.offset.x = CAST(f32) intValue; + } + else if (common_strcmp(subTexAttrib->name, + "hand_offset_y") == 0) + { + char *value = subTexAttrib->value; + i32 valueLen = common_strlen(value); + i32 intValue = common_atoi(value, valueLen); + + subTex.offset.y = CAST(f32) intValue; } else { @@ -701,13 +719,14 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, // TODO(doyle): XML specifies 0,0 top left, we // prefer 0,0 bottom right, so offset by size since 0,0 // is top left and size creates a bounding box below it - subTex.pos.y = 1024 - subTex.pos.y; - subTex.pos.y -= subTex.size.h; + subTex.rect.pos.y = 1024 - subTex.rect.pos.y; + subTex.rect.pos.y -= subTex.rect.size.h; + subTex.offset.y = subTex.rect.size.h - subTex.offset.y; #ifdef DENGINE_DEBUG ASSERT(key); #endif - Rect *subTexInHash = + SubTexture *subTexInHash = getFreeAtlasSubTexSlot(atlas, arena, key); *subTexInHash = subTex; } @@ -1126,8 +1145,8 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena, // all ascii characters, charToEncode represents the character // 1:1 const char key[2] = {charToEncode, 0}; - Rect *subTex = getFreeAtlasSubTexSlot(fontAtlas, arena, key); - *subTex = CAST(Rect){origin, font->maxSize}; + SubTexture *subTex = getFreeAtlasSubTexSlot(fontAtlas, arena, key); + subTex->rect = CAST(Rect){origin, font->maxSize}; charToEncode++; } diff --git a/src/Debug.c b/src/Debug.c index 43e037d..75d5e3d 100644 --- a/src/Debug.c +++ b/src/Debug.c @@ -377,7 +377,7 @@ void debug_drawUi(GameState *state, f32 dt) if (debugString) { - v2 strPos = v2_add(entity->pos, entity->hitboxSize); + v2 strPos = v2_add(entity->pos, entity->hitbox); i32 indexOfLowerAInMetrics = 'a' - CAST(i32) font->codepointRange.x; strPos.y += font->charMetrics[indexOfLowerAInMetrics].offset.y; diff --git a/src/Entity.c b/src/Entity.c index aff2619..b5b9e80 100644 --- a/src/Entity.c +++ b/src/Entity.c @@ -3,6 +3,16 @@ #include "Dengine/Platform.h" #include "Dengine/WorldTraveller.h" +SubTexture entity_getActiveSubTexture(Entity *const entity) +{ + EntityAnim *entityAnim = &entity->animList[entity->currAnimId]; + Animation *anim = entityAnim->anim; + char *frameName = anim->frameList[entityAnim->currFrame]; + + SubTexture result = asset_getAtlasSubTex(anim->atlas, frameName); + return result; +} + void entity_setActiveAnim(EventQueue *eventQueue, Entity *const entity, const char *const animName) { @@ -71,9 +81,9 @@ void entity_updateAnim(EventQueue *eventQueue, Entity *const entity, case entitytype_weapon: case entitytype_projectile: char *frameName = anim->frameList[currEntityAnim->currFrame]; - Rect texRect = + SubTexture texRect = asset_getAtlasSubTex(anim->atlas, frameName); - entity->renderSize = texRect.size; + entity->size = v2_scale(texRect.rect.size, entity->scale); default: break; } @@ -114,8 +124,8 @@ Entity *const entity_add(MemoryArena *const arena, World *const world, Entity entity = {0}; entity.id = world->uniqueIdAccumulator++; entity.pos = pos; - entity.hitboxSize = size; - entity.renderSize = size; + entity.size = size; + entity.hitbox = size; entity.scale = scale; entity.type = type; entity.direction = direction; diff --git a/src/Renderer.c b/src/Renderer.c index 872480d..44f151f 100644 --- a/src/Renderer.c +++ b/src/Renderer.c @@ -210,13 +210,13 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera, pos.x += charMetric.advance; /* Get texture out */ - Rect charTexRect = + SubTexture charTexRect = asset_getAtlasSubTex(font->atlas, &CAST(char)codepoint); v4 deprecatedTexRect = {0}; - deprecatedTexRect.vec2[0] = charTexRect.pos; + deprecatedTexRect.vec2[0] = charTexRect.rect.pos; deprecatedTexRect.vec2[1] = - v2_add(charTexRect.pos, charTexRect.size); + v2_add(charTexRect.rect.pos, charTexRect.rect.size); flipTexCoord(&deprecatedTexRect, FALSE, TRUE); @@ -244,7 +244,7 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity, // NOTE(doyle): Pos + Size since the origin of an entity is it's bottom left // corner. Add the two together so that the clipping point is the far right // side of the entity - v2 rightAlignedP = v2_add(entity->pos, entity->hitboxSize); + v2 rightAlignedP = v2_add(entity->pos, entity->hitbox); v2 leftAlignedP = entity->pos; if (math_pointInRect(camera, leftAlignedP) || math_pointInRect(camera, rightAlignedP)) @@ -252,14 +252,16 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity, EntityAnim *entityAnim = &entity->animList[entity->currAnimId]; Animation *anim = entityAnim->anim; char *frameName = anim->frameList[entityAnim->currFrame]; - Rect animRect = asset_getAtlasSubTex(anim->atlas, frameName); + SubTexture animRect = asset_getAtlasSubTex(anim->atlas, frameName); // TODO(doyle): Switch to rect v4 animTexRect = {0}; - animTexRect.vec2[0] = animRect.pos; - animTexRect.vec2[1] = v2_add(animRect.pos, animRect.size); + animTexRect.vec2[0] = animRect.rect.pos; + animTexRect.vec2[1] = v2_add(animRect.rect.pos, animRect.rect.size); - if (entity->direction == direction_east) + flipTexCoord(&animTexRect, entity->flipX, entity->flipY); + + if (entity->direction == direction_east) { flipTexCoord(&animTexRect, TRUE, FALSE); } @@ -270,9 +272,8 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity, updateBufferObject(renderer, &entityQuad, 1); v2 posInCameraSpace = v2_sub(entity->pos, camera.pos); - // TODO(doyle): Scale temporarily renderObject(renderer, posInCameraSpace, - v2_scale(entity->renderSize, entity->scale), pivotPoint, + entity->size, pivotPoint, entity->rotation + rotate, color, entity->tex); } } diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 7466079..2718bb7 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -301,7 +301,10 @@ INTERNAL void assetInit(GameState *state) claudeAtlas, claudeRipperBlast, numRects, duration); - char *claudeRipperBlastVfx[9] = { + char *claudeRipperBlastVfx[12] = { + "ClaudeSprite_Attack_RipperBlast_Vfx_01", + "ClaudeSprite_Attack_RipperBlast_Vfx_02", + "ClaudeSprite_Attack_RipperBlast_Vfx_03", "ClaudeSprite_Attack_RipperBlast_Vfx_04", "ClaudeSprite_Attack_RipperBlast_Vfx_05", "ClaudeSprite_Attack_RipperBlast_Vfx_06", @@ -601,19 +604,17 @@ INTERNAL void entityInit(GameState *state, v2 windowSize) dir = direction_east; tex = asset_getTex(assetManager, "ClaudeSprite.png"); collides = TRUE; + Entity *hero = entity_add(arena, world, pos, size, scale, type, dir, tex, collides); -#if 0 Entity *heroWeapon = - entity_add(arena, world, pos, V2(0, 0), entitytype_weapon, - hero->direction, hero->tex, FALSE); - heroWeapon->rotation = -90.0f; - heroWeapon->invisible = TRUE; + entity_add(arena, world, pos, V2(20, 20), scale, entitytype_weapon, + dir, tex, FALSE); + heroWeapon->flipX = TRUE; entity_addAnim(assetManager, heroWeapon, "claudeSword"); hero->stats->weapon = heroWeapon; -#endif hero->numAudioRenderers = 4; hero->audioRenderer = @@ -622,8 +623,8 @@ INTERNAL void entityInit(GameState *state, v2 windowSize) for (i32 i = 0; i < hero->numAudioRenderers; i++) hero->audioRenderer[i].sourceIndex = AUDIO_SOURCE_UNASSIGNED; - world->heroId = hero->id; - world->cameraFollowingId = hero->id; + world->heroId = hero->id; + world->cameraFollowingId = hero->id; /* Populate hero animation references */ entity_addAnim(assetManager, hero, "claudeIdle"); @@ -639,11 +640,11 @@ INTERNAL void entityInit(GameState *state, v2 windowSize) entity_addAnim(assetManager, hero, "claudeRipperBlast"); entity_addAnim(assetManager, hero, "claudeAirSlash"); - entity_setActiveAnim(eventQueue, hero, "claudeIdle"); + entity_setActiveAnim(eventQueue, hero, "claudeBattleIdle"); /* Create a NPC */ pos = V2(hero->pos.x * 3, CAST(f32) state->tileSize); - size = hero->hitboxSize; + size = hero->hitbox; type = entitytype_npc; dir = direction_null; tex = hero->tex; @@ -855,7 +856,7 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize) INTERNAL inline v4 getEntityScreenRect(Entity entity) { - v4 result = math_getRect(entity.pos, entity.hitboxSize); + v4 result = math_getRect(entity.pos, entity.hitbox); return result; } @@ -1162,12 +1163,6 @@ INTERNAL void beginAttack(AssetManager *assetManager, MemoryArena *arena, case entityattack_claudeAttack: { entity_setActiveAnim(eventQueue, attacker, "claudeAttack"); - if (attacker->stats->weapon) - { - attacker->stats->weapon->invisible = FALSE; - entity_setActiveAnim(eventQueue, attacker->stats->weapon, - "claudeAttackSlashLeft"); - } if (attacker->direction == direction_east) attacker->dPos.x += (1.0f * METERS_TO_PIXEL); else @@ -1178,12 +1173,6 @@ INTERNAL void beginAttack(AssetManager *assetManager, MemoryArena *arena, case entityattack_claudeAttackUp: { entity_setActiveAnim(eventQueue, attacker, "claudeAttackUp"); - if (attacker->stats->weapon) - { - attacker->stats->weapon->invisible = FALSE; - entity_setActiveAnim(eventQueue, attacker->stats->weapon, - "claudeAttackSlashLeft"); - } if (attacker->direction == direction_east) attacker->dPos.x += (1.0f * METERS_TO_PIXEL); else @@ -1194,12 +1183,6 @@ INTERNAL void beginAttack(AssetManager *assetManager, MemoryArena *arena, case entityattack_claudeAttackDown: { entity_setActiveAnim(eventQueue, attacker, "claudeAttackDown"); - if (attacker->stats->weapon) - { - attacker->stats->weapon->invisible = FALSE; - entity_setActiveAnim(eventQueue, attacker->stats->weapon, - "claudeAttackSlashLeft"); - } if (attacker->direction == direction_east) attacker->dPos.x += (1.0f * METERS_TO_PIXEL); else @@ -1330,12 +1313,6 @@ INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue, case entityattack_claudeAttackUp: case entityattack_claudeAttackDown: // TODO(doyle): Move animation offsets out and into animation type - - if (attacker->stats->weapon) - { - attacker->stats->weapon->invisible = TRUE; - } - if (attacker->direction == direction_east) attacker->dPos.x -= (1.0f * METERS_TO_PIXEL); else @@ -1515,7 +1492,7 @@ INTERNAL b32 checkCollision(Entity *a, Entity *b) b32 result = FALSE; if (a->collides && b->collides && a->collidesWith[b->type]) { - Rect aRect = {a->pos, a->hitboxSize}; + Rect aRect = {a->pos, a->hitbox}; v2 aTopLeftP = getPosRelativeToRect(aRect, V2(0, 0), rectbaseline_topLeft); @@ -1526,7 +1503,7 @@ INTERNAL b32 checkCollision(Entity *a, Entity *b) v2 aBottomRightP = getPosRelativeToRect(aRect, V2(0, 0), rectbaseline_bottomRight); - Rect bRect = {b->pos, b->hitboxSize}; + Rect bRect = {b->pos, b->hitbox}; if (math_pointInRect(bRect, aTopLeftP) || math_pointInRect(bRect, aTopRightP) || @@ -1730,7 +1707,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) V2((entity->pos.x - (0.5f * state->renderer.size.w)), (0.0f)); // NOTE(doyle): Account for the hero's origin being the bottom left - offsetFromEntityToCameraOrigin.x += (entity->hitboxSize.x * 0.5f); + offsetFromEntityToCameraOrigin.x += (entity->hitbox.w * 0.5f); world->cameraPos = offsetFromEntityToCameraOrigin; } @@ -2017,11 +1994,38 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) if (stats->weapon) { - stats->weapon->pos = entity->pos; + Entity *weapon = stats->weapon; + weapon->pos = entity->pos; + + // TODO(doyle): Add concept of entity origin and make all + // transform start from that origin point + if (entity->direction == direction_east) + { + weapon->pos.x += entity->size.w; + weapon->pos.x -= weapon->size.w; + } + weapon->direction = entity->direction; + + SubTexture entitySubTexture = + entity_getActiveSubTexture(entity); + weapon->pos.y += entitySubTexture.offset.y; + + if (weapon->direction == direction_west) + { + weapon->pos.x += entitySubTexture.offset.x; + // TODO(doyle): Typedef rotation to degrees for type safety + weapon->rotation = DEGREES_TO_RADIANS(60.0f); + } + else + { + weapon->pos.x -= entitySubTexture.offset.x; + weapon->rotation = DEGREES_TO_RADIANS(-60.0f); + } } if (entity->state == entitystate_battle) { + if (stats->actionTimer > 0) stats->actionTimer -= dt * stats->actionSpdMul; @@ -2067,9 +2071,19 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) { entity_updateAnim(eventQueue, entity, dt); /* Calculate region to render */ - renderer_entity(renderer, camera, entity, - v2_scale(entity->renderSize, 0.5f), 0, - V4(1, 1, 1, 1)); + + if (entity->type == entitytype_weapon) + { + renderer_entity(renderer, camera, entity, + v2_scale(entity->size, 0.5f), 0, + V4(1, 1, 1, 1.0f)); + } + else + { + renderer_entity(renderer, camera, entity, + v2_scale(entity->size, 0.5f), 0, + V4(1, 1, 1, 1)); + } } } @@ -2372,18 +2386,19 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) /* Draw hero avatar */ TexAtlas *heroAtlas = asset_getTexAtlas(assetManager, "ClaudeSprite.png"); - Rect heroAvatarRect = + SubTexture heroAvatarRect = asset_getAtlasSubTex(heroAtlas, "ClaudeSprite_Avatar_01"); - v2 heroAvatarP = - V2(10.0f, (renderer->size.h * 0.5f) - (0.5f * heroAvatarRect.size.h)); + v2 heroAvatarP = V2(10.0f, (renderer->size.h * 0.5f) - + (0.5f * heroAvatarRect.rect.size.h)); // TODO(doyle): Use rect in rendering not V4 v4 heroAvatarTexRect = {0}; - heroAvatarTexRect.vec2[0] = heroAvatarRect.pos; - heroAvatarTexRect.vec2[1] = v2_add(heroAvatarRect.pos, heroAvatarRect.size); + heroAvatarTexRect.vec2[0] = heroAvatarRect.rect.pos; + heroAvatarTexRect.vec2[1] = + v2_add(heroAvatarRect.rect.pos, heroAvatarRect.rect.size); RenderTex heroRenderTex = {hero->tex, heroAvatarTexRect}; - renderer_staticRect(renderer, heroAvatarP, heroAvatarRect.size, V2(0, 0), 0, + renderer_staticRect(renderer, heroAvatarP, heroAvatarRect.rect.size, V2(0, 0), 0, heroRenderTex, V4(1, 1, 1, 1)); char heroAvatarStr[20]; @@ -2392,7 +2407,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) f32 strLenInPixels = CAST(f32)(font->maxSize.w * common_strlen(heroAvatarStr)); v2 strPos = - V2(heroAvatarP.x, heroAvatarP.y - (0.5f * heroAvatarRect.size.h)); + V2(heroAvatarP.x, heroAvatarP.y - (0.5f * heroAvatarRect.rect.size.h)); renderer_staticString(&state->renderer, &state->arena, font, heroAvatarStr, strPos, V2(0, 0), 0, V4(0, 0, 1, 1)); @@ -2410,9 +2425,8 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) { v2 difference = v2_sub(entity->pos, hero->pos); f32 angle = math_atan2f(difference.y, difference.x); - f32 angleDegrees = RADIANS_TO_DEGREES(angle); - v2 heroCenter = v2_add(hero->pos, v2_scale(hero->hitboxSize, 0.5f)); + v2 heroCenter = v2_add(hero->pos, v2_scale(hero->hitbox, 0.5f)); RenderTex renderTex = renderer_createNullRenderTex(assetManager); f32 distance = v2_magnitude(hero->pos, entity->pos); renderer_rect(&state->renderer, camera, heroCenter, diff --git a/src/include/Dengine/AssetManager.h b/src/include/Dengine/AssetManager.h index 9d74797..41dc01a 100644 --- a/src/include/Dengine/AssetManager.h +++ b/src/include/Dengine/AssetManager.h @@ -28,7 +28,8 @@ typedef struct AssetManager * Texture Operations ********************************* */ -const Rect asset_getAtlasSubTex(TexAtlas *const atlas, const char *const key); +const SubTexture asset_getAtlasSubTex(TexAtlas *const atlas, + const char *const key); Texture *asset_getTex(AssetManager *const assetManager, const char *const key); TexAtlas *asset_getFreeTexAtlasSlot(AssetManager *const assetManager, MemoryArena *arena, const char *const key, diff --git a/src/include/Dengine/Assets.h b/src/include/Dengine/Assets.h index ea1a3c6..67466f6 100644 --- a/src/include/Dengine/Assets.h +++ b/src/include/Dengine/Assets.h @@ -94,6 +94,13 @@ typedef struct AudioVorbis * Texture Assets ********************************* */ + +typedef struct SubTexture +{ + Rect rect; + v2 offset; +} SubTexture; + typedef struct TexAtlas { Texture *tex; diff --git a/src/include/Dengine/Entity.h b/src/include/Dengine/Entity.h index 8bb48fa..123e34d 100644 --- a/src/include/Dengine/Entity.h +++ b/src/include/Dengine/Entity.h @@ -3,12 +3,11 @@ #include "Dengine/Common.h" #include "Dengine/Math.h" +#include "Dengine/Assets.h" typedef struct AssetManager AssetManager; typedef struct AudioRenderer AudioRenderer; typedef struct MemoryArena MemoryArena; -typedef struct Texture Texture; -typedef struct Animation Animation; typedef struct World World; typedef struct EventQueue EventQueue; @@ -95,8 +94,9 @@ struct Entity v2 pos; // Position v2 dPos; // Velocity - v2 hitboxSize; - v2 renderSize; + v2 hitbox; + v2 size; + f32 scale; f32 rotation; @@ -107,6 +107,8 @@ struct Entity enum Direction direction; Texture *tex; + b32 flipX; + b32 flipY; // TODO(doyle): Two collision flags, we want certain entities to collide // with certain types of entities only (i.e. projectile from hero to enemy, @@ -126,6 +128,7 @@ struct Entity i32 numAudioRenderers; }; +SubTexture entity_getActiveSubTexture(Entity *const entity); void entity_setActiveAnim(EventQueue *eventQueue, Entity *const entity, const char *const animName); void entity_updateAnim(EventQueue *eventQueue, Entity *const entity,