From e8b565508f767a45dde265217a4d0e5dd3880faa Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 28 Jan 2026 05:11:23 +0200 Subject: [PATCH] add player animations --- .gitignore | 1 + src/assets.zig | 15 ++- src/assets/game-2026-01-18.tiled-session | 50 -------- src/game.zig | 149 +++++++++++++++++------ 4 files changed, 129 insertions(+), 86 deletions(-) delete mode 100644 src/assets/game-2026-01-18.tiled-session diff --git a/.gitignore b/.gitignore index 880cd5d..6c2ffbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ zig-out .zig-cache +*.tiled-session diff --git a/src/assets.zig b/src/assets.zig index 9986b19..7f493b8 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -23,7 +23,9 @@ wood01: Audio.Data.Id, map: tiled.Tilemap, tilesets: tiled.Tileset.List, tileset_texture: Gfx.TextureId, +players_texture: Gfx.TextureId, tile_size: Engine.Vec2, +player_size: Engine.Vec2, pub fn init(gpa: std.mem.Allocator) !Assets { const font_id_array: FontName.EnumArray = .init(.{ @@ -60,6 +62,15 @@ pub fn init(gpa: std.mem.Allocator) !Assets { var tilesets: tiled.Tileset.List = .empty; try tilesets.add(gpa, "tileset.tsx", tileset); + const players_image = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Players/tilemap_packed.png")); + defer players_image.deinit(); + const players_texture = try Gfx.addTexture(&.{ + .{ + .width = players_image.width, + .height = players_image.height, + .rgba = players_image.rgba8_pixels + } + }); const tileset_image = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Tiles/tilemap_packed.png")); defer tileset_image.deinit(); @@ -77,7 +88,9 @@ pub fn init(gpa: std.mem.Allocator) !Assets { .map = map, .tilesets = tilesets, .tileset_texture = tileset_texture, - .tile_size = .init(16, 16) + .tile_size = .init(16, 16), + .players_texture = players_texture, + .player_size = .init(24, 24) }; } diff --git a/src/assets/game-2026-01-18.tiled-session b/src/assets/game-2026-01-18.tiled-session deleted file mode 100644 index 6b9e78a..0000000 --- a/src/assets/game-2026-01-18.tiled-session +++ /dev/null @@ -1,50 +0,0 @@ -{ - "Map/SizeTest": { - "height": 4300, - "width": 2 - }, - "activeFile": "map.tmx", - "expandedProjectPaths": [ - ], - "fileStates": { - "map.tmx": { - "scale": 1.5, - "selectedLayer": 0, - "viewCenter": { - "x": 49.33333333333337, - "y": 182 - } - }, - "tilemap.tsx": { - "dynamicWrapping": false, - "scaleInDock": 1.5, - "scaleInEditor": 5.5 - }, - "tiles.tsx": { - "dynamicWrapping": true, - "scaleInDock": 1, - "scaleInEditor": 1 - } - }, - "last.imagePath": "/home/rokas/code/games/game-2026-01-18/src/assets/kenney_desert-shooter-pack_1.0/PNG/Tiles", - "map.fixedSize": false, - "map.lastUsedFormat": "tmx", - "map.tileHeight": 16, - "map.tileWidth": 16, - "openFiles": [ - "tilemap.tsx", - "map.tmx" - ], - "project": "game-2026-01-18.tiled-project", - "recentFiles": [ - "tilemap.tsx", - "map.tmx", - "tiles.tsx" - ], - "tileset.lastUsedFormat": "tsx", - "tileset.tileSize": { - "height": 16, - "width": 16 - }, - "tileset.type": 0 -} diff --git a/src/game.zig b/src/game.zig index 8977237..aec3b9e 100644 --- a/src/game.zig +++ b/src/game.zig @@ -9,27 +9,91 @@ const imgui = Engine.imgui; const Vec2 = Engine.Vec2; const Rect = Engine.Math.Rect; const rgb = Engine.Math.rgb; +const TextureId = Engine.Graphics.TextureId; const Game = @This(); +const Animation = struct { + texture: TextureId, + frames: []Frame, + + const Frame = struct { + uv: Rect, + duration: f32, + }; + + const State = struct { + frame_index: usize, + timer: f32, + + const default = State{ + .frame_index = 0, + .timer = 0 + }; + + pub fn update(self: *State, animation: Animation, dt: f32) void { + self.timer += dt; + while (true) { + self.frame_index = @mod(self.frame_index, animation.frames.len); + const frame = animation.frames[self.frame_index]; + if (self.timer < frame.duration) { + break; + } + self.timer -= frame.duration; + self.frame_index += 1; + } + } + }; +}; + +arena: std.heap.ArenaAllocator, gpa: Allocator, assets: *Assets, player: Vec2, +player_anim_state: Animation.State = .default, +last_faced_left: bool = false, + +player_anim: Animation, pub fn init(gpa: Allocator, assets: *Assets) !Game { + var arena = std.heap.ArenaAllocator.init(gpa); + errdefer arena.deinit(); + + const texture_info = Engine.Graphics.getTextureInfo(assets.players_texture); + const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height); + const tile_size = assets.player_size; + const player_anim = Animation{ + .texture = assets.players_texture, + .frames = try arena.allocator().dupe(Animation.Frame, &.{ + .{ + .uv = getUVFromTilemap(tilemap_size, tile_size, 0, 0), + .duration = 0.1, + }, + .{ + .uv = getUVFromTilemap(tilemap_size, tile_size, 1, 0), + .duration = 0.1, + }, + .{ + .uv = getUVFromTilemap(tilemap_size, tile_size, 2, 0), + .duration = 0.15, + } + }), + }; + return Game{ + .arena = arena, .gpa = gpa, .assets = assets, - .player = findSpawnpoint(assets) orelse .init(0, 0) + .player = findSpawnpoint(assets) orelse .init(0, 0), + .player_anim = player_anim }; } pub fn deinit(self: *Game) void { - _ = self; // autofix + self.arena.deinit(); } - fn findSpawnpoint(assets: *Assets) ?Vec2 { const map = assets.map; for (map.layers) |layer| { @@ -43,22 +107,34 @@ fn findSpawnpoint(assets: *Assets) ?Vec2 { } const point = object.shape.point; if (std.mem.eql(u8, object.name, "spawnpoint")) { - const tile_height: f32 = @floatFromInt(map.tile_height); - const tile_width: f32 = @floatFromInt(map.tile_width); - return .init(point.x / tile_width, point.y / tile_height); + return .init(point.x, point.y); } } } return null; } +fn getUVFromTilemap(tilemap_size: Vec2, tile_size: Vec2, tile_x: f32, tile_y: f32) Rect { + return .{ + .pos = Vec2.init(tile_x, tile_y).multiply(tile_size).divide(tilemap_size), + .size = tile_size.divide(tilemap_size), + }; +} + +fn getUVFromTilemapByID(tilemap_size: Vec2, tile_size: Vec2, tile_id: u32) Rect { + const tile_id_f32: f32 = @floatFromInt(tile_id); + const width_in_tiles = tilemap_size.x / tile_size.x; + const tile_x = @rem(tile_id_f32, width_in_tiles); + const tile_y = @divFloor(tile_id_f32, width_in_tiles); + return getUVFromTilemap(tilemap_size, tile_size, tile_x, tile_y); +} + fn drawTilemap(self: *Game, frame: *Engine.Frame) void { const texture = self.assets.tileset_texture; const texture_info = Engine.Graphics.getTextureInfo(texture); const map = self.assets.map; - const tile_size = Vec2.init(1, 1); for (map.layers) |layer| { if (layer.variant != .tile) { continue; @@ -74,43 +150,29 @@ fn drawTilemap(self: *Game, frame: *Engine.Frame) void { const tile_gid = tile_layer.get(x, y) orelse continue; const tile = map.getTile(self.assets.tilesets, tile_gid) orelse continue; - const tilemap_width_in_tiles = @divExact(texture_info.width, tile.tileset.tile_width); - var uv: Rect = .{ - .pos = Vec2.initFromInt( - u32, - @rem(tile.id, tilemap_width_in_tiles), - @divFloor(tile.id, tilemap_width_in_tiles), - ).multiply( - .initFromInt(u32, tile.tileset.tile_width, tile.tileset.tile_height) - ), - .size = self.assets.tile_size, - }; - uv = uv.divide(.init(@floatFromInt(texture_info.width), @floatFromInt(texture_info.height))); - - var rect = Rect{ - .pos = .init(@floatFromInt(x), @floatFromInt(y)), - .size = .init(1, 1) - }; - rect = rect.multiply(tile_size); + const tilemap_size = Vec2.initFromInt(u32,texture_info.width, texture_info.height); + const tile_size = Vec2.initFromInt(u32, tile.tileset.tile_width, tile.tileset.tile_height); frame.drawRectangle(.{ - .rect = rect, + .rect = Rect{ + .pos = Vec2.initFromInt(i32, x, y).multiply(tile_size), + .size = tile_size + }, .color = rgb(255, 255, 255), .texture = .{ .id = self.assets.tileset_texture, - .uv = uv + .uv = getUVFromTilemapByID(tilemap_size, tile_size,tile.id) } }); } } - // map.getTile(layer, tilesets) } } pub fn tick(self: *Game, frame: *Engine.Frame) !void { const dt = frame.deltaTime(); - const canvas_size = Vec2.init(20, 15); + const canvas_size = Vec2.init(20 * 16, 15 * 16); frame.graphics.canvas_size = canvas_size; if (frame.isKeyPressed(.F3)) { @@ -138,13 +200,19 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { } dir = dir.normalized(); + if (dir.x != 0 or dir.y != 0) { + self.player_anim_state.update(self.player_anim, dt); + } else { + self.player_anim_state.frame_index = 0; + } + // if (dir.x != 0 or dir.y != 0) { // try frame.playAudio(.{ // .id = self.assets.wood01 // }); // } - self.player = self.player.add(dir.multiplyScalar(5 * dt)); + self.player = self.player.add(dir.multiplyScalar(50 * dt)); const regular_font = self.assets.font_id.get(.regular); @@ -154,17 +222,28 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { .rect = .init(0, 0, canvas_size.x, canvas_size.y), .color = rgb(20, 20, 20) }); - const size = Vec2.init(1, 1); + + if (dir.x < 0) { + self.last_faced_left = true; + } else if (dir.x > 0) { + self.last_faced_left = false; + } + + var size = self.assets.player_size; + if (self.last_faced_left) { + size.x *= -1; + } frame.drawRectangle(.{ .rect = .{ .pos = self.player.sub(size.divideScalar(2)), .size = size, }, - .color = rgb(200, 20, 20) + .color = rgb(255, 255, 255), + .texture = .{ + .id = self.player_anim.texture, + .uv = self.player_anim.frames[self.player_anim_state.frame_index].uv + } }); - if (dir.x != 0 or dir.y != 0) { - frame.drawRectanglOutline(self.player.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 0.1); - } frame.drawText(self.player, "Player", .{ .font = regular_font,