merge input subsystem into engine

This commit is contained in:
Rokas Puzonas 2026-01-18 10:30:43 +02:00
parent 69fed14d75
commit 61fb60ff1c
10 changed files with 456 additions and 450 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
zig-out
.zig-cache

View File

@ -60,6 +60,7 @@ pub fn build(b: *std.Build) !void {
const dep_stb = b.dependency("stb", .{});
mod_main.addImport("stb_image", dep_stb.module("stb_image"));
mod_main.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
mod_main.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
const dep_fontstash_c = b.dependency("fontstash_c", .{});
mod_main.addIncludePath(dep_fontstash_c.path("src"));

View File

@ -35,4 +35,19 @@ pub fn build(b: *std.Build) void {
.flags = &.{}
});
}
{
const mod_stb_rect_pack = b.addModule("stb_rect_pack", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/stb_rect_pack.zig"),
.link_libc = true,
});
mod_stb_rect_pack.addIncludePath(stb_dependency.path("."));
mod_stb_rect_pack.addCSourceFile(.{
.file = b.path("src/stb_rect_pack_impl.c"),
.flags = &.{}
});
}
}

View File

@ -0,0 +1,52 @@
const c = @cImport({
@cInclude("stb_rect_pack.h");
});
const STBRectPack = @This();
pub const Node = c.stbrp_node;
pub const Rect = c.stbrp_rect;
pub const Options = struct {
const Heuristic = enum(i32) {
bl_sort_height = c.STBRP_HEURISTIC_Skyline_BL_sortHeight,
bf_sort_height = c.STBRP_HEURISTIC_Skyline_BF_sortHeight,
pub const default: Heuristic = @enumFromInt(c.STBRP_HEURISTIC_Skyline_default);
};
width: u31,
height: u31,
nodes: []Node,
allow_out_of_memory: bool = false,
heuristic: Heuristic = .default,
};
ctx: c.stbrp_context,
pub fn init(options: Options) STBRectPack {
var ctx: c.stbrp_context = undefined;
c.stbrp_init_target(
&ctx,
options.width,
options.height,
options.nodes.ptr,
options.nodes.len
);
if (options.allow_out_of_memory) {
c.stbrp_setup_allow_out_of_mem(&ctx, 1);
}
if (options.heuristic != .default) {
c.stbrp_setup_heuristic(&ctx, options.heuristic);
}
return STBRectPack{ .ctx = ctx };
}
pub fn packRects(self: *STBRectPack, rects: []Rect) bool {
const success = c.stbrp_pack_rects(&self.ctx, rects.ptr, rects.len);
return success != 0;
}

View File

@ -0,0 +1,2 @@
#define STB_RECT_PACK_IMPLEMENTATION
#include "stb_rect_pack.h"

View File

