From 892fbb38be9fcbb3176326143f8ece1433d99bb4 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 18 Jul 2026 23:50:32 +0300 Subject: [PATCH] add terrain --- build.zig | 7 +--- src/app.zig | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/input.zig | 2 +- 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/build.zig b/build.zig index ea0663a..caaaceb 100644 --- a/build.zig +++ b/build.zig @@ -11,11 +11,8 @@ pub fn build(b: *std.Build) !void { "Prototype_Character/Default/idle.png", "Prototype_Character/Default/walk.png", "Prototype_Character/Default/death.png", - "font/pixeloid/PixeloidSans.ttf" - // "sokoban/sokoban_tilesheet.png", - // "roboto-font/Roboto-Regular.ttf", - // "tiled/map.tmx", - // "tiled/tileset.tsx" + "font/pixeloid/PixeloidSans.ttf", + "tranquil_tunnels_transparent.png" }; const asset_dir_realpath = try b.build_root.join(b.graph.arena, &.{ assets_dir }); diff --git a/src/app.zig b/src/app.zig index 9fb1f9f..57c3bef 100644 --- a/src/app.zig +++ b/src/app.zig @@ -44,6 +44,14 @@ idle_animations: AnimationDirections, walk_animations: AnimationDirections, death_animations: AnimationDirections, +terrain_sheet: Tilesheet, +terrain_tiles: []Tile, + +const terrain_origin_x: i32 = -80; +const terrain_origin_y: i32 = -60; +const terrain_width = 160; +const terrain_height = 70; + const EntitySlotMap = SlotMapType(u16, u16, Entity); const EntityId = EntitySlotMap.Id; @@ -219,7 +227,7 @@ const Entity = struct { }; const Upgrades = struct { - minion_count: u32 = 0, + minion_count: u32 = 1, minion_chop_damage: u32 = 2, minion_chop_rate: f32 = 1, minion_throw_cooldown: Nanoseconds = std.time.ns_per_s, @@ -227,7 +235,7 @@ const Upgrades = struct { minion_carry_capacity: u32 = 1, minion_walk: MinionWalkStats = .{}, - tree_station_count: u32 = 0, + tree_station_count: u32 = 1, tree_level: u32 = 1, tree_health: u32 = tree_levels[0].health, tree_respawn_duration: Nanoseconds = tree_levels[0].respawn_duration, @@ -390,6 +398,10 @@ const Shop = struct { }; }; +const Tile = struct { + sprite: Gfx.Sprite.Id +}; + fn initImageDataFromSTB(img: STBImage) Gfx.ImageData { return Gfx.ImageData{ .width = img.width, @@ -429,6 +441,16 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { .init(32, 32) ); + const terrain_sheet = try Tilesheet.init( + plt.arena, + initImageDataFromSTB(try STBImage.load(plt.assets.readFile("tranquil_tunnels_transparent.png"))), + 128, + 128, + .init(8, 8) + ); + + const terrain_tiles = try plt.arena.alloc(Tile, terrain_width*terrain_height); + const ns_per_s = std.time.ns_per_s; const idle_fps = 5; const walk_fps = 5; @@ -449,9 +471,13 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { .eternal_flame_id = undefined, .upgrades = .{}, .shop = undefined, + + .terrain_sheet = terrain_sheet, + .terrain_tiles = terrain_tiles, }; try self.initShop(plt.arena); + self.initTerrain(); self.spawnBurnStation(100); self.spawnBurnStation(150); @@ -553,6 +579,62 @@ fn initShop(self: *App, arena: Allocator) !void { }; } +fn pickRandom(self: *App, T: type, sprites: []const T) T { + const rng = self.prng.random(); + return sprites[rng.uintLessThan(usize, sprites.len)]; +} + +fn initTerrain(self: *App) void { + @memset(self.terrain_tiles, Tile{ + .sprite = Gfx.getNilSprite() + }); + + const surface_tiles = &[_]Gfx.Sprite.Id{ + self.terrain_sheet.get(4, 23), + self.terrain_sheet.get(5, 23), + self.terrain_sheet.get(3, 23), + self.terrain_sheet.get(4, 24) + }; + + for (0..terrain_width) |x| { + const tile = self.getTerrainTile(@as(i32, @intCast(x)) + terrain_origin_x, 0); + tile.sprite = self.pickRandom(Gfx.Sprite.Id, surface_tiles); + } + + const underground_tiles = &[_]Gfx.Sprite.Id{ + self.terrain_sheet.get(11, 23), + self.terrain_sheet.get(12, 23), + self.terrain_sheet.get(11, 24), + self.terrain_sheet.get(12, 24), + self.terrain_sheet.get(3, 107), + self.terrain_sheet.get(3, 107), + self.terrain_sheet.get(3, 107), + }; + + for (0..terrain_width) |x| { + for (1..4) |y| { + const tile = self.getTerrainTile(@as(i32, @intCast(x)) + terrain_origin_x, @intCast(y)); + tile.sprite = self.pickRandom(Gfx.Sprite.Id, underground_tiles); + } + } +} + +fn getTerrainTile(self: *App, x: i32, y: i32) *Tile { + const left_edge = terrain_origin_x; + const right_edge = terrain_width + terrain_origin_x - 1; + + const top_edge = terrain_origin_y; + const bottom_edge = terrain_height + terrain_origin_y - 1; + + assert(left_edge <= x and x <= right_edge); + assert(top_edge <= y and y <= bottom_edge); + const x_u32: u32 = @intCast(x-terrain_origin_x); + const y_u32: u32 = @intCast(y-terrain_origin_y); + return &self.terrain_tiles[y_u32 * terrain_width + x_u32]; +} + +// fn getTerrainEdgeLeft(self: *App) + fn spawnEntity(self: *App, e: Entity) ?EntityId { return self.entities.insert(e) catch { log.warn("Failed to spawn entity {}, entity limit reached", .{e.type}); @@ -1153,7 +1235,7 @@ fn showShopPanel( } Gfx.drawText(self.font, .{ - .text = try std.fmt.allocPrint(plt.arena, "{}H", .{cost}), + .text = if (limit_reached) "MAX" else try std.fmt.allocPrint(plt.arena, "{}H", .{cost}), .pos = .init(panel_size.x - 20, 0), .height = 24, .color = text_color, @@ -1605,6 +1687,21 @@ pub fn frame(self: *App, plt: Platform.Frame) !void { } } + { // Draw terrain + for (0..terrain_height) |y| { + for (0..terrain_width) |x| { + const tile = self.terrain_tiles[y * terrain_width + x]; + if (tile.sprite == Gfx.getNilSprite()) { + continue; + } + + const tile_size = Vec2.init(32, 32); + const tile_pos = Vec2.initFromInt(usize, x, y).add(.initFromInt(i32, terrain_origin_x, terrain_origin_y)); + Gfx.drawSprite(tile.sprite, tile_pos.multiply(tile_size), tile_size, .white); + } + } + } + { var tree_iter = self.initEntityIteratorByType(.tree); while (tree_iter.next()) |id| { diff --git a/src/input.zig b/src/input.zig index fda9811..4b746da 100644 --- a/src/input.zig +++ b/src/input.zig @@ -47,7 +47,7 @@ pub fn isMousePressed(self: Input, button: MouseButton) bool { return self.mouse_buttons.pressed.contains(button); } -pub fn iMouseReleased(self: Input, button: MouseButton) bool { +pub fn isMouseReleased(self: Input, button: MouseButton) bool { return self.mouse_buttons.released.contains(button); }