add animations
This commit is contained in:
parent
a249dc3cf9
commit
0b3f4486c0
@ -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
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,
|
||||
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,16 +639,25 @@ 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
|
||||
});
|
||||
|
||||
if (self.show_colliders) {
|
||||
frame.drawRectangle(.{
|
||||
.rect = self.player.getRect(),
|
||||
.color = rgba(255, 255, 255, 0.5),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (self.bullets.items) |*bullet| {
|
||||
bullet.kinetic.vel = bullet.dir.multiplyScalar(bullet.speed);
|
||||
@ -833,18 +912,52 @@ 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
|
||||
});
|
||||
|
||||
if (self.show_colliders) {
|
||||
frame.drawRectangle(.{
|
||||
.rect = enemy_rect,
|
||||
.color = rgba(20, 20, 200, 0.2),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var i: usize = 0;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user