add animations
This commit is contained in:
parent
9332649a63
commit
17acc49282
@ -7,7 +7,10 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
const assets_dir = "assets";
|
||||
const asset_files = [_][]const u8{
|
||||
"tranquil_tunnels_transparent.png"
|
||||
"tranquil_tunnels_transparent.png",
|
||||
"Prototype_Character/Default/idle.png",
|
||||
"Prototype_Character/Default/walk.png",
|
||||
"Prototype_Character/Default/death.png"
|
||||
// "sokoban/sokoban_tilesheet.png",
|
||||
// "roboto-font/Roboto-Regular.ttf",
|
||||
// "tiled/map.tmx",
|
||||
|
||||
237
src/app.zig
237
src/app.zig
@ -15,9 +15,24 @@ const Gfx = Platform.Gfx;
|
||||
const ImGUI = Platform.ImGUI;
|
||||
const Rect = Platform.Rect;
|
||||
|
||||
const SlotMapType = @import("./slot_map.zig").SlotMapType;
|
||||
|
||||
const App = @This();
|
||||
|
||||
tilesheet: Tilesheet,
|
||||
const Prng = std.Random.DefaultPrng;
|
||||
|
||||
gpa: Allocator,
|
||||
camera_pos: Vec2 = .init(0, 0),
|
||||
prng: Prng,
|
||||
|
||||
minions: MinionSlotMap,
|
||||
|
||||
idle_animations: AnimationDirections,
|
||||
walk_animations: AnimationDirections,
|
||||
death_animations: AnimationDirections,
|
||||
|
||||
const MinionSlotMap = SlotMapType(u16, u16, Minion);
|
||||
const MinionId = MinionSlotMap.Id;
|
||||
|
||||
const Tilesheet = struct {
|
||||
image: Gfx.ImageData,
|
||||
@ -66,50 +81,226 @@ const Tilesheet = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const Animation = struct {
|
||||
frames: []Gfx.Sprite.Id,
|
||||
frame_duration: Platform.Nanoseconds,
|
||||
|
||||
pub fn init(gpa: Allocator, tilesheet: Tilesheet, frame_duration: Platform.Nanoseconds, tiles: []const Vec2) !Animation {
|
||||
const frames = try gpa.alloc(Gfx.Sprite.Id, tiles.len);
|
||||
for (0.., tiles) |i, tile| {
|
||||
frames[i] = tilesheet.get(@intFromFloat(tile.x), @intFromFloat(tile.y));
|
||||
}
|
||||
|
||||
return Animation{
|
||||
.frames = frames,
|
||||
.frame_duration = frame_duration
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const AnimationDirections = struct {
|
||||
down: Animation,
|
||||
right: Animation,
|
||||
up: Animation,
|
||||
|
||||
pub fn init(gpa: Allocator, sheet: Tilesheet, frame_duration: Platform.Nanoseconds, frames_count: u32) !AnimationDirections {
|
||||
var tiles_buffer: [16]Vec2 = undefined;
|
||||
for (0..frames_count) |i| {
|
||||
tiles_buffer[i] = .initFromInt(usize, i, 0);
|
||||
}
|
||||
const down = try Animation.init(gpa, sheet, frame_duration, tiles_buffer[0..frames_count]);
|
||||
|
||||
for (0..frames_count) |i| {
|
||||
tiles_buffer[i] = .initFromInt(usize, i, 1);
|
||||
}
|
||||
const right = try Animation.init(gpa, sheet, frame_duration, tiles_buffer[0..frames_count]);
|
||||
|
||||
for (0..frames_count) |i| {
|
||||
tiles_buffer[i] = .initFromInt(usize, i, 2);
|
||||
}
|
||||
const up = try Animation.init(gpa, sheet, frame_duration, tiles_buffer[0..frames_count]);
|
||||
|
||||
return AnimationDirections{
|
||||
.down = down,
|
||||
.right = right,
|
||||
.up = up,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const AnimationName = enum {
|
||||
idle,
|
||||
walk,
|
||||
death,
|
||||
};
|
||||
|
||||
const AnimationDirection = enum {
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down
|
||||
};
|
||||
|
||||
const Minion = struct {
|
||||
pos: Vec2,
|
||||
animation: AnimationName,
|
||||
animation_dir: AnimationDirection,
|
||||
animation_index: u32,
|
||||
animation_timer: Platform.Nanoseconds,
|
||||
};
|
||||
|
||||
fn initImageDataFromSTB(img: STBImage) Gfx.ImageData {
|
||||
return Gfx.ImageData{
|
||||
.width = img.width,
|
||||
.height = img.height,
|
||||
.pixels = .{ .rgba8 = img.rgba8_pixels }
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
// const robot_font = Gfx.initFont();
|
||||
// Gfx.setFont(robot_font, plt.assets.readFile("roboto-font/Roboto-Regular.ttf"));
|
||||
|
||||
const tilesheet_png = try STBImage.load(plt.assets.readFile("tranquil_tunnels_transparent.png"));
|
||||
const tilesheet = try Tilesheet.init(
|
||||
const idle_sheet = try Tilesheet.init(
|
||||
plt.arena,
|
||||
Gfx.ImageData{
|
||||
.width = tilesheet_png.width,
|
||||
.height = tilesheet_png.height,
|
||||
.pixels = .{ .rgba8 = tilesheet_png.rgba8_pixels }
|
||||
},
|
||||
13,
|
||||
8,
|
||||
.init(64, 64)
|
||||
initImageDataFromSTB(try STBImage.load(plt.assets.readFile("Prototype_Character/Default/idle.png"))),
|
||||
2,
|
||||
3,
|
||||
.init(32, 32)
|
||||
);
|
||||
|
||||
const walk_sheet = try Tilesheet.init(
|
||||
plt.arena,
|
||||
initImageDataFromSTB(try STBImage.load(plt.assets.readFile("Prototype_Character/Default/walk.png"))),
|
||||
4,
|
||||
3,
|
||||
.init(32, 32)
|
||||
);
|
||||
|
||||
const death_sheet = try Tilesheet.init(
|
||||
plt.arena,
|
||||
initImageDataFromSTB(try STBImage.load(plt.assets.readFile("Prototype_Character/Default/death.png"))),
|
||||
3,
|
||||
3,
|
||||
.init(32, 32)
|
||||
);
|
||||
|
||||
const ns_per_s = std.time.ns_per_s;
|
||||
const idle_fps = 5;
|
||||
const walk_fps = 5;
|
||||
const death_fps = 3;
|
||||
|
||||
const now = std.Io.Timestamp.now(plt.io, .awake);
|
||||
|
||||
self.* = App{
|
||||
.tilesheet = tilesheet
|
||||
.gpa = plt.gpa,
|
||||
.idle_animations = try AnimationDirections.init(plt.arena, idle_sheet, ns_per_s / idle_fps, 2),
|
||||
.walk_animations = try AnimationDirections.init(plt.arena, walk_sheet, ns_per_s / walk_fps, 4),
|
||||
.death_animations = try AnimationDirections.init(plt.arena, death_sheet, ns_per_s / death_fps, 3),
|
||||
.minions = .init(.empty),
|
||||
.prng = Prng.init(@truncate(@as(u96, @intCast(now.nanoseconds))))
|
||||
};
|
||||
|
||||
try self.spawnMinion(.init(0, 0));
|
||||
try self.spawnMinion(.init(32, 0));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn spawnMinion(self: *App, pos: Vec2) !void {
|
||||
const minion_id = try self.minions.insert(self.gpa);
|
||||
const minion = self.minions.getAssumeExists(minion_id);
|
||||
minion.* = Minion{
|
||||
.pos = pos,
|
||||
.animation = .death,
|
||||
.animation_dir = .right,
|
||||
.animation_index = 0,
|
||||
.animation_timer = 0
|
||||
};
|
||||
}
|
||||
|
||||
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||
_ = self; // autofix
|
||||
const input = plt.input;
|
||||
_ = input; // autofix
|
||||
|
||||
const dt = plt.deltaTime();
|
||||
_ = dt; // autofix
|
||||
|
||||
if (plt.input.isKeyDown(.D)) {
|
||||
self.camera_pos.x += 50 * dt;
|
||||
}
|
||||
if (plt.input.isKeyDown(.A)) {
|
||||
self.camera_pos.x -= 50 * dt;
|
||||
}
|
||||
|
||||
if (plt.input.isMouseDown(.left)) {
|
||||
self.camera_pos = self.camera_pos.sub(plt.input.mouse_delta);
|
||||
}
|
||||
|
||||
Gfx.viewTranslate(plt.input.window_size.x/2, plt.input.window_size.y/2);
|
||||
Gfx.viewTranslate(-self.camera_pos.x, -self.camera_pos.y);
|
||||
|
||||
Gfx.setClearColor(.rgb(40, 40, 40));
|
||||
Gfx.drawLine(.init(0, 200), .init(300, 200), .white, 5);
|
||||
// Gfx.drawSprite(frames[self.player_frame_index], self.player_pos, .init(64, 64), .white);
|
||||
// Gfx.drawSprite(self.tilesheet.get(11, 6), self.player_pos.add(.init(0, 200)), .init(64, 64), .white);
|
||||
// Gfx.drawText(self.roboto_font, .{
|
||||
// .pos = .init(300, 100),
|
||||
// .text = "Hello, World!",
|
||||
// .height = 64,
|
||||
// });
|
||||
Gfx.drawLine(.init(-800, 0), .init(800, 0), .white, 5);
|
||||
|
||||
const character_size = Vec2.init(64, 64);
|
||||
|
||||
var minion_iter = self.minions.iterator();
|
||||
while (minion_iter.next()) |minion_id| {
|
||||
const minion = self.minions.getAssumeExists(minion_id);
|
||||
|
||||
const animations = switch (minion.animation) {
|
||||
.idle => self.idle_animations,
|
||||
.walk => self.walk_animations,
|
||||
.death => self.death_animations
|
||||
};
|
||||
var flip_animation = false;
|
||||
const animation = switch (minion.animation_dir) {
|
||||
.right => animations.right,
|
||||
.up => animations.up,
|
||||
.down => animations.down,
|
||||
.left => blk: {
|
||||
flip_animation = true;
|
||||
break :blk animations.right;
|
||||
}
|
||||
};
|
||||
|
||||
minion.animation_timer += plt.dt;
|
||||
while (minion.animation_timer >= animation.frame_duration) {
|
||||
minion.animation_timer -= animation.frame_duration;
|
||||
minion.animation_index += 1;
|
||||
}
|
||||
|
||||
minion.animation_index = minion.animation_index % @as(u32, @intCast(animation.frames.len));
|
||||
const sprite = animation.frames[minion.animation_index];
|
||||
|
||||
var size = character_size;
|
||||
var pos = minion.pos;
|
||||
pos.y -= character_size.y*0.7;
|
||||
if (flip_animation) {
|
||||
size.x *= -1;
|
||||
pos.x += character_size.x/2;
|
||||
} else {
|
||||
pos.x -= character_size.x/2;
|
||||
}
|
||||
|
||||
Gfx.drawRectangle(
|
||||
minion.pos.sub(.init(5, 5)),
|
||||
.init(10, 10),
|
||||
.white
|
||||
);
|
||||
Gfx.drawSprite(sprite, pos, size, .white);
|
||||
}
|
||||
|
||||
if (ImGUI.beginWindow(.{ .name = "Debug", .size = .init(200, 100) })) {
|
||||
defer ImGUI.endWindow();
|
||||
|
||||
if (ImGUI.button("spawn")) {
|
||||
try self.spawnMinion(.init(32, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
||||
self.minions.deinit(self.gpa);
|
||||
_ = plt; // autofix
|
||||
_ = self; // autofix
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ const State = struct {
|
||||
|
||||
frame_index: u64,
|
||||
|
||||
view: Mat4,
|
||||
shader: sg.Shader,
|
||||
pipeline: sg.Pipeline,
|
||||
bindings: sg.Bindings,
|
||||
@ -150,6 +151,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
self.* = State{
|
||||
.gpa = gpa,
|
||||
.frame_index = 0,
|
||||
.view = .initIdentity(),
|
||||
.shader = shader,
|
||||
.pipeline = pipeline,
|
||||
.bindings = bindings,
|
||||
@ -163,7 +165,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
|
||||
.linear_sampler = linear_sampler,
|
||||
.nearest_sampler = nearest_sampler,
|
||||
.default_sampler = .linear,
|
||||
.default_sampler = .nearest,
|
||||
|
||||
.textures_buffer = undefined,
|
||||
.textures = .init(.initBuffer(&self.textures_buffer)),
|
||||
@ -238,7 +240,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
var pixels: [width * height * 4]u8 = undefined;
|
||||
@memset(&pixels, 0xFF);
|
||||
|
||||
self.default_sprite = initSprite(.{ .padding = 1 });
|
||||
self.default_sprite = initSprite(.{ });
|
||||
setSprite(self.default_sprite, .{
|
||||
.image = ImageData{
|
||||
.width = width,
|
||||
@ -272,7 +274,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
}
|
||||
}
|
||||
|
||||
self.error_sprite = initSprite(.{ .padding = 1 });
|
||||
self.error_sprite = initSprite(.{ });
|
||||
setSprite(self.error_sprite, .{
|
||||
.image = .{
|
||||
.width = width,
|
||||
@ -328,6 +330,20 @@ fn toSokolColor(color: Color) sokol.gfx.Color {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn viewTranslate(x: f32, y: f32) void {
|
||||
flush(.{});
|
||||
|
||||
const self = &g_state;
|
||||
self.view.translate(x, y, 0);
|
||||
}
|
||||
|
||||
pub fn viewScale(x: f32, y: f32) void {
|
||||
flush(.{});
|
||||
|
||||
const self = &g_state;
|
||||
self.view.scale(x, y, 1);
|
||||
}
|
||||
|
||||
pub fn beginFrame() void {
|
||||
const self = &g_state;
|
||||
|
||||
@ -342,6 +358,8 @@ pub fn beginFrame() void {
|
||||
.swapchain = sglue.swapchain()
|
||||
});
|
||||
|
||||
self.view = .initIdentity();
|
||||
|
||||
const default_spritesheet = self.spritesheets.getAssumeExists(self.default_spritesheet);
|
||||
const spritesheet_texture = self.textures.getAssumeExists(default_spritesheet.texture);
|
||||
self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view;
|
||||
@ -430,7 +448,7 @@ pub fn flush(opts: FlushOptions) void {
|
||||
var vs_params: shd.VsParams = .{
|
||||
// TODO: Maybe 'view' matrix is not needed.
|
||||
// We probably should only have a combined 'mvp' matrix
|
||||
.view = .initIdentity(),
|
||||
.view = self.view,
|
||||
.projection = createProjectionMatrix(window_size)
|
||||
};
|
||||
|
||||
@ -1070,8 +1088,16 @@ fn drawSpriteQuad(id: Sprite.Id, points: [4]Vec2, color: Color) void {
|
||||
if (getSpriteUVRect(id)) |sprite_uv| {
|
||||
const sprite = self.sprites.getAssumeExists(id);
|
||||
const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet);
|
||||
const texel_size = spritesheet.size;
|
||||
const texel_size = Vec2.init(1, 1).divide(spritesheet.size);
|
||||
_ = texel_size; // autofix
|
||||
|
||||
// TODO: I hope this doesn't bite me in the ass.
|
||||
// The shrink of the UV by half a texel
|
||||
// This is to avoid linear sampling artifacts at edges
|
||||
//
|
||||
// Yes I did regret this. Fuck.
|
||||
//
|
||||
// quad.setUVRect(sprite_uv.shrink(texel_size.divideScalar(2)));
|
||||
quad.setUVRect(sprite_uv);
|
||||
texture = spritesheet.texture;
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ focused: bool,
|
||||
keyboard: KeyStateType(KeyCode),
|
||||
mouse_buttons: KeyStateType(MouseButton),
|
||||
mouse_position: ?Vec2,
|
||||
mouse_delta: Vec2,
|
||||
mouse_scroll: Vec2,
|
||||
|
||||
pub fn init(window_size: Vec2) Input {
|
||||
@ -24,6 +25,7 @@ pub fn init(window_size: Vec2) Input {
|
||||
.keyboard = .empty,
|
||||
.mouse_buttons = .empty,
|
||||
.mouse_position = null,
|
||||
.mouse_delta = .init(0, 0),
|
||||
.mouse_scroll = .init(0, 0),
|
||||
.window_size = window_size
|
||||
};
|
||||
|
||||
27
src/math.zig
27
src/math.zig
@ -336,11 +336,11 @@ pub const Mat4 = extern struct {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initScale(scale: Vec3) Mat4 {
|
||||
pub fn initScale(vec: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[0][0] = scale.x;
|
||||
self.columns[1][1] = scale.y;
|
||||
self.columns[2][2] = scale.z;
|
||||
self.columns[0][0] = vec.x;
|
||||
self.columns[1][1] = vec.y;
|
||||
self.columns[2][2] = vec.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -352,6 +352,18 @@ pub const Mat4 = extern struct {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn translate(self: *Mat4, x: f32, y: f32, z: f32) void {
|
||||
self.columns[3][0] += x;
|
||||
self.columns[3][1] += y;
|
||||
self.columns[3][2] += z;
|
||||
}
|
||||
|
||||
pub fn scale(self: *Mat4, x: f32, y: f32, z: f32) void {
|
||||
self.columns[0][0] *= x;
|
||||
self.columns[1][1] *= y;
|
||||
self.columns[2][2] *= z;
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Mat4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(&self.columns))));
|
||||
return ptr[0..16];
|
||||
@ -431,6 +443,13 @@ pub const Rect = struct {
|
||||
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
||||
return x_overlap and y_overlap;
|
||||
}
|
||||
|
||||
pub fn shrink(self: Rect, margin: Vec2) Rect {
|
||||
return Rect{
|
||||
.pos = self.pos.add(margin),
|
||||
.size = self.pos.add(margin.multiplyScalar(2))
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
|
||||
@ -44,6 +44,7 @@ fn PlatformType(App: type) type {
|
||||
last_frame_at: Io.Timestamp,
|
||||
|
||||
assets: Assets,
|
||||
last_mouse_position: ?Vec2,
|
||||
input: Input,
|
||||
events_buffer: [256]Input.Event,
|
||||
events_overflow: bool,
|
||||
@ -108,6 +109,13 @@ fn PlatformType(App: type) type {
|
||||
self.show_imgui = !self.show_imgui;
|
||||
}
|
||||
|
||||
if (self.last_mouse_position != null and self.input.mouse_position != null) {
|
||||
self.input.mouse_delta = self.input.mouse_position.?.sub(self.last_mouse_position.?);
|
||||
} else {
|
||||
self.input.mouse_delta = .init(0, 0);
|
||||
}
|
||||
self.last_mouse_position = self.input.mouse_position;
|
||||
|
||||
{
|
||||
Gfx.beginFrame();
|
||||
ImGUI.beginFrame(self.show_imgui, self.frame_arena.allocator());
|
||||
@ -140,6 +148,7 @@ fn PlatformType(App: type) type {
|
||||
self.input.keyboard.released = .empty;
|
||||
self.input.mouse_buttons.pressed = .empty;
|
||||
self.input.mouse_buttons.released = .empty;
|
||||
self.input.mouse_scroll = .init(0, 0);
|
||||
}
|
||||
|
||||
fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void {
|
||||
@ -566,7 +575,8 @@ pub fn run(App: type, opts: RunOptions) void {
|
||||
.last_key_pressed = null,
|
||||
.frame_arena = .init(gpa),
|
||||
.show_imgui = builtin.mode == .Debug,
|
||||
.assets = .init(gpa, io, assets_dir)
|
||||
.assets = .init(gpa, io, assets_dir),
|
||||
.last_mouse_position = null
|
||||
};
|
||||
|
||||
inline for (EmbeddedAssets.files) |file| {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user