diff --git a/libs/tiled/src/layer.zig b/libs/tiled/src/layer.zig index 184380a..22c3b66 100644 --- a/libs/tiled/src/layer.zig +++ b/libs/tiled/src/layer.zig @@ -136,6 +136,7 @@ pub const TileVariant = struct { const encoded_tiles = try lexer.nextExpectText(); const tiles = try parseEncoding(encoding, scratch.allocator(), encoded_tiles); try temp_tiles.appendSlice(scratch.allocator(), tiles.items); + continue; } else if (node.isTag("chunk")) { const chunk = try initChunkDataFromXml(arena, scratch, lexer, encoding); @@ -158,7 +159,6 @@ pub const TileVariant = struct { .fixed = try arena.dupe(u32, temp_tiles.items) }; } - } fn parseEncoding( diff --git a/libs/tiled/src/tilemap.zig b/libs/tiled/src/tilemap.zig index 3bf4e47..87e0e3a 100644 --- a/libs/tiled/src/tilemap.zig +++ b/libs/tiled/src/tilemap.zig @@ -73,6 +73,9 @@ pub const TilesetReference = struct { pub const Tile = struct { tileset: *const Tileset, id: u32, + flip_horizontal: bool, + flip_vertical: bool, + flip_diagonal: bool, pub fn getProperties(self: Tile) Property.List { return self.tileset.getTileProperties(self.id) orelse .empty; @@ -216,13 +219,19 @@ fn getTilesetByGid(self: *const Tilemap, gid: u32) ?TilesetReference { } pub fn getTile(self: *const Tilemap, tilesets: Tileset.List, gid: u32) ?Tile { - const tileset_ref = self.getTilesetByGid(gid & GlobalTileId.Flag.clear) orelse return null; + const Flag = GlobalTileId.Flag; + + const gid_without_flags = gid & Flag.clear; + const tileset_ref = self.getTilesetByGid(gid_without_flags) orelse return null; const tileset = tilesets.get(tileset_ref.source) orelse return null; - const id = gid - tileset_ref.first_gid; + const id = gid_without_flags - tileset_ref.first_gid; return Tile{ .tileset = tileset, - .id = id + .id = id, + .flip_horizontal = (gid & @intFromEnum(Flag.flipped_horizontally)) > 0, + .flip_vertical = (gid & @intFromEnum(Flag.flipped_vertically)) > 0, + .flip_diagonal = (gid & @intFromEnum(Flag.flipped_diagonally)) > 0 }; } diff --git a/src/assets.zig b/src/assets.zig index 119f1f0..00934e2 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -24,7 +24,6 @@ pub const Tilemap = struct { texture: Gfx.TextureId, tile_size: Engine.Vec2, - pub fn getTileUV(self: Tilemap, tile_x: f32, tile_y: f32) Rect { const texture_info = Engine.Graphics.getTextureInfo(self.texture); const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height); @@ -39,14 +38,17 @@ pub const Tilemap = struct { arena: std.heap.ArenaAllocator, font_id: FontName.EnumArray, -wood01: Audio.Data.Id, +pistol_mask: Gfx.TextureId, +bomb_mask: Gfx.TextureId, +laser_mask: Gfx.TextureId, +dungeon_tilemap: Tilemap, + +skeleton: Gfx.Sprite, +snake: Gfx.Sprite, +fire_spirit: Gfx.Sprite, + map: tiled.Tilemap, tilesets: tiled.Tileset.List, -move_sound: []Audio.Data.Id, - -terrain_tilemap: Tilemap, -players_tilemap: Tilemap, -weapons_tilemap: Tilemap, pub fn init(gpa: std.mem.Allocator) !Assets { var arena = std.heap.ArenaAllocator.init(gpa); @@ -58,11 +60,83 @@ pub fn init(gpa: std.mem.Allocator) !Assets { .italic = try Gfx.addFont("italic", @embedFile("assets/roboto-font/Roboto-Italic.ttf")), }); - const wood01 = try Audio.load(.{ - .format = .vorbis, - .data = @embedFile("assets/wood01.ogg"), + const pistol_mask_image = try STBImage.load(@embedFile("assets/pistol-mask.png")); + defer pistol_mask_image.deinit(); + const pistol_mask_texture = try Gfx.addTexture(&.{ + .{ + .width = pistol_mask_image.width, + .height = pistol_mask_image.height, + .rgba = pistol_mask_image.rgba8_pixels + } }); + const bomb_mask_image = try STBImage.load(@embedFile("assets/bomb-mask.png")); + defer bomb_mask_image.deinit(); + const bomb_mask_texture = try Gfx.addTexture(&.{ + .{ + .width = bomb_mask_image.width, + .height = bomb_mask_image.height, + .rgba = bomb_mask_image.rgba8_pixels + } + }); + + const laser_mask_image = try STBImage.load(@embedFile("assets/laser-mask.png")); + defer laser_mask_image.deinit(); + const laser_mask_texture = try Gfx.addTexture(&.{ + .{ + .width = laser_mask_image.width, + .height = laser_mask_image.height, + .rgba = laser_mask_image.rgba8_pixels + } + }); + + const creatures_image = try STBImage.load(@embedFile("assets/tiny-creatures/tilemap_packed.png")); + defer creatures_image.deinit(); + const creatures_texture = try Gfx.addTexture(&.{ + .{ + .width = creatures_image.width, + .height = creatures_image.height, + .rgba = creatures_image.rgba8_pixels + } + }); + + const snake_image = try STBImage.load(@embedFile("assets/snake.png")); + defer snake_image.deinit(); + const snake_texture = try Gfx.addTexture(&.{ + .{ + .width = snake_image.width, + .height = snake_image.height, + .rgba = snake_image.rgba8_pixels + } + }); + + const fire_spirit_image = try STBImage.load(@embedFile("assets/fire-spirit.png")); + defer fire_spirit_image.deinit(); + const fire_spirit_texture = try Gfx.addTexture(&.{ + .{ + .width = fire_spirit_image.width, + .height = fire_spirit_image.height, + .rgba = fire_spirit_image.rgba8_pixels + } + }); + + const creatures_tilemap = Tilemap{ + .texture = creatures_texture, + .tile_size = .init(16, 16) + }; + const snake = Gfx.Sprite{ + .texture = snake_texture, + .uv = .unit + }; + const skeleton = Gfx.Sprite{ + .texture = creatures_texture, + .uv = creatures_tilemap.getTileUV(1, 0) + }; + const fire_spirit = Gfx.Sprite{ + .texture = fire_spirit_texture, + .uv = .unit + }; + var scratch = std.heap.ArenaAllocator.init(gpa); defer scratch.deinit(); @@ -80,76 +154,42 @@ pub fn init(gpa: std.mem.Allocator) !Assets { gpa, &scratch, &xml_buffers, - @embedFile("assets/tileset.tsx") + @embedFile("assets/dungeon.tsx") ); var tilesets: tiled.Tileset.List = .empty; - try tilesets.add(gpa, "tilemap.tsx", tileset); + try tilesets.add(gpa, "dungeon.tsx", tileset); - const players_image = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Players/tilemap_packed.png")); - defer players_image.deinit(); - const players_texture = try Gfx.addTexture(&.{ + const dungeon_image = try STBImage.load(@embedFile("assets/tiny-dungeon/tilemap_packed.png")); + defer dungeon_image.deinit(); + const dungeon_texture = try Gfx.addTexture(&.{ .{ - .width = players_image.width, - .height = players_image.height, - .rgba = players_image.rgba8_pixels + .width = dungeon_image.width, + .height = dungeon_image.height, + .rgba = dungeon_image.rgba8_pixels } }); - const tileset_image = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Tiles/tilemap_packed.png")); - defer tileset_image.deinit(); - const tileset_texture = try Gfx.addTexture(&.{ - .{ - .width = tileset_image.width, - .height = tileset_image.height, - .rgba = tileset_image.rgba8_pixels - } - }); - - const weapons_tileset = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Weapons/tilemap_packed.png")); - defer weapons_tileset.deinit(); - const weapons_texture = try Gfx.addTexture(&.{ - .{ - .width = weapons_tileset.width, - .height = weapons_tileset.height, - .rgba = weapons_tileset.rgba8_pixels - } - }); - - const move_c = try Audio.load(.{ - .data = @embedFile("assets/kenney_desert-shooter-pack_1.0/Sounds/move-c.ogg"), - .format = .vorbis, - }); - const move_d = try Audio.load(.{ - .data = @embedFile("assets/kenney_desert-shooter-pack_1.0/Sounds/move-d.ogg"), - .format = .vorbis, - }); - const move_sound = try arena.allocator().dupe(Audio.Data.Id, &.{ move_c, move_d }); - return Assets{ .arena = arena, .font_id = font_id_array, - .wood01 = wood01, + .pistol_mask = pistol_mask_texture, + .bomb_mask = bomb_mask_texture, + .laser_mask = laser_mask_texture, + .snake = snake, + .skeleton = skeleton, + .fire_spirit = fire_spirit, .map = map, .tilesets = tilesets, - .move_sound = move_sound, - .terrain_tilemap = .{ - .texture = tileset_texture, - .tile_size = .initFromInt(u32, tileset.tile_width, tileset.tile_height) - }, - .players_tilemap = .{ - .texture = players_texture, - .tile_size = .init(24, 24) - }, - .weapons_tilemap = .{ - .texture = weapons_texture, - .tile_size = .init(24, 24) - }, + .dungeon_tilemap = .{ + .texture = dungeon_texture, + .tile_size = .init(16, 16) + } }; } pub fn deinit(self: *Assets, gpa: std.mem.Allocator) void { - self.map.deinit(); self.tilesets.deinit(gpa); + self.map.deinit(); self.arena.deinit(); } diff --git a/src/assets/dungeon.tsx b/src/assets/dungeon.tsx new file mode 100644 index 0000000..10bb6c1 --- /dev/null +++ b/src/assets/dungeon.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/fire-spirit.png b/src/assets/fire-spirit.png new file mode 100644 index 0000000..f6a14aa Binary files /dev/null and b/src/assets/fire-spirit.png differ diff --git a/src/assets/game.tiled-project b/src/assets/game.tiled-project new file mode 100644 index 0000000..d0eb592 --- /dev/null +++ b/src/assets/game.tiled-project @@ -0,0 +1,14 @@ +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "properties": [ + ], + "propertyTypes": [ + ] +} diff --git a/src/assets/map.tmx b/src/assets/map.tmx new file mode 100644 index 0000000..88723c1 --- /dev/null +++ b/src/assets/map.tmx @@ -0,0 +1,23 @@ + + + + + +2147483701,51,51,51,52,52,51,51,51,51,51,52,52,52,51,51,51,51,51,53, +536870963,49,49,49,49,49,49,49,49,50,50,50,50,50,49,49,49,49,49,2684354611, +1610612788,49,43,43,49,49,49,49,49,50,50,50,50,50,49,49,43,43,49,2684354611, +1610612788,49,43,43,49,49,50,49,49,49,50,50,50,50,49,49,43,43,50,2684354611, +1610612788,50,50,49,49,49,50,50,49,49,49,49,49,49,49,49,49,50,50,3758096436, +1610612788,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,50,3758096436, +1610612788,49,49,49,49,49,49,50,50,50,50,49,49,50,49,49,49,49,49,3758096436, +536870963,49,49,49,49,49,49,49,50,50,50,50,50,50,49,49,49,49,49,2684354611, +536870963,49,50,50,49,49,49,49,49,49,50,50,50,50,49,49,49,50,50,3758096436, +536870963,49,50,50,49,49,49,49,49,49,49,49,49,49,49,49,50,50,50,3758096436, +1610612788,50,50,49,49,49,49,50,50,49,49,49,49,49,49,50,50,50,49,2684354611, +1610612788,49,43,43,49,49,50,50,50,50,50,49,49,49,49,50,43,43,49,2684354611, +536870963,49,43,43,49,50,50,50,49,49,49,49,49,49,49,50,43,43,49,3758096436, +536870963,49,49,49,49,50,50,49,49,49,49,49,49,49,49,49,49,49,49,2684354611, +3221225525,1073741875,1073741875,1073741875,1073741875,1073741876,1073741876,1073741875,1073741875,1073741875,1073741875,1073741875,1073741875,1073741876,1073741876,1073741875,1073741875,1073741875,1073741876,1073741877 + + + diff --git a/src/assets/snake.png b/src/assets/snake.png new file mode 100644 index 0000000..ed2f0b2 Binary files /dev/null and b/src/assets/snake.png differ diff --git a/src/assets/tiny-dungeon/License.txt b/src/assets/tiny-dungeon/License.txt new file mode 100644 index 0000000..1b269f6 --- /dev/null +++ b/src/assets/tiny-dungeon/License.txt @@ -0,0 +1,22 @@ + + + Tiny Dungeon (1.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 05-07-2022 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/src/assets/tiny-dungeon/Tilesheet.txt b/src/assets/tiny-dungeon/Tilesheet.txt new file mode 100644 index 0000000..3ecbdd2 --- /dev/null +++ b/src/assets/tiny-dungeon/Tilesheet.txt @@ -0,0 +1,9 @@ +Tilesheet information: + +Tile size • 16px × 16px +Space between tiles • 1px × 1px +--- +Total tiles (horizontal) • 12 tiles +Total tiles (vertical) • 11 tiles +--- +Total tiles in sheet • 132 tiles \ No newline at end of file diff --git a/src/assets/tiny-dungeon/tilemap_packed.png b/src/assets/tiny-dungeon/tilemap_packed.png new file mode 100644 index 0000000..f6e8b93 Binary files /dev/null and b/src/assets/tiny-dungeon/tilemap_packed.png differ diff --git a/src/combat_screen.zig b/src/combat_screen.zig index da23ded..5daa3a4 100644 --- a/src/combat_screen.zig +++ b/src/combat_screen.zig @@ -10,6 +10,7 @@ const GenerationalArrayList = @import("./generational_array_list.zig").Generatio const Engine = @import("./engine/root.zig"); const Nanoseconds = Engine.Nanoseconds; const Vec2 = Engine.Vec2; +const Sprite = Engine.Graphics.Sprite; const Line = Engine.Math.Line; const Rect = Engine.Math.Rect; const Range = Engine.Math.Range; @@ -55,10 +56,11 @@ const Player = struct { max_health: u32 = 0, last_shot_at: ?Nanoseconds = null, invincible_until: ?Nanoseconds = null, - gun: ?Gun = null, + gun: Gun = .pistol, pub fn getRect(self: Player) Rect { - return getCenteredRect(self.kinetic.pos, 16); + const pos = self.kinetic.pos; + return Rect.initCentered(pos.x, pos.y, 16, 24); } }; @@ -90,6 +92,8 @@ const Enemy = struct { kinetic: Kinetic, speed: f32, size: f32, + sprite: Sprite, + target: ?Vec2 = null, distance_to_target: ?f32 = null, @@ -147,18 +151,8 @@ const Wave = struct { }; }; -const Pickup = struct { - const Kind = enum { - money - }; - - pos: Vec2, - kind: Kind, -}; - const world_size = Vec2.init(20 * 16, 15 * 16); const invincibility_duration_s = 0.5; -const pickup_spawn_duration_s = Range.init(1, 5); const wave_infos = [_]Wave.Info{ .{ .kind = .regular, @@ -196,24 +190,15 @@ bullets: std.ArrayList(Bullet) = .empty, lasers: std.ArrayList(Laser) = .empty, bombs: std.ArrayList(Bomb) = .empty, -pickups: std.ArrayList(Pickup) = .empty, -next_pickup_spawn_at: Nanoseconds, - wave_timer: Nanoseconds = 0, waves: std.ArrayList(Wave) = .empty, spawned_waves: std.ArrayList(usize) = .empty, pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScreen { - var rng = RNGState.init(seed); - - const next_pickup_spawn_at_s = pickup_spawn_duration_s.random(rng.random()); - const next_pickup_spawn_at: Nanoseconds = @intFromFloat(next_pickup_spawn_at_s * std.time.ns_per_s); - return CombatScreen{ .gpa = gpa, .assets = assets, - .next_pickup_spawn_at = next_pickup_spawn_at, .player = .{ .kinetic = .{ .pos = world_size.divideScalar(2) @@ -222,7 +207,7 @@ pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScre .health = state.max_health, .max_health = state.max_health, }, - .rng = rng + .rng = RNGState.init(seed) }; } @@ -235,7 +220,6 @@ pub fn deinit(self: *CombatScreen) void { self.bullets.deinit(self.gpa); self.enemies.deinit(self.gpa); self.waves.deinit(self.gpa); - self.pickups.deinit(self.gpa); self.spawned_waves.deinit(self.gpa); self.lasers.deinit(self.gpa); self.bombs.deinit(self.gpa); @@ -244,6 +228,7 @@ pub fn deinit(self: *CombatScreen) void { const EnemyOptions = struct { pos: ?Vec2 = null, + sprite: Sprite, size: f32 = 20 }; @@ -290,18 +275,8 @@ pub fn spawnEnemy(self: *CombatScreen, opts: EnemyOptions) !Enemy.Id { return try self.enemies.insert(self.gpa, Enemy{ .kinetic = .{ .pos = pos }, .speed = 50, - .size = opts.size - }); -} - -pub fn spawnPickup(self: *CombatScreen) !void { - const margin = 10; - const spawn_area = (Rect{ .pos = .zero, .size = world_size }).grow(-margin); - const pos = Vec2.initRandomRect(self.rng.random(), spawn_area); - - try self.pickups.append(self.gpa, Pickup{ - .pos = pos, - .kind = .money + .size = opts.size, + .sprite = opts.sprite }); } @@ -310,7 +285,49 @@ const TickResult = struct { player_finished: bool }; +fn drawMap(self: *CombatScreen, frame: *Engine.Frame) void { + const tilemap = self.assets.dungeon_tilemap; + const texture_info = Engine.Graphics.getTextureInfo(tilemap.texture); + const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height); + + for (self.assets.map.layers) |layer| { + if (layer.variant != .tile) { + continue; + } + + const tile_layer = layer.variant.tile; + for (0..tile_layer.height) |y| { + for (0..tile_layer.width) |x| { + const tile_gid = tile_layer.get(@intCast(x), @intCast(y)) orelse continue; + if (tile_gid == 0) continue; + + const tile = self.assets.map.getTile(self.assets.tilesets, tile_gid) orelse continue; + const tile_id_f32: f32 = @floatFromInt(tile.id); + const width_in_tiles = tilemap_size.x / tilemap.tile_size.x; + const tile_x = @rem(tile_id_f32, width_in_tiles); + const tile_y = @divFloor(tile_id_f32, width_in_tiles); + + frame.drawRectangle(.{ + .rect = .{ + .pos = Vec2.initFromInt(usize, x, y).multiply(tilemap.tile_size), + .size = tilemap.tile_size + }, + .color = rgb(255, 255, 255), + .sprite = .{ + .texture = tilemap.texture, + .uv = tilemap.getTileUV(tile_x, tile_y) + }, + .uv_flip_diagonal = tile.flip_diagonal, + .uv_flip_vertical = tile.flip_vertical, + .uv_flip_horizontal = tile.flip_horizontal, + }); + } + } + } +} + pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResult { + const dt = frame.deltaTime(); var result = TickResult{ @@ -357,7 +374,9 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul while (wave.enemies_spawned < expected_enemies) { switch (wave.kind) { .regular => { - _ = try self.spawnEnemy(.{}); + _ = try self.spawnEnemy(.{ + .sprite = self.assets.skeleton + }); }, .snake => { var arena = std.heap.ArenaAllocator.init(self.gpa); @@ -367,7 +386,9 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul const rand = self.rng.random(); const snake_length = rand.intRangeAtMost(u32, wave.min_group_size, wave.max_group_size); - const head_id = try self.spawnEnemy(.{ }); + const head_id = try self.spawnEnemy(.{ + .sprite = self.assets.snake + }); try enemies.append(arena.allocator(), head_id); const head = self.enemies.getAssumeExists(head_id); @@ -379,6 +400,7 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul for (0..snake_length) |i| { const tail_pos = head_pos.sub(dir_to_center.multiplyScalar(@floatFromInt(i * gap))); const tail_id = try self.spawnEnemy(.{ + .sprite = self.assets.snake, .pos = tail_pos }); try enemies.append(arena.allocator(), tail_id); @@ -407,6 +429,7 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul const enemy_id = try self.spawnEnemy(.{ .pos = center.add(Vec2.initAngle(angle).multiplyScalar(distance)), + .sprite = self.assets.fire_spirit, .size = 10 }); try enemies.append(arena.allocator(), enemy_id); @@ -434,19 +457,9 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul } } - if (self.wave_timer >= self.next_pickup_spawn_at) { - const next_pickup_spawn_at_s = pickup_spawn_duration_s.random(self.rng.random()); - const next_pickup_spawn_at: Nanoseconds = @intFromFloat(next_pickup_spawn_at_s * std.time.ns_per_s); - self.next_pickup_spawn_at = self.wave_timer + next_pickup_spawn_at; - try self.spawnPickup(); - } - frame.graphics.canvas_size = world_size; - frame.drawRectangle(.{ - .rect = .init(0, 0, world_size.x, world_size.y), - .color = rgb(20, 20, 20) - }); + self.drawMap(frame); var dir = Vec2.init(0, 0); if (frame.isKeyDown(.W)) { @@ -500,45 +513,52 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul if (frame.isMouseDown(.left) and cooldown_complete) { self.player.last_shot_at = frame.time_ns; - if (self.player.gun) |gun| { - switch (gun) { - .pistol => { - try self.bullets.append(self.gpa, .{ - .kinetic = .{ - .pos = self.player.kinetic.pos, - }, - .dir = bullet_dir, - .speed = 50 - }); - }, - .bomb => { - try self.bombs.append(self.gpa, .{ - .kinetic = .{ - .pos = self.player.kinetic.pos, - .vel = bullet_dir.multiplyScalar(800) - }, - .size = 10, - .explode_at = frame.time_ns + std.time.ns_per_s, - .explosion_radius = 50 - }); - }, - .laser => { - try self.lasers.append(self.gpa, .{ - .origin = self.player.kinetic.pos, - .dir = bullet_dir, - .size = 10, - .created_at = frame.time_ns, - .duration = std.time.ns_per_s - }); - } + switch (self.player.gun) { + .pistol => { + try self.bullets.append(self.gpa, .{ + .kinetic = .{ + .pos = self.player.kinetic.pos, + }, + .dir = bullet_dir, + .speed = 50 + }); + }, + .bomb => { + try self.bombs.append(self.gpa, .{ + .kinetic = .{ + .pos = self.player.kinetic.pos, + .vel = bullet_dir.multiplyScalar(800) + }, + .size = 10, + .explode_at = frame.time_ns + std.time.ns_per_s, + .explosion_radius = 50 + }); + }, + .laser => { + try self.lasers.append(self.gpa, .{ + .origin = self.player.kinetic.pos, + .dir = bullet_dir, + .size = 10, + .created_at = frame.time_ns, + .duration = std.time.ns_per_s + }); } } } } + frame.drawRectangle(.{ .rect = self.player.getRect(), - .color = rgb(255, 255, 255) + .color = rgb(255, 255, 255), + .sprite = .{ + .texture = switch (self.player.gun) { + .pistol => self.assets.pistol_mask, + .bomb => self.assets.bomb_mask, + .laser => self.assets.laser_mask, + }, + .uv = .unit + } }); for (self.bullets.items) |*bullet| { @@ -775,40 +795,19 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul } enemy.kinetic.update(dt, .{}); + frame.drawRectangle(.{ .rect = enemy_rect, - .color = rgb(20, 200, 20) + .color = rgba(20, 20, 200, 0.2), }); - } - } - - { - var index: usize = 0; - while (index < self.pickups.items.len) { - var destroy: bool = false; - const pickup = self.pickups.items[index]; - - const pickup_rect = Rect.initCentered(pickup.pos.x, pickup.pos.y, 10, 10); - - if (pickup_rect.hasOverlap(self.player.getRect())) { - switch (pickup.kind) { - .money => { - state.money += 1; - } - } - destroy = true; - } + const pos = enemy.kinetic.pos; + const sprite_size = enemy.sprite.getSize(); frame.drawRectangle(.{ - .rect = pickup_rect, - .color = rgb(20, 20, 200) + .rect = Rect.initCentered(pos.x, pos.y, sprite_size.x, sprite_size.y), + .color = rgb(255, 255, 255), + .sprite = enemy.sprite }); - - if (destroy) { - _ = self.pickups.swapRemove(index); - } else { - index += 1; - } } } @@ -842,7 +841,7 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul frame.drawTextFormat(.init(10, 30), text_opts, "{d}", .{ state.money }); frame.drawTextFormat(.init(10, 50), text_opts, "{d}/{d}", .{ self.player.health, self.player.max_health }); - frame.drawTextFormat(.init(10, 70), text_opts, "{?}", .{ self.player.gun }); + frame.drawTextFormat(.init(10, 70), text_opts, "{}", .{ self.player.gun }); result.player_died = (self.player.health == 0); if (self.enemies.count == 0 and self.waves.items.len == 0 and self.spawned_waves.items.len == wave_infos.len) { diff --git a/src/engine/graphics.zig b/src/engine/graphics.zig index 7b20316..0496baa 100644 --- a/src/engine/graphics.zig +++ b/src/engine/graphics.zig @@ -46,6 +46,9 @@ pub const Command = union(enum) { sprite: ?Sprite = null, rotation: f32 = 0, origin: Vec2 = .init(0, 0), + uv_flip_diagonal: bool = false, + uv_flip_horizontal: bool = false, + uv_flip_vertical: bool = false }; pub const DrawCircle = struct { @@ -88,6 +91,12 @@ pub const TextureInfo = Texture.Info; pub const Sprite = struct { texture: TextureId, uv: Rect, + + pub fn getSize(self: Sprite) Vec2 { + const texture_info = getTextureInfo(self.texture); + const texture_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height); + return self.uv.size.multiply(texture_size); + } }; var gpa: std.mem.Allocator = undefined; @@ -333,7 +342,41 @@ fn drawRectangle(opts: Command.DrawRectangle) void { if (opts.sprite) |sprite| { const uv = sprite.uv; - const quad = [4]Vertex{ .{ .pos = pos.add(top_left), .uv = .init(uv.left(), uv.top()) }, .{ .pos = pos.add(top_right), .uv = .init(uv.right(), uv.top()) }, .{ .pos = pos.add(bottom_right), .uv = .init(uv.right(), uv.bottom()) }, .{ .pos = pos.add(bottom_left), .uv = .init(uv.left(), uv.bottom()) } }; + var uv_top_left = Vec2.init(uv.left(), uv.top()); + var uv_top_right = Vec2.init(uv.right(), uv.top()); + var uv_bottom_right = Vec2.init(uv.right(), uv.bottom()); + var uv_bottom_left = Vec2.init(uv.left(), uv.bottom()); + + if (opts.uv_flip_diagonal) { + std.mem.swap(Vec2, &uv_bottom_left, &uv_top_right); + } + if (opts.uv_flip_horizontal) { + std.mem.swap(Vec2, &uv_top_left, &uv_top_right); + std.mem.swap(Vec2, &uv_bottom_left, &uv_bottom_right); + } + if (opts.uv_flip_vertical) { + std.mem.swap(Vec2, &uv_top_left, &uv_bottom_left); + std.mem.swap(Vec2, &uv_top_right, &uv_bottom_right); + } + + const quad = [4]Vertex{ + .{ + .pos = pos.add(top_left), + .uv = uv_top_left + }, + .{ + .pos = pos.add(top_right), + .uv = uv_top_right, + }, + .{ + .pos = pos.add(bottom_right), + .uv = uv_bottom_right + }, + .{ + .pos = pos.add(bottom_left), + .uv = uv_bottom_left + } + }; drawQuad(quad, opts.color, sprite.texture); } else { const quad = .{ pos.add(top_left), pos.add(top_right), pos.add(bottom_right), pos.add(bottom_left) }; diff --git a/src/engine/math.zig b/src/engine/math.zig index 885907c..720b738 100644 --- a/src/engine/math.zig +++ b/src/engine/math.zig @@ -368,6 +368,11 @@ pub const Rect = struct { pos: Vec2, size: Vec2, + pub const unit = Rect{ + .pos = .zero, + .size = .init(1, 1), + }; + pub const zero = Rect{ .pos = Vec2.zero, .size = Vec2.zero diff --git a/src/game.zig b/src/game.zig index 38b4f69..0f419c7 100644 --- a/src/game.zig +++ b/src/game.zig @@ -124,10 +124,6 @@ pub fn debug(self: *Game) !void { } defer imgui.endWindow(); - if (imgui.button("Spawn enemy")) { - _ = try self.combat_screen.spawnEnemy(.{}); - } - if (imgui.button("Restart")) { try self.restartAndShowCombatScreen(); } @@ -144,10 +140,7 @@ pub fn debug(self: *Game) !void { const screen = &self.combat_screen; - const time_left_til_pickup = screen.next_pickup_spawn_at - screen.wave_timer; - imgui.textFmt("Waves: {}\n", .{screen.waves.items.len}); imgui.textFmt("Bullets: {}\n", .{screen.bullets.items.len}); imgui.textFmt("Enemies: {}\n", .{screen.enemies.count}); - imgui.textFmt("Time until next pickup: {d:.2}s\n", .{@as(f32, @floatFromInt(time_left_til_pickup)) / std.time.ns_per_s}); }