add background

This commit is contained in:
Rokas Puzonas 2026-02-01 03:48:44 +02:00
parent beeacdc701
commit a4d803fc57
15 changed files with 345 additions and 184 deletions

View File

@ -136,6 +136,7 @@ pub const TileVariant = struct {
const encoded_tiles = try lexer.nextExpectText(); const encoded_tiles = try lexer.nextExpectText();
const tiles = try parseEncoding(encoding, scratch.allocator(), encoded_tiles); const tiles = try parseEncoding(encoding, scratch.allocator(), encoded_tiles);
try temp_tiles.appendSlice(scratch.allocator(), tiles.items); try temp_tiles.appendSlice(scratch.allocator(), tiles.items);
continue;
} else if (node.isTag("chunk")) { } else if (node.isTag("chunk")) {
const chunk = try initChunkDataFromXml(arena, scratch, lexer, encoding); const chunk = try initChunkDataFromXml(arena, scratch, lexer, encoding);
@ -158,7 +159,6 @@ pub const TileVariant = struct {
.fixed = try arena.dupe(u32, temp_tiles.items) .fixed = try arena.dupe(u32, temp_tiles.items)
}; };
} }
} }
fn parseEncoding( fn parseEncoding(

View File

@ -73,6 +73,9 @@ pub const TilesetReference = struct {
pub const Tile = struct { pub const Tile = struct {
tileset: *const Tileset, tileset: *const Tileset,
id: u32, id: u32,
flip_horizontal: bool,
flip_vertical: bool,
flip_diagonal: bool,
pub fn getProperties(self: Tile) Property.List { pub fn getProperties(self: Tile) Property.List {
return self.tileset.getTileProperties(self.id) orelse .empty; 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 { 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 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{ return Tile{
.tileset = tileset, .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
}; };
} }

View File

@ -24,7 +24,6 @@ pub const Tilemap = struct {
texture: Gfx.TextureId, texture: Gfx.TextureId,
tile_size: Engine.Vec2, tile_size: Engine.Vec2,
pub fn getTileUV(self: Tilemap, tile_x: f32, tile_y: f32) Rect { pub fn getTileUV(self: Tilemap, tile_x: f32, tile_y: f32) Rect {
const texture_info = Engine.Graphics.getTextureInfo(self.texture); const texture_info = Engine.Graphics.getTextureInfo(self.texture);
const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height); const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height);
@ -39,14 +38,17 @@ pub const Tilemap = struct {
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
font_id: FontName.EnumArray, 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, map: tiled.Tilemap,
tilesets: tiled.Tileset.List, 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 { pub fn init(gpa: std.mem.Allocator) !Assets {
var arena = std.heap.ArenaAllocator.init(gpa); 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")), .italic = try Gfx.addFont("italic", @embedFile("assets/roboto-font/Roboto-Italic.ttf")),
}); });
const wood01 = try Audio.load(.{ const pistol_mask_image = try STBImage.load(@embedFile("assets/pistol-mask.png"));
.format = .vorbis, defer pistol_mask_image.deinit();
.data = @embedFile("assets/wood01.ogg"), 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); var scratch = std.heap.ArenaAllocator.init(gpa);
defer scratch.deinit(); defer scratch.deinit();
@ -80,76 +154,42 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
gpa, gpa,
&scratch, &scratch,
&xml_buffers, &xml_buffers,
@embedFile("assets/tileset.tsx") @embedFile("assets/dungeon.tsx")
); );
var tilesets: tiled.Tileset.List = .empty; 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")); const dungeon_image = try STBImage.load(@embedFile("assets/tiny-dungeon/tilemap_packed.png"));
defer players_image.deinit(); defer dungeon_image.deinit();
const players_texture = try Gfx.addTexture(&.{ const dungeon_texture = try Gfx.addTexture(&.{
.{ .{
.width = players_image.width, .width = dungeon_image.width,
.height = players_image.height, .height = dungeon_image.height,
.rgba = players_image.rgba8_pixels .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{ return Assets{
.arena = arena, .arena = arena,
.font_id = font_id_array, .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, .map = map,
.tilesets = tilesets, .tilesets = tilesets,
.move_sound = move_sound, .dungeon_tilemap = .{
.terrain_tilemap = .{ .texture = dungeon_texture,
.texture = tileset_texture, .tile_size = .init(16, 16)
.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)
},
}; };
} }
pub fn deinit(self: *Assets, gpa: std.mem.Allocator) void { pub fn deinit(self: *Assets, gpa: std.mem.Allocator) void {
self.map.deinit();
self.tilesets.deinit(gpa); self.tilesets.deinit(gpa);
self.map.deinit();
self.arena.deinit(); self.arena.deinit();
} }

4
src/assets/dungeon.tsx Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.2" name="dungeon" tilewidth="16" tileheight="16" tilecount="132" columns="12">
<image source="tiny-dungeon/tilemap_packed.png" width="192" height="176"/>
</tileset>

BIN
src/assets/fire-spirit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

View File

@ -0,0 +1,14 @@
{
"automappingRulesFile": "",
"commands": [
],
"compatibilityVersion": 1100,
"extensionsPath": "extensions",
"folders": [
"."
],
"properties": [
],
"propertyTypes": [
]
}

23
src/assets/map.tmx Normal file
View File

@ -0,0 +1,23 @@
<?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="16" tileheight="16" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="dungeon.tsx"/>
<layer id="1" name="Tile Layer 1" width="20" height="15">
<data encoding="csv">
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
</data>
</layer>
</map>

BIN
src/assets/snake.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -10,6 +10,7 @@ const GenerationalArrayList = @import("./generational_array_list.zig").Generatio
const Engine = @import("./engine/root.zig"); const Engine = @import("./engine/root.zig");
const Nanoseconds = Engine.Nanoseconds; const Nanoseconds = Engine.Nanoseconds;
const Vec2 = Engine.Vec2; const Vec2 = Engine.Vec2;
const Sprite = Engine.Graphics.Sprite;
const Line = Engine.Math.Line; const Line = Engine.Math.Line;
const Rect = Engine.Math.Rect; const Rect = Engine.Math.Rect;
const Range = Engine.Math.Range; const Range = Engine.Math.Range;
@ -55,10 +56,11 @@ const Player = struct {
max_health: u32 = 0, max_health: u32 = 0,
last_shot_at: ?Nanoseconds = null, last_shot_at: ?Nanoseconds = null,
invincible_until: ?Nanoseconds = null, invincible_until: ?Nanoseconds = null,
gun: ?Gun = null, gun: Gun = .pistol,
pub fn getRect(self: Player) Rect { 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, kinetic: Kinetic,
speed: f32, speed: f32,
size: f32, size: f32,
sprite: Sprite,
target: ?Vec2 = null, target: ?Vec2 = null,
distance_to_target: ?f32 = 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 world_size = Vec2.init(20 * 16, 15 * 16);
const invincibility_duration_s = 0.5; const invincibility_duration_s = 0.5;
const pickup_spawn_duration_s = Range.init(1, 5);
const wave_infos = [_]Wave.Info{ const wave_infos = [_]Wave.Info{
.{ .{
.kind = .regular, .kind = .regular,
@ -196,24 +190,15 @@ bullets: std.ArrayList(Bullet) = .empty,
lasers: std.ArrayList(Laser) = .empty, lasers: std.ArrayList(Laser) = .empty,
bombs: std.ArrayList(Bomb) = .empty, bombs: std.ArrayList(Bomb) = .empty,
pickups: std.ArrayList(Pickup) = .empty,
next_pickup_spawn_at: Nanoseconds,
wave_timer: Nanoseconds = 0, wave_timer: Nanoseconds = 0,
waves: std.ArrayList(Wave) = .empty, waves: std.ArrayList(Wave) = .empty,
spawned_waves: std.ArrayList(usize) = .empty, spawned_waves: std.ArrayList(usize) = .empty,
pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScreen { 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{ return CombatScreen{
.gpa = gpa, .gpa = gpa,
.assets = assets, .assets = assets,
.next_pickup_spawn_at = next_pickup_spawn_at,
.player = .{ .player = .{
.kinetic = .{ .kinetic = .{
.pos = world_size.divideScalar(2) .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, .health = state.max_health,
.max_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.bullets.deinit(self.gpa);
self.enemies.deinit(self.gpa); self.enemies.deinit(self.gpa);
self.waves.deinit(self.gpa); self.waves.deinit(self.gpa);
self.pickups.deinit(self.gpa);
self.spawned_waves.deinit(self.gpa); self.spawned_waves.deinit(self.gpa);
self.lasers.deinit(self.gpa); self.lasers.deinit(self.gpa);
self.bombs.deinit(self.gpa); self.bombs.deinit(self.gpa);
@ -244,6 +228,7 @@ pub fn deinit(self: *CombatScreen) void {
const EnemyOptions = struct { const EnemyOptions = struct {
pos: ?Vec2 = null, pos: ?Vec2 = null,
sprite: Sprite,
size: f32 = 20 size: f32 = 20
}; };
@ -290,18 +275,8 @@ pub fn spawnEnemy(self: *CombatScreen, opts: EnemyOptions) !Enemy.Id {
return try self.enemies.insert(self.gpa, Enemy{ return try self.enemies.insert(self.gpa, Enemy{
.kinetic = .{ .pos = pos }, .kinetic = .{ .pos = pos },
.speed = 50, .speed = 50,
.size = opts.size .size = opts.size,
}); .sprite = opts.sprite
}
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
}); });
} }
@ -310,7 +285,49 @@ const TickResult = struct {
player_finished: bool 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 { pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResult {
const dt = frame.deltaTime(); const dt = frame.deltaTime();
var result = TickResult{ var result = TickResult{
@ -357,7 +374,9 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
while (wave.enemies_spawned < expected_enemies) { while (wave.enemies_spawned < expected_enemies) {
switch (wave.kind) { switch (wave.kind) {
.regular => { .regular => {
_ = try self.spawnEnemy(.{}); _ = try self.spawnEnemy(.{
.sprite = self.assets.skeleton
});
}, },
.snake => { .snake => {
var arena = std.heap.ArenaAllocator.init(self.gpa); 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 rand = self.rng.random();
const snake_length = rand.intRangeAtMost(u32, wave.min_group_size, wave.max_group_size); 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); try enemies.append(arena.allocator(), head_id);
const head = self.enemies.getAssumeExists(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| { for (0..snake_length) |i| {
const tail_pos = head_pos.sub(dir_to_center.multiplyScalar(@floatFromInt(i * gap))); const tail_pos = head_pos.sub(dir_to_center.multiplyScalar(@floatFromInt(i * gap)));
const tail_id = try self.spawnEnemy(.{ const tail_id = try self.spawnEnemy(.{
.sprite = self.assets.snake,
.pos = tail_pos .pos = tail_pos
}); });
try enemies.append(arena.allocator(), tail_id); 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(.{ const enemy_id = try self.spawnEnemy(.{
.pos = center.add(Vec2.initAngle(angle).multiplyScalar(distance)), .pos = center.add(Vec2.initAngle(angle).multiplyScalar(distance)),
.sprite = self.assets.fire_spirit,
.size = 10 .size = 10
}); });
try enemies.append(arena.allocator(), enemy_id); 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.graphics.canvas_size = world_size;
frame.drawRectangle(.{ self.drawMap(frame);
.rect = .init(0, 0, world_size.x, world_size.y),
.color = rgb(20, 20, 20)
});
var dir = Vec2.init(0, 0); var dir = Vec2.init(0, 0);
if (frame.isKeyDown(.W)) { if (frame.isKeyDown(.W)) {
@ -500,8 +513,7 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
if (frame.isMouseDown(.left) and cooldown_complete) { if (frame.isMouseDown(.left) and cooldown_complete) {
self.player.last_shot_at = frame.time_ns; self.player.last_shot_at = frame.time_ns;
if (self.player.gun) |gun| { switch (self.player.gun) {
switch (gun) {
.pistol => { .pistol => {
try self.bullets.append(self.gpa, .{ try self.bullets.append(self.gpa, .{
.kinetic = .{ .kinetic = .{
@ -534,11 +546,19 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
} }
} }
} }
}
frame.drawRectangle(.{ frame.drawRectangle(.{
.rect = self.player.getRect(), .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| { for (self.bullets.items) |*bullet| {
@ -775,40 +795,19 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
} }
enemy.kinetic.update(dt, .{}); enemy.kinetic.update(dt, .{});
frame.drawRectangle(.{ frame.drawRectangle(.{
.rect = enemy_rect, .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(.{ frame.drawRectangle(.{
.rect = pickup_rect, .rect = Rect.initCentered(pos.x, pos.y, sprite_size.x, sprite_size.y),
.color = rgb(20, 20, 200) .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, 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, 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); 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) { if (self.enemies.count == 0 and self.waves.items.len == 0 and self.spawned_waves.items.len == wave_infos.len) {

View File

@ -46,6 +46,9 @@ pub const Command = union(enum) {
sprite: ?Sprite = null, sprite: ?Sprite = null,
rotation: f32 = 0, rotation: f32 = 0,
origin: Vec2 = .init(0, 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 { pub const DrawCircle = struct {
@ -88,6 +91,12 @@ pub const TextureInfo = Texture.Info;
pub const Sprite = struct { pub const Sprite = struct {
texture: TextureId, texture: TextureId,
uv: Rect, 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; var gpa: std.mem.Allocator = undefined;
@ -333,7 +342,41 @@ fn drawRectangle(opts: Command.DrawRectangle) void {
if (opts.sprite) |sprite| { if (opts.sprite) |sprite| {
const uv = sprite.uv; 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); drawQuad(quad, opts.color, sprite.texture);
} else { } else {
const quad = .{ pos.add(top_left), pos.add(top_right), pos.add(bottom_right), pos.add(bottom_left) }; const quad = .{ pos.add(top_left), pos.add(top_right), pos.add(bottom_right), pos.add(bottom_left) };

View File

@ -368,6 +368,11 @@ pub const Rect = struct {
pos: Vec2, pos: Vec2,
size: Vec2, size: Vec2,
pub const unit = Rect{
.pos = .zero,
.size = .init(1, 1),
};
pub const zero = Rect{ pub const zero = Rect{
.pos = Vec2.zero, .pos = Vec2.zero,
.size = Vec2.zero .size = Vec2.zero

View File

@ -124,10 +124,6 @@ pub fn debug(self: *Game) !void {
} }
defer imgui.endWindow(); defer imgui.endWindow();
if (imgui.button("Spawn enemy")) {
_ = try self.combat_screen.spawnEnemy(.{});
}
if (imgui.button("Restart")) { if (imgui.button("Restart")) {
try self.restartAndShowCombatScreen(); try self.restartAndShowCombatScreen();
} }
@ -144,10 +140,7 @@ pub fn debug(self: *Game) !void {
const screen = &self.combat_screen; 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("Waves: {}\n", .{screen.waves.items.len});
imgui.textFmt("Bullets: {}\n", .{screen.bullets.items.len}); imgui.textFmt("Bullets: {}\n", .{screen.bullets.items.len});
imgui.textFmt("Enemies: {}\n", .{screen.enemies.count}); 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});
} }