add animations

This commit is contained in:
Rokas Puzonas 2026-02-01 05:19:00 +02:00
parent a249dc3cf9
commit 0b3f4486c0
4 changed files with 157 additions and 28 deletions

View File

@ -42,6 +42,7 @@ pistol_mask: Gfx.TextureId,
bomb_mask: Gfx.TextureId,
laser_mask: Gfx.TextureId,
bullet: Gfx.TextureId,
shadow: Gfx.TextureId,
dungeon_tilemap: Tilemap,
skeleton: Gfx.Sprite,
@ -176,6 +177,16 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
}
});
const shadow_image = try STBImage.load(@embedFile("assets/shadow.png"));
defer shadow_image.deinit();
const shadow_texture = try Gfx.addTexture(&.{
.{
.width = shadow_image.width,
.height = shadow_image.height,
.rgba = shadow_image.rgba8_pixels
}
});
return Assets{
.arena = arena,
.font_id = font_id_array,
@ -188,6 +199,7 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
.map = map,
.tilesets = tilesets,
.bullet = bullet_texture,
.shadow = shadow_texture,
.dungeon_tilemap = .{
.texture = dungeon_texture,
.tile_size = .init(16, 16)

BIN
src/assets/shadow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

@ -58,6 +58,7 @@ const Player = struct {
invincible_until: ?Nanoseconds = null,
gun: Gun = .pistol,
facing_left: bool = false,
started_walk_at: ?Nanoseconds = null,
pub fn getRect(self: Player) Rect {
const pos = self.kinetic.pos;
@ -95,6 +96,9 @@ const Enemy = struct {
size: f32,
sprite: Sprite,
facing_left: bool = false,
created_at: Nanoseconds,
animation_offset: f32,
animation_kind: AnimationKind,
target: ?Vec2 = null,
distance_to_target: ?f32 = null,
@ -104,6 +108,12 @@ const Enemy = struct {
dead: bool = false,
const AnimationKind = enum {
walk,
float,
slide
};
const List = GenerationalArrayList(Enemy);
const Id = List.Id;
};
@ -197,6 +207,8 @@ wave_timer: Nanoseconds = 0,
waves: std.ArrayList(Wave) = .empty,
spawned_waves: std.ArrayList(usize) = .empty,
show_colliders: bool = false,
pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScreen {
return CombatScreen{
.gpa = gpa,
@ -230,8 +242,10 @@ pub fn deinit(self: *CombatScreen) void {
const EnemyOptions = struct {
pos: ?Vec2 = null,
time_ns: Nanoseconds,
sprite: Sprite,
size: f32 = 20
size: f32 = 20,
animation_kind: Enemy.AnimationKind,
};
fn pickSpawnLocation(rand: std.Random, margin: f32, size: f32) Vec2 {
@ -278,7 +292,10 @@ pub fn spawnEnemy(self: *CombatScreen, opts: EnemyOptions) !Enemy.Id {
.kinetic = .{ .pos = pos },
.speed = 50,
.size = opts.size,
.sprite = opts.sprite
.sprite = opts.sprite,
.created_at = opts.time_ns,
.animation_offset = self.rng.random().float(f32),
.animation_kind = opts.animation_kind
});
}
@ -376,7 +393,9 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
switch (wave.kind) {
.regular => {
_ = try self.spawnEnemy(.{
.sprite = self.assets.skeleton
.sprite = self.assets.skeleton,
.time_ns = frame.time_ns,
.animation_kind = .walk
});
},
.snake => {
@ -388,7 +407,9 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
const snake_length = rand.intRangeAtMost(u32, wave.min_group_size, wave.max_group_size);
const head_id = try self.spawnEnemy(.{
.sprite = self.assets.snake
.sprite = self.assets.snake,
.time_ns = frame.time_ns,
.animation_kind = .slide
});
try enemies.append(arena.allocator(), head_id);
@ -401,8 +422,10 @@ 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(.{
.time_ns = frame.time_ns,
.sprite = self.assets.snake,
.pos = tail_pos
.pos = tail_pos,
.animation_kind = .slide
});
try enemies.append(arena.allocator(), tail_id);
}
@ -429,9 +452,11 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
const distance = min_cluster_radius + rand.float(f32) * (max_cluster_radius - min_cluster_radius);
const enemy_id = try self.spawnEnemy(.{
.time_ns = frame.time_ns,
.pos = center.add(Vec2.initAngle(angle).multiplyScalar(distance)),
.sprite = self.assets.fire_spirit,
.size = 10
.size = 10,
.animation_kind = .float
});
try enemies.append(arena.allocator(), enemy_id);
}
@ -462,6 +487,38 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
self.drawMap(frame);
var shadows: std.ArrayList(Vec2) = .empty;
try shadows.append(
frame.arena.allocator(),
self.player.kinetic.pos.add(.{ .x = 0, .y = 4 })
);
{
var enemies_iter = self.enemies.iterator();
while (enemies_iter.nextItem()) |enemy| {
try shadows.append(frame.arena.allocator(), enemy.kinetic.pos);
}
}
for (shadows.items) |shadow_caster| {
const shadow_size = Engine.Graphics.getTextureSize(self.assets.shadow);
frame.drawRectangle(.{
.rect = .{
.pos = shadow_caster.add(.{
.x = -shadow_size.x/2,
.y = shadow_size.y/2,
}),
.size = shadow_size,
},
.color = rgba(255, 255, 255, 0.5),
.sprite = .{
.texture = self.assets.shadow,
.uv = .unit
}
});
}
var dir = Vec2.init(0, 0);
if (frame.isKeyDown(.W)) {
dir.y -= 1;
@ -479,6 +536,11 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
if (dir.x != 0) {
self.player.facing_left = dir.x < 0;
if (self.player.started_walk_at == null) {
self.player.started_walk_at = frame.time_ns;
}
} else {
self.player.started_walk_at = null;
}
if (frame.isKeyPressed(._1)) {
@ -496,17 +558,19 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
.friction = 0.99988,
.max_speed = 400
});
const player_size = self.player.getRect().size;
self.player.kinetic.pos.x = std.math.clamp(
self.player.kinetic.pos.x,
player_size.x/2,
world_size.x - player_size.x/2
);
self.player.kinetic.pos.y = std.math.clamp(
self.player.kinetic.pos.y,
player_size.y/2,
world_size.y - player_size.y/2
);
if (frame.input.mouse_position) |mouse| {
const cursor_size = Vec2.init(10, 10);
frame.drawRectangle(.{
.rect = .{
.pos = mouse.sub(cursor_size.divideScalar(2)),
.size = cursor_size,
},
.color = rgba(255, 200, 200, 0.5)
});
const bullet_dir = mouse.sub(self.player.kinetic.pos).normalized();
var cooldown_complete = true;
@ -543,17 +607,23 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
try self.lasers.append(self.gpa, .{
.origin = self.player.kinetic.pos,
.dir = bullet_dir,
.size = 10,
.size = 3,
.created_at = frame.time_ns,
.duration = std.time.ns_per_s
.duration = std.time.ns_per_ms * 10
});
}
}
}
}
{
var walk_animation: f32 = 0;
if (self.player.started_walk_at) |started_walk_at| {
const walk_duration = frame.time_ns - started_walk_at;
const duration_ns = std.time.ns_per_ms * 250;
walk_animation = @as(f32, @floatFromInt(@mod(walk_duration, duration_ns))) / duration_ns;
}
var sprite = Engine.Graphics.Sprite{
.texture = switch (self.player.gun) {
.pistol => self.assets.pistol_mask,
@ -569,15 +639,24 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
}
const pos = self.player.kinetic.pos;
frame.drawRectangle(.{
.rect = Rect.initCentered(pos.x, pos.y, sprite_size.x, sprite_size.y),
.rect = Rect.initCentered(
pos.x,
pos.y - @sin(walk_animation * std.math.pi) * 4,
sprite_size.x,
sprite_size.y
),
.rotation = @sin((walk_animation-0.5)*2 * std.math.pi)*0.1,
.origin = .init(sprite_size.x/2, sprite_size.y),
.color = rgb(255, 255, 255),
.sprite = sprite
});
frame.drawRectangle(.{
.rect = self.player.getRect(),
.color = rgba(255, 255, 255, 0.5),
});
if (self.show_colliders) {
frame.drawRectangle(.{
.rect = self.player.getRect(),
.color = rgba(255, 255, 255, 0.5),
});
}
}
for (self.bullets.items) |*bullet| {
@ -833,16 +912,50 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
if (enemy.facing_left) {
sprite = sprite.flipHorizontal();
}
var enemy_sprite_rect = Rect.initCentered(
pos.x,
pos.y,
sprite_size.x,
sprite_size.y
);
var rotation: f32 = 0;
const walk_duration = frame.time_ns - enemy.created_at;
if (enemy.animation_kind == .walk) {
const duration_ns = std.time.ns_per_ms * 250;
var walk_animation = @as(f32, @floatFromInt(@mod(walk_duration, duration_ns))) / duration_ns;
walk_animation = @mod(walk_animation + enemy.animation_offset, 1);
enemy_sprite_rect.pos.y -= @sin(walk_animation * std.math.pi) * 2;
rotation = @sin((walk_animation-0.5)*2 * std.math.pi)*0.1;
} else if (enemy.animation_kind == .float) {
const duration_ns = std.time.ns_per_s;
var walk_animation = @as(f32, @floatFromInt(@mod(walk_duration, duration_ns))) / duration_ns;
walk_animation = @mod(walk_animation + enemy.animation_offset, 1);
enemy_sprite_rect.pos.y += @sin((walk_animation-0.5)*2 * std.math.pi) * 2;
} else if (enemy.animation_kind == .slide) {
const duration_ns = std.time.ns_per_s;
var walk_animation = @as(f32, @floatFromInt(@mod(walk_duration, duration_ns))) / duration_ns;
walk_animation = @mod(walk_animation + enemy.animation_offset, 1);
enemy_sprite_rect.pos.x += @sin((walk_animation-0.5)*2 * std.math.pi) * 2;
}
frame.drawRectangle(.{
.rect = Rect.initCentered(pos.x, pos.y, sprite_size.x, sprite_size.y),
.rect = enemy_sprite_rect,
.rotation = rotation,
.origin = .init(sprite_size.x/2, sprite_size.y),
.color = rgb(255, 255, 255),
.sprite = sprite
});
frame.drawRectangle(.{
.rect = enemy_rect,
.color = rgba(20, 20, 200, 0.2),
});
if (self.show_colliders) {
frame.drawRectangle(.{
.rect = enemy_rect,
.color = rgba(20, 20, 200, 0.2),
});
}
}
}

View File

@ -128,6 +128,10 @@ pub fn debug(self: *Game) !void {
try self.restartAndShowCombatScreen();
}
if (imgui.button("Toggle colliders")) {
self.combat_screen.show_colliders = !self.combat_screen.show_colliders;
}
if (self.show_shop) {
if (imgui.button("Swap to combat")) {
self.show_shop = false;