@ -5,6 +5,8 @@ const Engine = @import("./engine/root.zig");
const Gfx = Engine.Graphics;
const Audio = Engine.Audio;
const STBRectPack = @import("stb_rect_pack");
const Assets = @This();
const FontName = enum {
@ -15,30 +17,21 @@ const FontName = enum {
const EnumArray = std.EnumArray(FontName, Gfx.Font.Id);
};
font_id: FontName.EnumArray,
pub var font_id: FontName.EnumArray = undefined;
pub var wood01: Audio.Data.Id = undefined;
wood01: Audio.Data.Id,
pub fn init(gpa: std.mem.Allocator) !Assets {
_ = gpa; // autofix
const font_id_array: FontName.EnumArray = .init(.{
pub fn init() !void {
font_id = .init(.{
.regular = try Gfx.addFont("regular", @embedFile("assets/roboto-font/Roboto-Regular.ttf")),
.bold = try Gfx.addFont("bold", @embedFile("assets/roboto-font/Roboto-Bold.ttf")),
.italic = try Gfx.addFont("italic", @embedFile("assets/roboto-font/Roboto-Italic.ttf")),
});
const wood01 = try Audio.load(.{
wood01 = try Audio.load(.{
.format = .vorbis,
.data = @embedFile("assets/wood01.ogg"),
});
return Assets{
.font_id = font_id_array,
.wood01 = wood01
};
}
pub fn deinit(self: *Assets, gpa: std.mem.Allocator) void {
_ = self; // autofix
_ = gpa; // autofix
pub fn deinit() void {
}

View File

@ -1,239 +0,0 @@
const std = @import("std");
const sokol = @import("sokol");
const Engine = @import("./root.zig");
const Nanoseconds = Engine.Nanoseconds;
const Math = @import("./math.zig");
const Vec2 = Math.Vec2;
const Input = @This();
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
SPACE = 32,
APOSTROPHE = 39,
COMMA = 44,
MINUS = 45,
PERIOD = 46,
SLASH = 47,
_0 = 48,
_1 = 49,
_2 = 50,
_3 = 51,
_4 = 52,
_5 = 53,
_6 = 54,
_7 = 55,
_8 = 56,
_9 = 57,
SEMICOLON = 59,
EQUAL = 61,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
LEFT_BRACKET = 91,
BACKSLASH = 92,
RIGHT_BRACKET = 93,
GRAVE_ACCENT = 96,
WORLD_1 = 161,
WORLD_2 = 162,
ESCAPE = 256,
ENTER = 257,
TAB = 258,
BACKSPACE = 259,
INSERT = 260,
DELETE = 261,
RIGHT = 262,
LEFT = 263,
DOWN = 264,
UP = 265,
PAGE_UP = 266,
PAGE_DOWN = 267,
HOME = 268,
END = 269,
CAPS_LOCK = 280,
SCROLL_LOCK = 281,
NUM_LOCK = 282,
PRINT_SCREEN = 283,
PAUSE = 284,
F1 = 290,
F2 = 291,
F3 = 292,
F4 = 293,
F5 = 294,
F6 = 295,
F7 = 296,
F8 = 297,
F9 = 298,
F10 = 299,
F11 = 300,
F12 = 301,
F13 = 302,
F14 = 303,
F15 = 304,
F16 = 305,
F17 = 306,
F18 = 307,
F19 = 308,
F20 = 309,
F21 = 310,
F22 = 311,
F23 = 312,
F24 = 313,
F25 = 314,
KP_0 = 320,
KP_1 = 321,
KP_2 = 322,
KP_3 = 323,
KP_4 = 324,
KP_5 = 325,
KP_6 = 326,
KP_7 = 327,
KP_8 = 328,
KP_9 = 329,
KP_DECIMAL = 330,
KP_DIVIDE = 331,
KP_MULTIPLY = 332,
KP_SUBTRACT = 333,
KP_ADD = 334,
KP_ENTER = 335,
KP_EQUAL = 336,
LEFT_SHIFT = 340,
LEFT_CONTROL = 341,
LEFT_ALT = 342,
LEFT_SUPER = 343,
RIGHT_SHIFT = 344,
RIGHT_CONTROL = 345,
RIGHT_ALT = 346,
RIGHT_SUPER = 347,
MENU = 348,
};
pub const KeyState = struct {
down: bool,
pressed: bool,
released: bool,
down_duration: ?f64,
pub const RepeatOptions = struct {
first_at: f64 = 0,
period: f64
};
pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool {
if (!self.down) {
last_repeat_at.* = null;
return false;
}
const down_duration = self.down_duration.?;
if (last_repeat_at.* != null) {
if (down_duration >= last_repeat_at.*.? + opts.period) {
last_repeat_at.* = last_repeat_at.*.? + opts.period;
return true;
}
} else {
if (down_duration >= opts.first_at) {
last_repeat_at.* = opts.first_at;
return true;
}
}
return false;
}
};
pub const Mouse = struct {
pub const Button = enum {
left,
right,
middle,
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?Button {
return switch(mouse_button) {
.LEFT => Button.left,
.RIGHT => Button.right,
.MIDDLE => Button.middle,
else => null
};
}
};
position: ?Vec2,
buttons: std.EnumSet(Button),
pub const empty = Mouse{
.position = null,
.buttons = .initEmpty()
};
};
down_keys: std.EnumSet(KeyCode),
pressed_keys: std.EnumSet(KeyCode),
released_keys: std.EnumSet(KeyCode),
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds),
mouse: Mouse,
pub const empty = Input{
.down_keys = .initEmpty(),
.pressed_keys = .initEmpty(),
.released_keys = .initEmpty(),
.pressed_keys_at = .init(.{}),
.mouse = .empty
};
pub fn isKeyDown(self: *Input, key_code: KeyCode) bool {
return self.down_keys.contains(key_code);
}
pub fn getKeyDownDuration(self: *Input, frame: Engine.Frame, key_code: KeyCode) ?f64 {
if (!self.isKeyDown(key_code)) {
return null;
}
const pressed_at_ns = self.pressed_keys_at.get(key_code).?;
const duration_ns = frame.time_ns - pressed_at_ns;
return @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_s;
}
pub fn isKeyPressed(self: *Input, key_code: KeyCode) bool {
return self.pressed_keys.contains(key_code);
}
pub fn isKeyReleased(self: *Input, key_code: KeyCode) bool {
return self.released_keys.contains(key_code);
}
pub fn getKeyState(self: *Input, frame: Engine.Frame, key_code: KeyCode) KeyState {
return KeyState{
.down = self.isKeyDown(key_code),
.released = self.isKeyReleased(key_code),
.pressed = self.isKeyPressed(key_code),
.down_duration = self.getKeyDownDuration(frame, key_code)
};
}

View File

@ -8,8 +8,6 @@ const sapp = sokol.app;
pub const Math = @import("./math.zig");
pub const Vec2 = Math.Vec2;
pub const Input = @import("./input.zig");
const ScreenScalar = @import("./screen_scaler.zig");
pub const imgui = @import("./imgui.zig");
pub const Graphics = @import("./graphics.zig");
@ -27,43 +25,240 @@ const Engine = @This();
pub const Nanoseconds = u64;
pub const Event = union(enum) {
const Event = union(enum) {
mouse_pressed: struct {
button: Input.Mouse.Button,
button: Mouse.Button,
position: Vec2,
},
mouse_released: struct {
button: Input.Mouse.Button,
button: Mouse.Button,
position: Vec2,
},
mouse_move: Vec2,
mouse_enter: Vec2,
mouse_leave,
mouse_scroll: Vec2,
key_pressed: struct {
code: Input.KeyCode,
repeat: bool
},
key_released: Input.KeyCode,
key_pressed: struct { code: KeyCode, repeat: bool },
key_released: KeyCode,
window_resize,
char: u21,
};
pub const Frame = struct {
time_ns: Nanoseconds,
dt_ns: Nanoseconds,
dt: f32,
input: *Input
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
SPACE = 32,
APOSTROPHE = 39,
COMMA = 44,
MINUS = 45,
PERIOD = 46,
SLASH = 47,
_0 = 48,
_1 = 49,
_2 = 50,
_3 = 51,
_4 = 52,
_5 = 53,
_6 = 54,
_7 = 55,
_8 = 56,
_9 = 57,
SEMICOLON = 59,
EQUAL = 61,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
LEFT_BRACKET = 91,
BACKSLASH = 92,
RIGHT_BRACKET = 93,
GRAVE_ACCENT = 96,
WORLD_1 = 161,
WORLD_2 = 162,
ESCAPE = 256,
ENTER = 257,
TAB = 258,
BACKSPACE = 259,
INSERT = 260,
DELETE = 261,
RIGHT = 262,
LEFT = 263,
DOWN = 264,
UP = 265,
PAGE_UP = 266,
PAGE_DOWN = 267,
HOME = 268,
END = 269,
CAPS_LOCK = 280,
SCROLL_LOCK = 281,
NUM_LOCK = 282,
PRINT_SCREEN = 283,
PAUSE = 284,
F1 = 290,
F2 = 291,
F3 = 292,
F4 = 293,
F5 = 294,
F6 = 295,
F7 = 296,
F8 = 297,
F9 = 298,
F10 = 299,
F11 = 300,
F12 = 301,
F13 = 302,
F14 = 303,
F15 = 304,
F16 = 305,
F17 = 306,
F18 = 307,
F19 = 308,
F20 = 309,
F21 = 310,
F22 = 311,
F23 = 312,
F24 = 313,
F25 = 314,
KP_0 = 320,
KP_1 = 321,
KP_2 = 322,
KP_3 = 323,
KP_4 = 324,
KP_5 = 325,
KP_6 = 326,
KP_7 = 327,
KP_8 = 328,
KP_9 = 329,
KP_DECIMAL = 330,
KP_DIVIDE = 331,
KP_MULTIPLY = 332,
KP_SUBTRACT = 333,
KP_ADD = 334,
KP_ENTER = 335,
KP_EQUAL = 336,
LEFT_SHIFT = 340,
LEFT_CONTROL = 341,
LEFT_ALT = 342,
LEFT_SUPER = 343,
RIGHT_SHIFT = 344,
RIGHT_CONTROL = 345,
RIGHT_ALT = 346,
RIGHT_SUPER = 347,
MENU = 348,
};
allocator: std.mem.Allocator,
started_at: std.time.Instant,
mouse_inside: bool,
last_frame_at: Nanoseconds,
input: Input,
pub const KeyState = struct {
down: bool,
pressed: bool,
released: bool,
down_duration: ?f64,
game: Game,
assets: Assets,
pub const RepeatOptions = struct {
first_at: f64 = 0,
period: f64
};
pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool {
if (!self.down) {
last_repeat_at.* = null;
return false;
}
const down_duration = self.down_duration.?;
if (last_repeat_at.* != null) {
if (down_duration >= last_repeat_at.*.? + opts.period) {
last_repeat_at.* = last_repeat_at.*.? + opts.period;
return true;
}
} else {
if (down_duration >= opts.first_at) {
last_repeat_at.* = opts.first_at;
return true;
}
}
return false;
}
};
pub const Mouse = struct {
pub const Button = enum {
left,
right,
middle,
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?Button {
return switch(mouse_button) {
.LEFT => Button.left,
.RIGHT => Button.right,
.MIDDLE => Button.middle,
else => null
};
}
};
position: ?Vec2,
buttons: std.EnumSet(Button),
pub const empty = Mouse{
.position = null,
.buttons = .initEmpty()
};
};
const State = struct {
const Input = struct {
down_keys: std.EnumSet(KeyCode),
pressed_keys: std.EnumSet(KeyCode),
released_keys: std.EnumSet(KeyCode),
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds),
mouse: Mouse,
pub const empty = Input{
.down_keys = .initEmpty(),
.pressed_keys = .initEmpty(),
.released_keys = .initEmpty(),
.pressed_keys_at = .init(.{}),
.mouse = .empty
};
};
allocator: std.mem.Allocator,
started_at: std.time.Instant,
last_frame_at_ns: Nanoseconds,
frame_delta_ns: Nanoseconds = 0,
// Input
input: Input = .empty,
mouse_inside: bool = false,
// Graphics
canvas_size: Vec2 = .init(100, 100),
// Audio
};
var state: State = undefined;
const RunOptions = struct {
window_title: [*:0]const u8 = "Game",
@ -71,29 +266,28 @@ const RunOptions = struct {
window_height: u31 = 480,
};
pub fn run(self: *Engine, opts: RunOptions) !void {
self.* = Engine{
.allocator = undefined,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.input = .empty,
.mouse_inside = false,
.last_frame_at = 0,
.assets = undefined,
.game = undefined
};
pub fn run(opts: RunOptions) !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
// TODO: Use tracy TracingAllocator
var allocator: std.mem.Allocator = undefined;
if (builtin.cpu.arch.isWasm()) {
self.allocator = std.heap.wasm_allocator;
allocator = std.heap.wasm_allocator;
} else if (builtin.mode == .Debug) {
self.allocator = debug_allocator.allocator();
allocator = debug_allocator.allocator();
} else {
self.allocator = std.heap.smp_allocator;
allocator = std.heap.smp_allocator;
}
state = State{
.allocator = allocator,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.input = .empty,
.mouse_inside = false,
.last_frame_at_ns = 0,
};
tracy.setThreadName("Main");
if (builtin.os.tag == .linux) {
@ -103,6 +297,8 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
.flags = std.posix.SA.RESTART,
};
std.posix.sigaction(std.posix.SIG.INT, &sa, null);
} else {
// TODO: Windows Ctrl+C handler
}
// TODO: Don't hard code icon path, allow changing through options
@ -121,11 +317,10 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
};
sapp.run(.{
.init_userdata_cb = sokolInitCallback,
.frame_userdata_cb = sokolFrameCallback,
.cleanup_userdata_cb = sokolCleanupCallback,
.event_userdata_cb = sokolEventCallback,
.user_data = self,
.init_cb = sokolInitCallback,
.frame_cb = sokolFrameCallback,
.cleanup_cb = sokolCleanupCallback,
.event_cb = sokolEventCallback,
.width = opts.window_width,
.height = opts.window_height,
.icon = icon,
@ -137,45 +332,90 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
});
}
fn sokolInit(self: *Engine) !void {
const zone = tracy.initZone(@src(), .{ });
defer zone.deinit();
try Gfx.init(.{
.allocator = self.allocator,
.logger = .{ .func = sokolLogCallback },
.imgui_font = .{
.ttf_data = @embedFile("../assets/roboto-font/Roboto-Regular.ttf"),
}
});
try Audio.init(.{
.allocator = self.allocator,
.logger = .{ .func = sokolLogCallback },
});
self.assets = try Assets.init(self.allocator);
self.game = try Game.init(self.allocator, &self.assets);
pub fn isKeyDown(key_code: KeyCode) bool {
const input = &state.input;
return input.down_keys.contains(key_code);
}
fn sokolCleanup(self: *Engine) void {
const zone = tracy.initZone(@src(), .{ });
pub fn getKeyDownDuration(frame: Engine.Frame, key_code: KeyCode) ?f64 {
if (!isKeyDown(key_code)) {
return null;
}
const input = &state.input;
const pressed_at_ns = input.pressed_keys_at.get(key_code).?;
const duration_ns = frame.time_ns - pressed_at_ns;
return @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_s;
}
pub fn isKeyPressed(key_code: KeyCode) bool {
const input = &state.input;
return input.pressed_keys.contains(key_code);
}
pub fn isKeyReleased(key_code: KeyCode) bool {
const input = &state.input;
return input.released_keys.contains(key_code);
}
pub fn getKeyState(key_code: KeyCode) KeyState {
return KeyState{
.down = isKeyDown(key_code),
.released = isKeyReleased(key_code),
.pressed = isKeyPressed(key_code),
.down_duration = getKeyDownDuration(key_code)
};
}
pub fn timePassed() Nanoseconds {
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
return now.since(state.started_at);
}
pub fn deltaTime() f32 {
return @as(f32, @floatFromInt(state.frame_delta_ns)) / std.time.ns_per_s;
}
pub fn getCanvasSize() Vec2 {
return state.canvas_size;
}
fn sokolInit() !void {
const zone = tracy.initZone(@src(), .{});
defer zone.deinit();
try Gfx.init(.{ .allocator = state.allocator, .logger = .{ .func = sokolLogCallback }, .imgui_font = .{
.ttf_data = @embedFile("../assets/roboto-font/Roboto-Regular.ttf"),
} });
try Audio.init(.{
.allocator = state.allocator,
.logger = .{ .func = sokolLogCallback },
});
try Assets.init();
try Game.init();
}
fn sokolCleanup() void {
const zone = tracy.initZone(@src(), .{});
defer zone.deinit();
Audio.deinit();
self.game.deinit();
self.assets.deinit(self.allocator);
Game.deinit();
Assets.deinit();
Gfx.deinit();
}
fn sokolFrame(self: *Engine) !void {
fn sokolFrame() !void {
tracy.frameMark();
const time_passed = self.timePassed();
defer self.last_frame_at = time_passed;
const dt_ns = time_passed - self.last_frame_at;
const time_passed = timePassed();
defer state.last_frame_at_ns = time_passed;
state.frame_delta_ns = time_passed - state.last_frame_at_ns;
const zone = tracy.initZone(@src(), .{ });
const zone = tracy.initZone(@src(), .{});
defer zone.deinit();
Gfx.beginFrame();
@ -183,37 +423,26 @@ fn sokolFrame(self: *Engine) !void {
{
const window_size: Vec2 = .init(sapp.widthf(), sapp.heightf());
const ctx = ScreenScalar.push(window_size, self.game.canvas_size);
const ctx = ScreenScalar.push(window_size, state.canvas_size);
defer ctx.pop();
Graphics.font_resolution_scale = ctx.scale;
try self.game.tick(Frame{
.time_ns = time_passed,
.dt_ns = dt_ns,
.dt = @as(f32, @floatFromInt(dt_ns)) / std.time.ns_per_s,
.input = &self.input
});
try Game.tick();
}
try self.game.debug();
try Game.debug();
self.input.pressed_keys = .initEmpty();
self.input.released_keys = .initEmpty();
state.input.pressed_keys = .initEmpty();
state.input.released_keys = .initEmpty();
}
fn timePassed(self: *Engine) Nanoseconds {
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
return now.since(self.started_at);
}
fn event(self: *Engine, e: Event) !void {
const input = &self.input;
fn processEvent(e: Event) !void {
const input = &state.input;
switch (e) {
.key_pressed => |opts| {
if (!opts.repeat) {
input.pressed_keys_at.put(opts.code, self.timePassed());
input.pressed_keys_at.put(opts.code, timePassed());
input.pressed_keys.insert(opts.code);
input.down_keys.insert(opts.code);
}
@ -247,32 +476,28 @@ fn event(self: *Engine, e: Event) !void {
input.mouse.position = opts.position;
input.mouse.buttons.remove(opts.button);
},
else => {}
else => {},
}
}
fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
const zone = tracy.initZone(@src(), .{ });
fn sokolEvent(e_ptr: [*c]const sapp.Event) !bool {
const zone = tracy.initZone(@src(), .{});
defer zone.deinit();
const e = e_ptr.*;
const MouseButton = Input.Mouse.Button;
if (imgui.handleEvent(e)) {
if (self.mouse_inside) {
try self.event(Event{
.mouse_leave = {}
});
if (state.mouse_inside) {
try processEvent(Event{ .mouse_leave = {} });
}
self.mouse_inside = false;
state.mouse_inside = false;
return true;
}
blk: switch (e.type) {
.MOUSE_DOWN => {
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
const mouse_button = Mouse.Button.fromSokol(e.mouse_button) orelse break :blk;
try self.event(Event{
try processEvent(Event{
.mouse_pressed = .{
.button = mouse_button,
.position = Vec2.init(e.mouse_x, e.mouse_y)
@ -282,9 +507,9 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true;
},
.MOUSE_UP => {
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
const mouse_button = Mouse.Button.fromSokol(e.mouse_button) orelse break :blk;
try self.event(Event{
try processEvent(Event{
.mouse_released = .{
.button = mouse_button,
.position = Vec2.init(e.mouse_x, e.mouse_y)
@ -294,62 +519,54 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true;
},
.MOUSE_MOVE => {
if (!self.mouse_inside) {
try self.event(Event{
if (!state.mouse_inside) {
try processEvent(Event{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
});
} else {
try self.event(Event{
try processEvent(Event{
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
});
}
self.mouse_inside = true;
state.mouse_inside = true;
return true;
},
.MOUSE_ENTER => {
if (!self.mouse_inside) {
try self.event(Event{
if (!state.mouse_inside) {
try processEvent(Event{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
});
}
self.mouse_inside = true;
state.mouse_inside = true;
return true;
},
.RESIZED => {
if (self.mouse_inside) {
try self.event(Event{
.mouse_leave = {}
});
if (state.mouse_inside) {
try processEvent(Event{ .mouse_leave = {} });
}
try self.event(Event{
.window_resize = {}
});
try processEvent(Event{ .window_resize = {} });
self.mouse_inside = false;
state.mouse_inside = false;
return true;
},
.MOUSE_LEAVE => {
if (self.mouse_inside) {
try self.event(Event{
.mouse_leave = {}
});
if (state.mouse_inside) {
try processEvent(Event{ .mouse_leave = {} });
}
self.mouse_inside = false;
state.mouse_inside = false;
return true;
},
.MOUSE_SCROLL => {
try self.event(Event{
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
});
try processEvent(Event{ .mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y) });
return true;
},
.KEY_DOWN => {
try self.event(Event{
try processEvent(Event{
.key_pressed = .{
.code = @enumFromInt(@intFromEnum(e.key_code)),
.repeat = e.key_repeat
@ -359,14 +576,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true;
},
.KEY_UP => {
try self.event(Event{
try processEvent(Event{
.key_released = @enumFromInt(@intFromEnum(e.key_code))
});
return true;
},
.CHAR => {
try self.event(Event{
try processEvent(Event{
.char = @intCast(e.char_code)
});
@ -375,16 +592,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
.QUIT_REQUESTED => {
// TODO: handle quit request. Maybe show confirmation window in certain cases.
},
else => {}
else => {},
}
return false;
}
fn sokolEventCallback(e_ptr: [*c]const sapp.Event, userdata: ?*anyopaque) callconv(.c) void {
const engine: *Engine = @alignCast(@ptrCast(userdata));
const consume_event = engine.sokolEvent(e_ptr) catch |e| blk: {
fn sokolEventCallback(e_ptr: [*c]const sapp.Event) callconv(.c) void {
const consume_event = sokolEvent(e_ptr) catch |e| blk: {
log.err("sokolEvent() failed: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
@ -397,16 +612,12 @@ fn sokolEventCallback(e_ptr: [*c]const sapp.Event, userdata: ?*anyopaque) callco
}
}
fn sokolCleanupCallback(userdata: ?*anyopaque) callconv(.c) void {
const engine: *Engine = @alignCast(@ptrCast(userdata));
engine.sokolCleanup();
fn sokolCleanupCallback() callconv(.c) void {
sokolCleanup();
}
fn sokolInitCallback(userdata: ?*anyopaque) callconv(.c) void {
const engine: *Engine = @alignCast(@ptrCast(userdata));
engine.sokolInit() catch |e| {
fn sokolInitCallback() callconv(.c) void {
sokolInit() catch |e| {
log.err("sokolInit() failed: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
@ -415,10 +626,8 @@ fn sokolInitCallback(userdata: ?*anyopaque) callconv(.c) void {
};
}
fn sokolFrameCallback(userdata: ?*anyopaque) callconv(.c) void {
const engine: *Engine = @alignCast(@ptrCast(userdata));
engine.sokolFrame() catch |e| {
fn sokolFrameCallback() callconv(.c) void {
sokolFrame() catch |e| {
log.err("sokolFrame() failed: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
@ -449,27 +658,9 @@ fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [
_ = user_data;
if (filename != null) {
sokolLogFmt(
log_level,
"[{s}][id:{}] {s}:{}: {s}",
.{
cStrToZig(tag orelse "-"),
log_item,
std.fs.path.basename(cStrToZig(filename orelse "-")),
line_nr,
cStrToZig(message orelse "")
}
);
sokolLogFmt(log_level, "[{s}][id:{}] {s}:{}: {s}", .{ cStrToZig(tag orelse "-"), log_item, std.fs.path.basename(cStrToZig(filename orelse "-")), line_nr, cStrToZig(message orelse "") });
} else {
sokolLogFmt(
log_level,
"[{s}][id:{}] {s}",
.{
cStrToZig(tag orelse "-"),
log_item,
cStrToZig(message orelse "")
}
);
sokolLogFmt(log_level, "[{s}][id:{}] {s}", .{ cStrToZig(tag orelse "-"), log_item, cStrToZig(message orelse "") });
}
}

View File

@ -13,67 +13,57 @@ const Audio = Engine.Audio;
const Game = @This();
gpa: Allocator,
assets: *Assets,
canvas_size: Vec2,
var player_position: Vec2 = undefined;
player: Vec2,
pub fn init(gpa: Allocator, assets: *Assets) !Game {
return Game{
.gpa = gpa,
.assets = assets,
.canvas_size = Vec2.init(100, 100),
.player = .init(50, 50)
};
pub fn init() !void {
player_position = .init(50, 50);
}
pub fn deinit(self: *Game) void {
_ = self; // autofix
pub fn deinit() void {
}
pub fn tick(self: *Game, frame: Engine.Frame) !void {
pub fn tick() !void {
const dt = Engine.deltaTime();
var dir = Vec2.init(0, 0);
if (frame.input.isKeyDown(.W)) {
if (Engine.isKeyDown(.W)) {
dir.y -= 1;
}
if (frame.input.isKeyDown(.S)) {
if (Engine.isKeyDown(.S)) {
dir.y += 1;
}
if (frame.input.isKeyDown(.A)) {
if (Engine.isKeyDown(.A)) {
dir.x -= 1;
}
if (frame.input.isKeyDown(.D)) {
if (Engine.isKeyDown(.D)) {
dir.x += 1;
}
dir = dir.normalized();
if (dir.x != 0 or dir.y != 0) {
Audio.play(.{
.id = self.assets.wood01
.id = Assets.wood01
});
}
self.player = self.player.add(dir.multiplyScalar(50 * frame.dt));
player_position = player_position.add(dir.multiplyScalar(50 * dt));
const regular_font = self.assets.font_id.get(.regular);
const regular_font = Assets.font_id.get(.regular);
Gfx.drawRectangle(.init(0, 0), self.canvas_size, rgb(20, 20, 20));
Gfx.drawRectangle(.init(0, 0), Engine.getCanvasSize(), rgb(20, 20, 20));
const size = Vec2.init(20, 20);
Gfx.drawRectangle(self.player.sub(size.divideScalar(2)), size, rgb(200, 20, 20));
Gfx.drawRectangle(player_position.sub(size.divideScalar(2)), size, rgb(200, 20, 20));
if (dir.x != 0 or dir.y != 0) {
Gfx.drawRectanglOutline(self.player.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 3);
Gfx.drawRectanglOutline(player_position.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 3);
}
Gfx.drawText(self.player, "Player", .{
Gfx.drawText(player_position, "Player", .{
.font = regular_font,
.size = 10
});
}
pub fn debug(self: *Game) !void {
_ = self; // autofix
pub fn debug() !void {
if (!imgui.beginWindow(.{
.name = "Debug",
.pos = Vec2.init(20, 20),

View File

@ -1,8 +1,7 @@
const Engine = @import("./engine/root.zig");
var engine: Engine = undefined;
pub fn main() !void {
try engine.run(.{});
try Engine.run(.{
});
}