From 8097ff725b9bf027c6ab1ceabf1f34ee50e36df1 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Tue, 18 Aug 2026 01:01:16 +0300 Subject: [PATCH] add entities --- assets/tiled/map.tmx | 4 +- libs/tiled/src/tilemap.zig | 57 ++++++++++ src/app.zig | 216 ++++++++++++++++++++++--------------- src/platform/audio.zig | 2 +- src/platform/graphics.zig | 8 +- src/platform/root.zig | 1 + src/platform/slot_map.zig | 14 ++- 7 files changed, 208 insertions(+), 94 deletions(-) diff --git a/assets/tiled/map.tmx b/assets/tiled/map.tmx index c10b77c..a3ad273 100644 --- a/assets/tiled/map.tmx +++ b/assets/tiled/map.tmx @@ -1,5 +1,5 @@ - + @@ -49,7 +49,7 @@ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, diff --git a/libs/tiled/src/tilemap.zig b/libs/tiled/src/tilemap.zig index edcbfa4..49bf926 100644 --- a/libs/tiled/src/tilemap.zig +++ b/libs/tiled/src/tilemap.zig @@ -247,6 +247,63 @@ pub fn getLayer(self: *const Tilemap, name: []const u8) ?*const Layer { return null; } +pub const Bounds = struct { + min_x: usize, + min_y: usize, + max_x: usize, + max_y: usize, + + const zero = Bounds{ + .min_x = 0, + .min_y = 0, + .max_x = 0, + .max_y = 0, + }; + + pub fn width(self: Bounds) usize { + return self.max_x - self.min_x + 1; + } + + pub fn height(self: Bounds) usize { + return self.max_y - self.min_y + 1; + } +}; + +pub fn getTileBounds(self: *const Tilemap) Bounds { + var result: ?Bounds = null; + + for (self.layers) |*layer| { + if (layer.variant != .tile) { + continue; + } + + for (0..self.height) |y| { + for (0..self.width) |x| { + const gid = layer.variant.tile.get(x, y).?; + if (gid != 0) { + if (result) |bounds| { + result = Bounds{ + .min_x = @min(x, bounds.min_x), + .min_y = @min(y, bounds.min_y), + .max_x = @max(x, bounds.max_x), + .max_y = @max(y, bounds.max_y) + }; + } else { + result = Bounds{ + .min_x = x, + .min_y = y, + .max_x = x, + .max_y = y + }; + } + } + } + } + } + + return result orelse Bounds.zero; +} + pub fn deinit(self: *const Tilemap) void { self.arena.deinit(); } diff --git a/src/app.zig b/src/app.zig index cf2e816..640f8a4 100644 --- a/src/app.zig +++ b/src/app.zig @@ -78,15 +78,32 @@ const Tilemap = struct { } }; -player_pos: Vec2, +const Entity = struct { + type: Type, + + pos_x: i32 = 0, + pos_y: i32 = 0, + sprite: ?Gfx.Sprite.Id = null, + + player_direction: Direction = .down, + player_frame_index: u32 = 0, + + const Type = enum { + player, + box + }; + + const SlotMap = Platform.SlotMapType(u8, u16, Entity); + const Id = SlotMap.Id; +}; + roboto_font: Gfx.Font.Id, tilemap: Tilemap, +entities: Entity.SlotMap, tilesheet: Tilesheet, player_sprites: PlayerSprites, -player_direction: Direction, -player_frame_index: u32, animation_timer: Platform.Nanoseconds, footstep_buffer: Audio.BufferId, @@ -188,18 +205,18 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { }); self.* = App{ - .player_pos = .init(0, 0), .player_sprites = player_sprites, .roboto_font = robot_font, - .player_direction = .left, - .player_frame_index = 0, .animation_timer = 0, .tilesheet = tilesheet, .footstep_buffer = footstep_sound, .sfx_bus = Audio.addBus(.{ .label = "sfx" }), - .tilemap = try Tilemap.init(plt.gpa, 30, 20) + .tilemap = undefined, + .entities = try .initCapacity(plt.gpa, 256) }; + var player_pos_x: i32 = 0; + var player_pos_y: i32 = 0; { var scratch = std.heap.ArenaAllocator.init(plt.gpa); defer scratch.deinit(); @@ -215,11 +232,18 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { try tilesets.add(plt.gpa, "tileset.tsx", try Tiled.Tileset.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/tileset.tsx"))); + const bounds = tilemap.getTileBounds(); + + self.tilemap = try Tilemap.init(plt.gpa, bounds.width(), bounds.height()); + const ground = tilemap.getLayer("Ground").?; const walls = tilemap.getLayer("Walls").?; - for (0..tilemap.height) |y| { - for (0..tilemap.width) |x| { - const tile = self.tilemap.get(x, y); + for (0..bounds.height()) |oy| { + for (0..bounds.width()) |ox| { + const x = bounds.min_x + ox; + const y = bounds.min_y + oy; + + const tile = self.tilemap.get(ox, oy); if (tilemap.getTile(ground, tilesets, x, y)) |tiled_tile| { const pos = tiled_tile.getPosition(); @@ -235,100 +259,67 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { const markers = &tilemap.getLayer("Markers").?.variant.object; const spawn = markers.getByName("spawn").?; - self.player_pos.x = @divFloor(spawn.shape.point.x, @as(f32, @floatFromInt(tilemap.tile_width))); - self.player_pos.y = @divFloor(spawn.shape.point.y, @as(f32, @floatFromInt(tilemap.tile_height))); - } + player_pos_x = @divFloor(@as(i32, @intFromFloat(spawn.shape.point.x)), @as(i32, @intCast(tilemap.tile_width))); + player_pos_x -= @intCast(bounds.min_x); + player_pos_y = @divFloor(@as(i32, @intFromFloat(spawn.shape.point.y)),@as(i32, @intCast(tilemap.tile_height))); + player_pos_y -= @intCast(bounds.min_y); - { - var min_x: usize = self.tilemap.width; - var max_x: usize = 0; - var min_y: usize = self.tilemap.height; - var max_y: usize = 0; - for (0..self.tilemap.height) |y| { - for (0..self.tilemap.width) |x| { - const tile = self.tilemap.get(x, y); - if (tile.ground_sprite != null or tile.wall_sprite != null) { - min_x = @min(min_x, x); - min_y = @min(min_y, y); - max_x = @max(max_x, x); - max_y = @max(max_y, y); + const boxes = tilemap.getLayer("Boxes").?; + for (0..bounds.height()) |oy| { + for (0..bounds.width()) |ox| { + const x = bounds.min_x + ox; + const y = bounds.min_y + oy; + + if (tilemap.getTile(boxes, tilesets, x, y)) |tiled_tile| { + const pos = tiled_tile.getPosition(); + _ = try self.spawnBox(@intCast(ox), @intCast(oy), tilesheet.get(pos.x, pos.y)); } } } - - const map_width = max_x - min_x + 1; - const map_height = max_y - min_y + 1; - - var new_tilemap = try Tilemap.init(plt.gpa, map_width, map_height); - for (0..map_height) |y| { - for (0..map_width) |x| { - new_tilemap.get(x, y).* = self.tilemap.get(min_x + x, min_y + y).*; - } - } - - self.tilemap.deinit(plt.gpa); - self.tilemap = new_tilemap; - - self.player_pos.x -= @floatFromInt(min_x); - self.player_pos.y -= @floatFromInt(min_y); } + const player_id = try self.spawnPlayer(); + const player = self.entities.getAssumeExists(player_id); + player.pos_x = player_pos_x; + player.pos_y = player_pos_y; + return null; } +fn spawnPlayer(self: *App) !Entity.Id { + return try self.entities.insert(Entity{ + .type = .player, + }); +} + +fn spawnBox(self: *App, x: i32, y: i32, sprite: Gfx.Sprite.Id) !Entity.Id { + return try self.entities.insert(Entity{ + .type = .box, + .pos_x = x, + .pos_y = y, + .sprite = sprite + }); +} + pub fn frame(self: *App, plt: Platform.Frame) !void { const input = plt.input; - var dir: Vec2 = .init(0, 0); + var dir_y: i32 = 0; + var dir_x: i32 = 0; if (input.isKeyPressedOrRepeat(.S)) { - dir.y += 1; + dir_y += 1; } if (input.isKeyPressedOrRepeat(.W)) { - dir.y -= 1; + dir_y -= 1; } if (input.isKeyPressedOrRepeat(.D)) { - dir.x += 1; + dir_x += 1; } if (input.isKeyPressedOrRepeat(.A)) { - dir.x -= 1; + dir_x -= 1; } - if (dir.x != 0) { - dir.y = 0; - } - self.player_pos = self.player_pos.add(dir); - - if (dir.x != 0 or dir.y != 0) { - self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus }); - } - - var new_direction = self.player_direction; - if (dir.x < 0) { - new_direction = .left; - } else if (dir.x > 0) { - new_direction = .right; - } else if (dir.y > 0) { - new_direction = .down; - } else if (dir.y < 0) { - new_direction = .up; - } - - if (self.player_direction != new_direction) { - self.player_frame_index = 0; - self.player_direction = new_direction; - } - - const frames = self.player_sprites.getDirection(self.player_direction); - - if (dir.x != 0 or dir.y != 0) { - self.animation_timer += plt.input.delta_time; - const animatin_time = std.time.ns_per_s / 5; - while (self.animation_timer >= animatin_time) { - self.player_frame_index = (self.player_frame_index + 1) % @as(u32, @intCast(frames.len)); - self.animation_timer -= animatin_time; - } - } else { - self.animation_timer = 0; - self.player_frame_index = 0; + if (dir_x != 0) { + dir_y = 0; } Gfx.setClearColor(.rgb(40, 40, 40)); @@ -348,9 +339,64 @@ pub fn frame(self: *App, plt: Platform.Frame) !void { } } - Gfx.drawSprite(frames[self.player_frame_index], self.player_pos.multiply(tile_size), tile_size, .white); + var entity_iter = self.entities.iterator(); + while (entity_iter.next()) |entity_id| { + const entity = self.entities.getAssumeExists(entity_id); + if (entity.type == .player) { + entity.pos_x += dir_x; + entity.pos_y += dir_y; + + var new_direction = entity.player_direction; + if (dir_x < 0) { + new_direction = .left; + } else if (dir_x > 0) { + new_direction = .right; + } else if (dir_y > 0) { + new_direction = .down; + } else if (dir_y < 0) { + new_direction = .up; + } + + if (entity.player_direction != new_direction) { + entity.player_frame_index = 0; + entity.player_direction = new_direction; + } + + const frames = self.player_sprites.getDirection(entity.player_direction); + + if (dir_x != 0 or dir_y != 0) { + self.animation_timer += plt.input.delta_time; + const animatin_time = std.time.ns_per_s / 5; + while (self.animation_timer >= animatin_time) { + entity.player_frame_index = (entity.player_frame_index + 1) % @as(u32, @intCast(frames.len)); + self.animation_timer -= animatin_time; + } + } else { + self.animation_timer = 0; + entity.player_frame_index = 0; + } + + if (dir_x != 0 or dir_y != 0) { + self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus }); + } + + entity.sprite = frames[entity.player_frame_index]; + } + + + if (entity.sprite) |sprite| { + Gfx.drawSprite( + sprite, + Vec2.initFromInt(i32, entity.pos_x, entity.pos_y).multiply(tile_size), + tile_size, + .white + ); + } + } + } pub fn deinit(self: *App, plt: Platform.Deinit) void { self.tilemap.deinit(plt.gpa); + self.entities.slots.deinit(plt.gpa); } diff --git a/src/platform/audio.zig b/src/platform/audio.zig index 07e976f..b157ed4 100644 --- a/src/platform/audio.zig +++ b/src/platform/audio.zig @@ -147,7 +147,7 @@ pub fn init(io: Io, gpa: std.mem.Allocator, opts: InitOptions) !void { .vorbis_alloc_buffer = vorbis_alloc_buffer, .thread_state = .{ .mutex = .init, - .sounds = .init(sounds), + .sounds = .initBuffer(sounds), .bus_volumes = bus_volumes, } }; diff --git a/src/platform/graphics.zig b/src/platform/graphics.zig index d8b0d05..a468ca7 100644 --- a/src/platform/graphics.zig +++ b/src/platform/graphics.zig @@ -168,16 +168,16 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void { .transforms = .initBuffer(&self.transforms_buffer), .textures_buffer = undefined, - .textures = .init(&self.textures_buffer), + .textures = .initBuffer(&self.textures_buffer), .sprites_buffer = undefined, - .sprites = .init(&self.sprites_buffer), + .sprites = .initBuffer(&self.sprites_buffer), .spritesheets_buffer = undefined, - .spritesheets = .init(&self.spritesheets_buffer), + .spritesheets = .initBuffer(&self.spritesheets_buffer), .fonts_buffer = undefined, - .fonts = .init(&self.fonts_buffer), + .fonts = .initBuffer(&self.fonts_buffer), }; self.default_spritesheet = initSpritesheet(.{}); diff --git a/src/platform/root.zig b/src/platform/root.zig index 94ee4f3..9ed0c5e 100644 --- a/src/platform/root.zig +++ b/src/platform/root.zig @@ -27,6 +27,7 @@ pub const ImGUI = @import("./imgui.zig"); pub const Gfx = @import("./graphics.zig"); pub const Input = @import("./input.zig"); pub const Audio = @import("./audio.zig"); +pub const SlotMapType = @import("./slot_map.zig").SlotMapType; const EmbeddedAssets = @import("embedded_assets"); diff --git a/src/platform/slot_map.zig b/src/platform/slot_map.zig index 62003ad..1b3c36e 100644 --- a/src/platform/slot_map.zig +++ b/src/platform/slot_map.zig @@ -87,7 +87,7 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type { }; pub fn clearRetainingCapacity(self: *Self) void { - self.* = .init(self.slots.items); + self.* = .initBuffer(self.slots.items); } fn insertHole(self: *Self, index: Index) void { @@ -219,11 +219,21 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type { }; } - pub fn init(slots: []Slot) Self { + pub fn initBuffer(slots: []Slot) Self { var self: Self = .empty; self.slots = .initBuffer(slots); return self; } + + pub fn initCapacity(gpa: Allocator, capacity: usize) !Self { + var self: Self = .empty; + self.slots = try .initCapacity(gpa, capacity); + return self; + } + + pub fn deinit(self: *Self, gpa: Allocator) void { + self.slots.deinit(gpa); + } }; }