diff --git a/src/assets/tiled/first.tmx b/src/assets/tiled/first.tmx index 12ed9a9..db04b68 100644 --- a/src/assets/tiled/first.tmx +++ b/src/assets/tiled/first.tmx @@ -1,7 +1,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, @@ -9,7 +9,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,5,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,5,0,0,0,41,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, @@ -18,6 +18,28 @@ 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, +0,0,0,0,1,2,2,2,2,2,2,2,2,4,0,0,0,0,0,0, +0,0,0,0,17,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0, +0,0,0,0,17,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0, +0,0,0,0,17,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0, +0,0,0,0,17,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0, +0,0,0,0,17,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0, +0,0,0,0,17,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0, +0,0,0,0,83,2,2,68,2,2,2,37,67,36,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/src/assets/tiled/main.tiled-session b/src/assets/tiled/main.tiled-session index ebbdeba..036a136 100644 --- a/src/assets/tiled/main.tiled-session +++ b/src/assets/tiled/main.tiled-session @@ -28,6 +28,7 @@ "recentFiles": [ "first.tmx" ], + "textEdit.monospace": true, "tileset.lastUsedFormat": "tsx", "tileset.tileSize": { "height": 8, diff --git a/src/assets/tiled/tileset.tsx b/src/assets/tiled/tileset.tsx index 70adfea..2522770 100644 --- a/src/assets/tiled/tileset.tsx +++ b/src/assets/tiled/tileset.tsx @@ -1,4 +1,14 @@ + + + + + + + + + + diff --git a/src/entity.zig b/src/entity.zig index b3b3aa5..b3ebe1d 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -12,7 +12,9 @@ pub const Id = List.Id; pub const Type = enum { nil, - player + player, + solid, + pot, }; type: Type, diff --git a/src/game.zig b/src/game.zig index 4390668..be19d79 100644 --- a/src/game.zig +++ b/src/game.zig @@ -25,34 +25,58 @@ pub const Input = struct { move_down: Window.KeyState, move_left: Window.KeyState, move_right: Window.KeyState, + restart: bool }; -const tile_size = Vec2.init(8, 8); +pub const Level = struct { + entities: Entity.List, + timers: Timer.List, + + pub const empty = Level{ + .entities = .empty, + .timers = .empty + }; + + pub fn clone(self: *Level, gpa: Allocator) !Level { + var entities = try self.entities.clone(gpa); + errdefer entities.deinit(gpa); + + var timers = try self.timers.clone(gpa); + errdefer timers.deinit(gpa); + + return Level{ + .entities = entities, + .timers = timers + }; + } + + pub fn deinit(self: *Level, gpa: Allocator) void { + self.entities.deinit(gpa); + self.timers.deinit(gpa); + } +}; canvas_size: Vec2, -entities: Entity.List, -timers: Timer.List, +level: Level, + +current_level: u32, +levels: std.ArrayList(Level), -last_move: Vec2, last_up_repeat_at: ?f64 = null, last_down_repeat_at: ?f64 = null, last_left_repeat_at: ?f64 = null, last_right_repeat_at: ?f64 = null, -pub fn init(gpa: Allocator) !Game { - var game = Game{ - .canvas_size = (Vec2.init(20, 15)).multiply(tile_size), - .entities = .empty, - .timers = .empty, - .last_move = .init(0, 0) - }; - errdefer game.deinit(gpa); +show_grid: bool = false, - _ = try game.entities.insert(gpa, .{ - .type = .player, - .position = .init(0, 0), - .render_tile = .{ .id = .player }, - }); +pub fn init(gpa: Allocator) !Game { + var self = Game{ + .canvas_size = (Vec2.init(20, 15)), + .level = .empty, + .levels = .empty, + .current_level = 0, + }; + errdefer self.deinit(gpa); const manager = try tiled.ResourceManager.init(); defer manager.deinit(); @@ -62,6 +86,24 @@ pub fn init(gpa: Allocator) !Game { const map = try manager.loadMapFromBuffer(@embedFile("assets/tiled/first.tmx")); defer map.deinit(); + try self.levels.append(gpa, try loadLevelFromTiled(gpa, map)); + try self.restartLevel(gpa); + + return self; +} + +fn restartLevel(self: *Game, gpa: Allocator) !void { + const level_copy = try self.levels.items[self.current_level].clone(gpa); + errdefer level_copy.deinit(gpa); + + self.level.deinit(gpa); + self.level = level_copy; +} + +fn loadLevelFromTiled(gpa: Allocator, map: tiled.Map) !Level { + var level: Level = .empty; + errdefer level.deinit(gpa); + var layer_iter = map.iterLayers(); while (layer_iter.next()) |layer| { if (layer.layer.visible == 0) { @@ -71,31 +113,127 @@ pub fn init(gpa: Allocator) !Game { continue; } + const layer_props = tiled.Properties{ .inner = layer.layer.properties }; + const is_layer_solid = layer_props.getPropertyBool("solid") orelse false; + const map_width = map.map.width; for (0..map.map.height) |y| { for (0..map_width) |x| { const tile = map.getTile(layer, x, y) orelse continue; + const tile_props = tiled.Properties{ .inner = tile.tile.properties }; + const tile_type: []const u8 = std.mem.span(tile_props.getPropertyString("type") orelse ""); - if (tile.getPropertyString("type")) |tile_type| { - _ = tile_type; // autofix + const tile_size = Vec2.init(8, 8); + var entity: Entity = .{ + .type = .nil, + .position = Vec2.init(@floatFromInt(x), @floatFromInt(y)), + .render_tile = .{ .position = tile.getUpperLeft().divide(tile_size) }, + }; + if (std.mem.eql(u8, tile_type, "player")) { + entity.type = .player; + } else if (std.mem.eql(u8, tile_type, "pot")) { + entity.type = .pot; + } else if (is_layer_solid) { + entity.type = .solid; } - _ = try game.entities.insert(gpa, .{ - .type = .nil, - .position = Vec2.init(@floatFromInt(x), @floatFromInt(y)).multiply(tile_size), - .render_tile = .{ .position = tile.getUpperLeft().divide(tile_size) }, - }); + _ = try level.entities.insert(gpa, entity); } } } - return game; + return level; } pub fn deinit(self: *Game, gpa: Allocator) void { - self.entities.deinit(gpa); - self.timers.deinit(gpa); + self.level.deinit(gpa); + for (self.levels.items) |*level| { + level.deinit(gpa); + } + self.levels.deinit(gpa); +} + +fn drawGrid(self: *Game, size: Vec2, color: Vec4, line_width: f32) void { + var x: f32 = 0; + while (x < self.canvas_size.x) { + x += size.x; + Gfx.drawLine( + .init(x, 0), + .init(x, self.canvas_size.y), + color, + line_width + ); + } + + var y: f32 = 0; + while (y < self.canvas_size.y) { + y += size.y; + Gfx.drawLine( + .init(0, y), + .init(self.canvas_size.x, y), + color, + line_width + ); + } +} + +fn getEntityAt(self: *Game, pos: Vec2) ?Entity.Id { + var iter = self.level.entities.iterator(); + while (iter.next()) |tuple| { + const entity = tuple.item; + if (entity.position.eql(pos)) { + return tuple.id; + } + } + + return null; +} + +fn isSolidAt(self: *Game, pos: Vec2) bool { + if (self.getEntityAt(pos)) |entity_id| { + const entity = self.level.entities.getAssumeExists(entity_id); + return entity.type == .solid; + } + + return false; +} + +fn canMove(self: *Game, entity: *Entity, dir: Vec2) bool { + const next_pos = entity.position.add(dir); + if (self.isSolidAt(next_pos)) { + return false; + } + if (next_pos.x < 0 or next_pos.x >= self.canvas_size.x) { + return false; + } + if (next_pos.y < 0 or next_pos.y >= self.canvas_size.y) { + return false; + } + + return true; +} + +fn moveEntity(self: *Game, entity: *Entity, dir: Vec2) bool { + if (entity.type == .solid) { + return false; + } + + if (dir.x == 0 and dir.y == 0) { + return true; + } + + const next_pos = entity.position.add(dir); + if (self.getEntityAt(next_pos)) |next_entity_id| { + const next_entity = self.level.entities.getAssumeExists(next_entity_id); + if (!self.moveEntity(next_entity, dir)) { + return false; + } + } + + entity.position = next_pos; + + return true; } pub fn getInput(self: *Game, window: *Window) Input { @@ -109,45 +247,27 @@ pub fn getInput(self: *Game, window: *Window) Input { .move_down = window.getKeyState(.S), .move_left = window.getKeyState(.A), .move_right = window.getKeyState(.D), + .restart = window.isKeyPressed(.R) }; } -fn drawGrid(self: *Game, size: Vec2, color: Vec4) void { - var x: f32 = 0; - while (x < self.canvas_size.x) { - x += size.x; - Gfx.drawLine( - .init(x, 0), - .init(x, self.canvas_size.y), - color, - 1 - ); - } - - var y: f32 = 0; - while (y < self.canvas_size.y) { - y += size.y; - Gfx.drawLine( - .init(0, y), - .init(self.canvas_size.x, y), - color, - 1 - ); - } -} - pub fn tick(self: *Game, input: Input) !void { Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), rgb_hex("#222323").?); - self.drawGrid(tile_size, rgb(20, 20, 20)); + if (self.show_grid) { + self.drawGrid(.init(1, 1), rgb(20, 20, 20), 0.1); + } - self.timers.now += input.dt; + if (input.restart) { + try self.restartLevel(input.allocator); + } + + self.level.timers.now += input.dt; var move: Vec2 = .init(0, 0); - defer self.last_move = move; const repeat_options = Window.KeyState.RepeatOptions{ - .first_at = 0.4, - .period = 0.2 + .first_at = 0.3, + .period = 0.1 }; if (input.move_up.pressed or input.move_up.repeat(&self.last_up_repeat_at, repeat_options)) { @@ -163,18 +283,16 @@ pub fn tick(self: *Game, input: Input) !void { move.x += 1; } - var iter = self.entities.iterator(); + var iter = self.level.entities.iterator(); while (iter.nextItem()) |entity| { if (entity.type == .player) { - // const velocity = input.move.multiplyScalar(100); - // entity.position = entity.position.add(velocity.multiplyScalar(input.dt)); - entity.position = entity.position.add(move.multiply(tile_size)); + _ = self.moveEntity(entity, move); } if (entity.render_tile) |render_tile| { switch (render_tile) { - .id => |tile_id| Gfx.drawTileById(tile_id, entity.position, tile_size, rgb(255, 255, 255)), - .position => |position| Gfx.drawTile(position, entity.position, tile_size, rgb(255, 255, 255)), + .id => |tile_id| Gfx.drawTileById(tile_id, entity.position, .init(1,1), rgb(255, 255, 255)), + .position => |position| Gfx.drawTile(position, entity.position, .init(1,1), rgb(255, 255, 255)), } } } @@ -190,6 +308,7 @@ pub fn debug(self: *Game) !void { } defer imgui.endWindow(); - imgui.textFmt("Entities: {}", .{self.entities.len}); - imgui.textFmt("Timers: {}", .{self.timers.array_list.len}); + imgui.textFmt("Entities: {}", .{self.level.entities.len}); + imgui.textFmt("Timers: {}", .{self.level.timers.array_list.len}); + _ = imgui.checkbox("Show grid", &self.show_grid); } diff --git a/src/generational_array_list.zig b/src/generational_array_list.zig index 3d49e69..52befe7 100644 --- a/src/generational_array_list.zig +++ b/src/generational_array_list.zig @@ -300,6 +300,26 @@ pub fn GenerationalArrayList(Item: type) type { allocator.free(self.items[0..self.capacity]); } + pub fn clone(self: *Self, allocator: Allocator) !Self { + const items = try allocator.dupe(Item, self.items[0..self.capacity]); + errdefer allocator.free(items); + + const generations = try allocator.dupe(Generation, self.generations[0..self.capacity]); + errdefer allocator.free(generations); + + const unused = try allocator.dupe(u8, self.unused[0..divCeilGeneration(self.capacity)]); + errdefer allocator.free(unused); + + return Self{ + .items = items.ptr, + .generations = generations.ptr, + .unused = unused.ptr, + .len = self.len, + .count = self.count, + .capacity = self.capacity + }; + } + pub fn getMetadata(self: *Self) Metadata { return Metadata{ .len = self.len, diff --git a/src/tiled.zig b/src/tiled.zig index 616058d..58700ba 100644 --- a/src/tiled.zig +++ b/src/tiled.zig @@ -63,18 +63,6 @@ pub const ResourceManager = struct { pub const Tile = struct { tile: *c.tmx_tile, - pub fn getPropertyString(self: Tile, key: [*:0]const u8) ?[*:0]const u8 { - const maybe_prop = c.tmx_get_property(self.tile.properties, key); - if (maybe_prop == null) { - return null; - } - const prop: *c.tmx_property = maybe_prop.?; - if (prop.type != c.PT_STRING) { - return null; - } - return prop.value.string; - } - pub fn getUpperLeft(self: Tile) Vec2 { return Vec2.init( @floatFromInt(self.tile.ul_x), @@ -152,4 +140,38 @@ pub const Layer = struct { }; }; +pub const Properties = struct { + inner: ?*c.tmx_properties, + + pub fn getPropertyString(self: Properties, key: [*:0]const u8) ?[*:0]const u8 { + const inner = self.inner orelse return null; + + const maybe_prop = c.tmx_get_property(inner, key); + if (maybe_prop == null) { + return null; + } + const prop: *c.tmx_property = maybe_prop.?; + if (prop.type != c.PT_STRING) { + return null; + } + return prop.value.string; + } + + pub fn getPropertyBool(self: Properties, key: [*:0]const u8) ?bool { + const inner = self.inner orelse return null; + + const maybe_prop = c.tmx_get_property(inner, key); + if (maybe_prop == null) { + return null; + } + + const prop: *c.tmx_property = maybe_prop.?; + if (prop.type != c.PT_BOOL) { + return null; + } + + return prop.value.boolean != 0; + } +}; + pub const FLIP_BITS_REMOVAL: u32 = c.TMX_FLIP_BITS_REMOVAL; diff --git a/src/timer.zig b/src/timer.zig index 1b67a7f..4460617 100644 --- a/src/timer.zig +++ b/src/timer.zig @@ -32,6 +32,16 @@ pub const List = struct { self.array_list.deinit(gpa); } + pub fn clone(self: *List, gpa: Allocator) !List { + const array_list = try self.array_list.clone(gpa); + errdefer array_list.deinit(gpa); + + return List{ + .now = self.now, + .array_list = array_list + }; + } + pub fn start(self: *List, gpa: Allocator, opts: Options) !Id { assert(opts.duration > 0); return try self.array_list.insert(gpa, .{