add moveable pots

This commit is contained in:
Rokas Puzonas 2025-12-14 21:19:02 +02:00
parent e5e4e429b6
commit 73fbbafbfc
8 changed files with 286 additions and 80 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1"> <map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="1">
<tileset firstgid="1" source="tileset.tsx"/> <tileset firstgid="1" source="tileset.tsx"/>
<layer id="1" name="Tile Layer 1" width="20" height="15"> <layer id="1" name="Actors" width="20" height="15">
<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,
@ -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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,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, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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
</data>
</layer>
<layer id="2" name="Walls" width="20" height="15">
<properties>
<property name="solid" type="bool" value="true"/>
</properties>
<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,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
</data> </data>
</layer> </layer>
</map> </map>

View File

@ -28,6 +28,7 @@
"recentFiles": [ "recentFiles": [
"first.tmx" "first.tmx"
], ],
"textEdit.monospace": true,
"tileset.lastUsedFormat": "tsx", "tileset.lastUsedFormat": "tsx",
"tileset.tileSize": { "tileset.tileSize": {
"height": 8, "height": 8,

View File

@ -1,4 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.2" name="tileset" tilewidth="8" tileheight="8" tilecount="160" columns="16"> <tileset version="1.10" tiledversion="1.11.2" name="tileset" tilewidth="8" tileheight="8" tilecount="160" columns="16">
<image source="../kenney-micro-roguelike/colored_tilemap_packed.png" width="128" height="80"/> <image source="../kenney-micro-roguelike/colored_tilemap_packed.png" width="128" height="80"/>
<tile id="4">
<properties>
<property name="type" value="player"/>
</properties>
</tile>
<tile id="40">
<properties>
<property name="type" value="pot"/>
</properties>
</tile>
</tileset> </tileset>

View File

@ -12,7 +12,9 @@ pub const Id = List.Id;
pub const Type = enum { pub const Type = enum {
nil, nil,
player player,
solid,
pot,
}; };
type: Type, type: Type,

View File

@ -25,34 +25,58 @@ pub const Input = struct {
move_down: Window.KeyState, move_down: Window.KeyState,
move_left: Window.KeyState, move_left: Window.KeyState,
move_right: Window.KeyState, move_right: Window.KeyState,
restart: bool
}; };
const tile_size = Vec2.init(8, 8); pub const Level = struct {
canvas_size: Vec2,
entities: Entity.List, entities: Entity.List,
timers: Timer.List, timers: Timer.List,
last_move: Vec2, 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,
level: Level,
current_level: u32,
levels: std.ArrayList(Level),
last_up_repeat_at: ?f64 = null, last_up_repeat_at: ?f64 = null,
last_down_repeat_at: ?f64 = null, last_down_repeat_at: ?f64 = null,
last_left_repeat_at: ?f64 = null, last_left_repeat_at: ?f64 = null,
last_right_repeat_at: ?f64 = null, last_right_repeat_at: ?f64 = null,
pub fn init(gpa: Allocator) !Game { show_grid: bool = false,
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);
_ = try game.entities.insert(gpa, .{ pub fn init(gpa: Allocator) !Game {
.type = .player, var self = Game{
.position = .init(0, 0), .canvas_size = (Vec2.init(20, 15)),
.render_tile = .{ .id = .player }, .level = .empty,
}); .levels = .empty,
.current_level = 0,
};
errdefer self.deinit(gpa);
const manager = try tiled.ResourceManager.init(); const manager = try tiled.ResourceManager.init();
defer manager.deinit(); defer manager.deinit();
@ -62,6 +86,24 @@ pub fn init(gpa: Allocator) !Game {
const map = try manager.loadMapFromBuffer(@embedFile("assets/tiled/first.tmx")); const map = try manager.loadMapFromBuffer(@embedFile("assets/tiled/first.tmx"));
defer map.deinit(); 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(); var layer_iter = map.iterLayers();
while (layer_iter.next()) |layer| { while (layer_iter.next()) |layer| {
if (layer.layer.visible == 0) { if (layer.layer.visible == 0) {
@ -71,31 +113,127 @@ pub fn init(gpa: Allocator) !Game {
continue; 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; const map_width = map.map.width;
for (0..map.map.height) |y| { for (0..map.map.height) |y| {
for (0..map_width) |x| { for (0..map_width) |x| {
const tile = map.getTile(layer, x, y) orelse continue; 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| { const tile_size = Vec2.init(8, 8);
_ = tile_type; // autofix var entity: Entity = .{
}
_ = try game.entities.insert(gpa, .{
.type = .nil, .type = .nil,
.position = Vec2.init(@floatFromInt(x), @floatFromInt(y)).multiply(tile_size), .position = Vec2.init(@floatFromInt(x), @floatFromInt(y)),
.render_tile = .{ .position = tile.getUpperLeft().divide(tile_size) }, .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 level.entities.insert(gpa, entity);
} }
} }
} }
return game; return level;
} }
pub fn deinit(self: *Game, gpa: Allocator) void { pub fn deinit(self: *Game, gpa: Allocator) void {
self.entities.deinit(gpa); self.level.deinit(gpa);
self.timers.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 { 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_down = window.getKeyState(.S),
.move_left = window.getKeyState(.A), .move_left = window.getKeyState(.A),
.move_right = window.getKeyState(.D), .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 { 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").?); 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); var move: Vec2 = .init(0, 0);
defer self.last_move = move;
const repeat_options = Window.KeyState.RepeatOptions{ const repeat_options = Window.KeyState.RepeatOptions{
.first_at = 0.4, .first_at = 0.3,
.period = 0.2 .period = 0.1
}; };
if (input.move_up.pressed or input.move_up.repeat(&self.last_up_repeat_at, repeat_options)) { 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; move.x += 1;
} }
var iter = self.entities.iterator(); var iter = self.level.entities.iterator();
while (iter.nextItem()) |entity| { while (iter.nextItem()) |entity| {
if (entity.type == .player) { if (entity.type == .player) {
// const velocity = input.move.multiplyScalar(100); _ = self.moveEntity(entity, move);
// entity.position = entity.position.add(velocity.multiplyScalar(input.dt));
entity.position = entity.position.add(move.multiply(tile_size));
} }
if (entity.render_tile) |render_tile| { if (entity.render_tile) |render_tile| {
switch (render_tile) { switch (render_tile) {
.id => |tile_id| Gfx.drawTileById(tile_id, 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, tile_size, 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(); defer imgui.endWindow();
imgui.textFmt("Entities: {}", .{self.entities.len}); imgui.textFmt("Entities: {}", .{self.level.entities.len});
imgui.textFmt("Timers: {}", .{self.timers.array_list.len}); imgui.textFmt("Timers: {}", .{self.level.timers.array_list.len});
_ = imgui.checkbox("Show grid", &self.show_grid);
} }

View File

@ -300,6 +300,26 @@ pub fn GenerationalArrayList(Item: type) type {
allocator.free(self.items[0..self.capacity]); 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 { pub fn getMetadata(self: *Self) Metadata {
return Metadata{ return Metadata{
.len = self.len, .len = self.len,

View File

@ -63,18 +63,6 @@ pub const ResourceManager = struct {
pub const Tile = struct { pub const Tile = struct {
tile: *c.tmx_tile, 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 { pub fn getUpperLeft(self: Tile) Vec2 {
return Vec2.init( return Vec2.init(
@floatFromInt(self.tile.ul_x), @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; pub const FLIP_BITS_REMOVAL: u32 = c.TMX_FLIP_BITS_REMOVAL;

View File

@ -32,6 +32,16 @@ pub const List = struct {
self.array_list.deinit(gpa); 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 { pub fn start(self: *List, gpa: Allocator, opts: Options) !Id {
assert(opts.duration > 0); assert(opts.duration > 0);
return try self.array_list.insert(gpa, .{ return try self.array_list.insert(gpa, .{