add animations
This commit is contained in:
parent
a249dc3cf9
commit
0b3f4486c0
@ -42,6 +42,7 @@ pistol_mask: Gfx.TextureId,
|
|||||||
bomb_mask: Gfx.TextureId,
|
bomb_mask: Gfx.TextureId,
|
||||||
laser_mask: Gfx.TextureId,
|
laser_mask: Gfx.TextureId,
|
||||||
bullet: Gfx.TextureId,
|
bullet: Gfx.TextureId,
|
||||||
|
shadow: Gfx.TextureId,
|
||||||
dungeon_tilemap: Tilemap,
|
dungeon_tilemap: Tilemap,
|
||||||
|
|
||||||
skeleton: Gfx.Sprite,
|
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{
|
return Assets{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.font_id = font_id_array,
|
.font_id = font_id_array,
|
||||||
@ -188,6 +199,7 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
|
|||||||
.map = map,
|
.map = map,
|
||||||
.tilesets = tilesets,
|
.tilesets = tilesets,
|
||||||
.bullet = bullet_texture,
|
.bullet = bullet_texture,
|
||||||
|
.shadow = shadow_texture,
|
||||||
.dungeon_tilemap = .{
|
.dungeon_tilemap = .{
|
||||||
.texture = dungeon_texture,
|
.texture = dungeon_texture,
|
||||||
.tile_size = .init(16, 16)
|
.tile_size = .init(16, 16)
|
||||||
|
|||||||
BIN
src/assets/shadow.png
Normal file
BIN
src/assets/shadow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 234 B |
@ -58,6 +58,7 @@ const Player = struct {
|
|||||||
invincible_until: ?Nanoseconds = null,
|
invincible_until: ?Nanoseconds = null,
|
||||||
gun: Gun = .pistol,
|
gun: Gun = .pistol,
|
||||||
facing_left: bool = false,
|
facing_left: bool = false,
|
||||||
|
started_walk_at: ?Nanoseconds = null,
|
||||||
|
|
||||||
pub fn getRect(self: Player) Rect {
|
pub fn getRect(self: Player) Rect {
|
||||||
const pos = self.kinetic.pos;
|
const pos = self.kinetic.pos;
|
||||||
@ -95,6 +96,9 @@ const Enemy = struct {
|
|||||||
size: f32,
|
size: f32,
|
||||||
sprite: Sprite,
|
sprite: Sprite,
|
||||||
facing_left: bool = false,
|
facing_left: bool = false,
|
||||||
|
created_at: Nanoseconds,
|
||||||
|
animation_offset: f32,
|
||||||
|
animation_kind: AnimationKind,
|
||||||
|
|
||||||
target: ?Vec2 = null,
|
target: ?Vec2 = null,
|
||||||
distance_to_target: ?f32 = null,
|
distance_to_target: ?f32 = null,
|
||||||
@ -104,6 +108,12 @@ const Enemy = struct {
|
|||||||
|
|
||||||
dead: bool = false,
|
dead: bool = false,
|
||||||
|
|
||||||
|
const AnimationKind = enum {
|
||||||
|
walk,
|
||||||
|
float,
|
||||||
|
slide
|
||||||
|
};
|
||||||
|
|
||||||
const List = GenerationalArrayList(Enemy);
|
const List = GenerationalArrayList(Enemy);
|
||||||
const Id = List.Id;
|
const Id = List.Id;
|
||||||
};
|
};
|
||||||
@ -197,6 +207,8 @@ 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,
|
||||||
|
|
||||||
|
show_colliders: bool = false,
|
||||||
|
|
||||||
pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScreen {
|
pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScreen {
|
||||||
return CombatScreen{
|
return CombatScreen{
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
@ -230,8 +242,10 @@ pub fn deinit(self: *CombatScreen) void {
|
|||||||
|
|
||||||
const EnemyOptions = struct {
|
const EnemyOptions = struct {
|
||||||
pos: ?Vec2 = null,
|
pos: ?Vec2 = null,
|
||||||
|
time_ns: Nanoseconds,
|
||||||
sprite: Sprite,
|
sprite: Sprite,
|
||||||
size: f32 = 20
|
size: f32 = 20,
|
||||||
|
animation_kind: Enemy.AnimationKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn pickSpawnLocation(rand: std.Random, margin: f32, size: f32) Vec2 {
|
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 },
|
.kinetic = .{ .pos = pos },
|
||||||
.speed = 50,
|
.speed = 50,
|
||||||
.size = opts.size,
|
.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) {
|
switch (wave.kind) {
|
||||||
.regular => {
|
.regular => {
|
||||||
_ = try self.spawnEnemy(.{
|
_ = try self.spawnEnemy(.{
|
||||||
.sprite = self.assets.skeleton
|
.sprite = self.assets.skeleton,
|
||||||
|
.time_ns = frame.time_ns,
|
||||||
|
.animation_kind = .walk
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
.snake => {
|
.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 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
|
.sprite = self.assets.snake,
|
||||||
|
.time_ns = frame.time_ns,
|
||||||
|
.animation_kind = .slide
|
||||||
});
|
});
|
||||||
try enemies.append(arena.allocator(), head_id);
|
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| {
|
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(.{
|
||||||
|
.time_ns = frame.time_ns,
|
||||||
.sprite = self.assets.snake,
|
.sprite = self.assets.snake,
|
||||||
.pos = tail_pos
|
.pos = tail_pos,
|
||||||
|
.animation_kind = .slide
|
||||||
});
|
});
|
||||||
try enemies.append(arena.allocator(), tail_id);
|
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 distance = min_cluster_radius + rand.float(f32) * (max_cluster_radius - min_cluster_radius);
|
||||||
|
|
||||||
const enemy_id = try self.spawnEnemy(.{
|
const enemy_id = try self.spawnEnemy(.{
|
||||||
|
.time_ns = frame.time_ns,
|
||||||
.pos = center.add(Vec2.initAngle(angle).multiplyScalar(distance)),
|
.pos = center.add(Vec2.initAngle(angle).multiplyScalar(distance)),
|
||||||
.sprite = self.assets.fire_spirit,
|
.sprite = self.assets.fire_spirit,
|
||||||
.size = 10
|
.size = 10,
|
||||||
|
.animation_kind = .float
|
||||||
});
|
});
|
||||||
try enemies.append(arena.allocator(), enemy_id);
|
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);
|
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);
|
var dir = Vec2.init(0, 0);
|
||||||
if (frame.isKeyDown(.W)) {
|
if (frame.isKeyDown(.W)) {
|
||||||
dir.y -= 1;
|
dir.y -= 1;
|
||||||
@ -479,6 +536,11 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
|
|
||||||
if (dir.x != 0) {
|
if (dir.x != 0) {
|
||||||
self.player.facing_left = 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)) {
|
if (frame.isKeyPressed(._1)) {
|
||||||
@ -496,17 +558,19 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
.friction = 0.99988,
|
.friction = 0.99988,
|
||||||
.max_speed = 400
|
.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| {
|
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();
|
const bullet_dir = mouse.sub(self.player.kinetic.pos).normalized();
|
||||||
|
|
||||||
var cooldown_complete = true;
|
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, .{
|
try self.lasers.append(self.gpa, .{
|
||||||
.origin = self.player.kinetic.pos,
|
.origin = self.player.kinetic.pos,
|
||||||
.dir = bullet_dir,
|
.dir = bullet_dir,
|
||||||
.size = 10,
|
.size = 3,
|
||||||
.created_at = frame.time_ns,
|
.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{
|
var sprite = Engine.Graphics.Sprite{
|
||||||
.texture = switch (self.player.gun) {
|
.texture = switch (self.player.gun) {
|
||||||
.pistol => self.assets.pistol_mask,
|
.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;
|
const pos = self.player.kinetic.pos;
|
||||||
frame.drawRectangle(.{
|
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),
|
.color = rgb(255, 255, 255),
|
||||||
.sprite = sprite
|
.sprite = sprite
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.drawRectangle(.{
|
if (self.show_colliders) {
|
||||||
.rect = self.player.getRect(),
|
frame.drawRectangle(.{
|
||||||
.color = rgba(255, 255, 255, 0.5),
|
.rect = self.player.getRect(),
|
||||||
});
|
.color = rgba(255, 255, 255, 0.5),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self.bullets.items) |*bullet| {
|
for (self.bullets.items) |*bullet| {
|
||||||
@ -833,16 +912,50 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
if (enemy.facing_left) {
|
if (enemy.facing_left) {
|
||||||
sprite = sprite.flipHorizontal();
|
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(.{
|
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),
|
.color = rgb(255, 255, 255),
|
||||||
.sprite = sprite
|
.sprite = sprite
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.drawRectangle(.{
|
if (self.show_colliders) {
|
||||||
.rect = enemy_rect,
|
frame.drawRectangle(.{
|
||||||
.color = rgba(20, 20, 200, 0.2),
|
.rect = enemy_rect,
|
||||||
});
|
.color = rgba(20, 20, 200, 0.2),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -128,6 +128,10 @@ pub fn debug(self: *Game) !void {
|
|||||||
try self.restartAndShowCombatScreen();
|
try self.restartAndShowCombatScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (imgui.button("Toggle colliders")) {
|
||||||
|
self.combat_screen.show_colliders = !self.combat_screen.show_colliders;
|
||||||
|
}
|
||||||
|
|
||||||
if (self.show_shop) {
|
if (self.show_shop) {
|
||||||
if (imgui.button("Swap to combat")) {
|
if (imgui.button("Swap to combat")) {
|
||||||
self.show_shop = false;
|
self.show_shop = false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user