add entities

This commit is contained in:
Rokas Puzonas 2026-08-18 01:01:16 +03:00
parent 330560f79b
commit 8097ff725b
7 changed files with 208 additions and 94 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="64" tileheight="64" infinite="0" nextlayerid="6" nextobjectid="2"> <map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="64" tileheight="64" infinite="0" nextlayerid="6" nextobjectid="4">
<tileset firstgid="1" source="tileset.tsx"/> <tileset firstgid="1" source="tileset.tsx"/>
<layer id="2" name="Ground" width="30" height="20"> <layer id="2" name="Ground" width="30" height="20">
<data encoding="csv"> <data encoding="csv">
@ -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
</data> </data>
</layer> </layer>
<layer id="4" name="Pushable" width="30" height="20"> <layer id="4" name="Boxes" width="30" height="20">
<data encoding="csv"> <data encoding="csv">
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, 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,

View File

@ -247,6 +247,63 @@ pub fn getLayer(self: *const Tilemap, name: []const u8) ?*const Layer {
return null; 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 { pub fn deinit(self: *const Tilemap) void {
self.arena.deinit(); self.arena.deinit();
} }

View File

@ -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, roboto_font: Gfx.Font.Id,
tilemap: Tilemap, tilemap: Tilemap,
entities: Entity.SlotMap,
tilesheet: Tilesheet, tilesheet: Tilesheet,
player_sprites: PlayerSprites, player_sprites: PlayerSprites,
player_direction: Direction,
player_frame_index: u32,
animation_timer: Platform.Nanoseconds, animation_timer: Platform.Nanoseconds,
footstep_buffer: Audio.BufferId, footstep_buffer: Audio.BufferId,
@ -188,18 +205,18 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
}); });
self.* = App{ self.* = App{
.player_pos = .init(0, 0),
.player_sprites = player_sprites, .player_sprites = player_sprites,
.roboto_font = robot_font, .roboto_font = robot_font,
.player_direction = .left,
.player_frame_index = 0,
.animation_timer = 0, .animation_timer = 0,
.tilesheet = tilesheet, .tilesheet = tilesheet,
.footstep_buffer = footstep_sound, .footstep_buffer = footstep_sound,
.sfx_bus = Audio.addBus(.{ .label = "sfx" }), .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); var scratch = std.heap.ArenaAllocator.init(plt.gpa);
defer scratch.deinit(); 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"))); 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 ground = tilemap.getLayer("Ground").?;
const walls = tilemap.getLayer("Walls").?; const walls = tilemap.getLayer("Walls").?;
for (0..tilemap.height) |y| { for (0..bounds.height()) |oy| {
for (0..tilemap.width) |x| { for (0..bounds.width()) |ox| {
const tile = self.tilemap.get(x, y); 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| { if (tilemap.getTile(ground, tilesets, x, y)) |tiled_tile| {
const pos = tiled_tile.getPosition(); 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 markers = &tilemap.getLayer("Markers").?.variant.object;
const spawn = markers.getByName("spawn").?; const spawn = markers.getByName("spawn").?;
self.player_pos.x = @divFloor(spawn.shape.point.x, @as(f32, @floatFromInt(tilemap.tile_width))); player_pos_x = @divFloor(@as(i32, @intFromFloat(spawn.shape.point.x)), @as(i32, @intCast(tilemap.tile_width)));
self.player_pos.y = @divFloor(spawn.shape.point.y, @as(f32, @floatFromInt(tilemap.tile_height))); 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);
{ const boxes = tilemap.getLayer("Boxes").?;
var min_x: usize = self.tilemap.width; for (0..bounds.height()) |oy| {
var max_x: usize = 0; for (0..bounds.width()) |ox| {
var min_y: usize = self.tilemap.height; const x = bounds.min_x + ox;
var max_y: usize = 0; const y = bounds.min_y + oy;
for (0..self.tilemap.height) |y| {
for (0..self.tilemap.width) |x| { if (tilemap.getTile(boxes, tilesets, x, y)) |tiled_tile| {
const tile = self.tilemap.get(x, y); const pos = tiled_tile.getPosition();
if (tile.ground_sprite != null or tile.wall_sprite != null) { _ = try self.spawnBox(@intCast(ox), @intCast(oy), tilesheet.get(pos.x, pos.y));
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 map_width = max_x - min_x + 1; const player_id = try self.spawnPlayer();
const map_height = max_y - min_y + 1; const player = self.entities.getAssumeExists(player_id);
player.pos_x = player_pos_x;
var new_tilemap = try Tilemap.init(plt.gpa, map_width, map_height); player.pos_y = player_pos_y;
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);
}
return null; 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 { pub fn frame(self: *App, plt: Platform.Frame) !void {
const input = plt.input; const input = plt.input;
var dir: Vec2 = .init(0, 0); var dir_y: i32 = 0;
var dir_x: i32 = 0;
if (input.isKeyPressedOrRepeat(.S)) { if (input.isKeyPressedOrRepeat(.S)) {
dir.y += 1; dir_y += 1;
} }
if (input.isKeyPressedOrRepeat(.W)) { if (input.isKeyPressedOrRepeat(.W)) {
dir.y -= 1; dir_y -= 1;
} }
if (input.isKeyPressedOrRepeat(.D)) { if (input.isKeyPressedOrRepeat(.D)) {
dir.x += 1; dir_x += 1;
} }
if (input.isKeyPressedOrRepeat(.A)) { if (input.isKeyPressedOrRepeat(.A)) {
dir.x -= 1; dir_x -= 1;
} }
if (dir.x != 0) { if (dir_x != 0) {
dir.y = 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;
} }
Gfx.setClearColor(.rgb(40, 40, 40)); 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 { pub fn deinit(self: *App, plt: Platform.Deinit) void {
self.tilemap.deinit(plt.gpa); self.tilemap.deinit(plt.gpa);
self.entities.slots.deinit(plt.gpa);
} }

View File

@ -147,7 +147,7 @@ pub fn init(io: Io, gpa: std.mem.Allocator, opts: InitOptions) !void {
.vorbis_alloc_buffer = vorbis_alloc_buffer, .vorbis_alloc_buffer = vorbis_alloc_buffer,
.thread_state = .{ .thread_state = .{
.mutex = .init, .mutex = .init,
.sounds = .init(sounds), .sounds = .initBuffer(sounds),
.bus_volumes = bus_volumes, .bus_volumes = bus_volumes,
} }
}; };

View File

@ -168,16 +168,16 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
.transforms = .initBuffer(&self.transforms_buffer), .transforms = .initBuffer(&self.transforms_buffer),
.textures_buffer = undefined, .textures_buffer = undefined,
.textures = .init(&self.textures_buffer), .textures = .initBuffer(&self.textures_buffer),
.sprites_buffer = undefined, .sprites_buffer = undefined,
.sprites = .init(&self.sprites_buffer), .sprites = .initBuffer(&self.sprites_buffer),
.spritesheets_buffer = undefined, .spritesheets_buffer = undefined,
.spritesheets = .init(&self.spritesheets_buffer), .spritesheets = .initBuffer(&self.spritesheets_buffer),
.fonts_buffer = undefined, .fonts_buffer = undefined,
.fonts = .init(&self.fonts_buffer), .fonts = .initBuffer(&self.fonts_buffer),
}; };
self.default_spritesheet = initSpritesheet(.{}); self.default_spritesheet = initSpritesheet(.{});

View File

@ -27,6 +27,7 @@ pub const ImGUI = @import("./imgui.zig");
pub const Gfx = @import("./graphics.zig"); pub const Gfx = @import("./graphics.zig");
pub const Input = @import("./input.zig"); pub const Input = @import("./input.zig");
pub const Audio = @import("./audio.zig"); pub const Audio = @import("./audio.zig");
pub const SlotMapType = @import("./slot_map.zig").SlotMapType;
const EmbeddedAssets = @import("embedded_assets"); const EmbeddedAssets = @import("embedded_assets");

View File

@ -87,7 +87,7 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
}; };
pub fn clearRetainingCapacity(self: *Self) void { pub fn clearRetainingCapacity(self: *Self) void {
self.* = .init(self.slots.items); self.* = .initBuffer(self.slots.items);
} }
fn insertHole(self: *Self, index: Index) void { 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; var self: Self = .empty;
self.slots = .initBuffer(slots); self.slots = .initBuffer(slots);
return self; 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);
}
}; };
} }