move engine code to separate folder
This commit is contained in:
parent
8b7c50375c
commit
4df4f42022
11
src/assets.zig
Normal file
11
src/assets.zig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
const Assets = @This();
|
||||||
|
|
||||||
|
pub fn init() !Assets {
|
||||||
|
return Assets{
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Assets) void {
|
||||||
|
_ = self; // autofix
|
||||||
|
}
|
||||||
@ -825,7 +825,7 @@ pub fn init(options: Options) !void {
|
|||||||
.disable_set_mouse_cursor = true
|
.disable_set_mouse_cursor = true
|
||||||
});
|
});
|
||||||
|
|
||||||
imgui.addFont(@embedFile("./assets/roboto-font/Roboto-Regular.ttf"), 16);
|
imgui.addFont(@embedFile("../assets/roboto-font/Roboto-Regular.ttf"), 16);
|
||||||
|
|
||||||
// make sure the fontstash atlas width/height is pow-2
|
// make sure the fontstash atlas width/height is pow-2
|
||||||
const atlas_dim = std.math.ceilPowerOfTwoAssert(u32, @intFromFloat(512 * dpi_scale));
|
const atlas_dim = std.math.ceilPowerOfTwoAssert(u32, @intFromFloat(512 * dpi_scale));
|
||||||
@ -836,12 +836,12 @@ pub fn init(options: Options) !void {
|
|||||||
assert(g_fons_context != null);
|
assert(g_fons_context != null);
|
||||||
|
|
||||||
font_id_map = .init(.{
|
font_id_map = .init(.{
|
||||||
.regular = loadEmbededFont(g_fons_context, "regular", "./assets/roboto-font/Roboto-Regular.ttf"),
|
.regular = loadEmbededFont(g_fons_context, "regular", "../assets/roboto-font/Roboto-Regular.ttf"),
|
||||||
.italic = loadEmbededFont(g_fons_context, "italic", "./assets/roboto-font/Roboto-Italic.ttf"),
|
.italic = loadEmbededFont(g_fons_context, "italic", "../assets/roboto-font/Roboto-Italic.ttf"),
|
||||||
.bold = loadEmbededFont(g_fons_context, "bold", "./assets/roboto-font/Roboto-Bold.ttf"),
|
.bold = loadEmbededFont(g_fons_context, "bold", "../assets/roboto-font/Roboto-Bold.ttf"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const tilemap = try ImageData.load(@embedFile("./assets/kenney-micro-roguelike/colored_tilemap_packed.png"));
|
const tilemap = try ImageData.load(@embedFile("../assets/kenney-micro-roguelike/colored_tilemap_packed.png"));
|
||||||
defer tilemap.deinit();
|
defer tilemap.deinit();
|
||||||
|
|
||||||
image_map = .init(.{
|
image_map = .init(.{
|
||||||
@ -1057,10 +1057,6 @@ pub fn popTransform() void {
|
|||||||
sgl.popMatrix();
|
sgl.popMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(ev: [*c]const sapp.Event) bool {
|
|
||||||
return imgui.handleEvent(ev.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn createProjectionMatrix() Mat4 {
|
fn createProjectionMatrix() Mat4 {
|
||||||
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
||||||
|
|
||||||
239
src/engine/input.zig
Normal file
239
src/engine/input.zig
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
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)
|
||||||
|
};
|
||||||
|
}
|
||||||
445
src/engine/root.zig
Normal file
445
src/engine/root.zig
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const log = std.log.scoped(.engine);
|
||||||
|
|
||||||
|
const sokol = @import("sokol");
|
||||||
|
const sapp = sokol.app;
|
||||||
|
|
||||||
|
pub const Math = @import("./math.zig");
|
||||||
|
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");
|
||||||
|
const tracy = @import("tracy");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const Gfx = Graphics;
|
||||||
|
|
||||||
|
const Game = @import("../game.zig");
|
||||||
|
const Assets = @import("../assets.zig");
|
||||||
|
|
||||||
|
const Engine = @This();
|
||||||
|
|
||||||
|
pub const Nanoseconds = u64;
|
||||||
|
|
||||||
|
pub const Event = union(enum) {
|
||||||
|
mouse_pressed: struct {
|
||||||
|
button: Input.Mouse.Button,
|
||||||
|
position: Vec2,
|
||||||
|
},
|
||||||
|
mouse_released: struct {
|
||||||
|
button: Input.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,
|
||||||
|
window_resize,
|
||||||
|
char: u21,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Frame = struct {
|
||||||
|
time_ns: Nanoseconds,
|
||||||
|
dt_ns: Nanoseconds,
|
||||||
|
dt: f32,
|
||||||
|
input: *Input
|
||||||
|
};
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
started_at: std.time.Instant,
|
||||||
|
mouse_inside: bool,
|
||||||
|
last_frame_at: Nanoseconds,
|
||||||
|
input: Input,
|
||||||
|
|
||||||
|
game: Game,
|
||||||
|
assets: Assets,
|
||||||
|
|
||||||
|
const RunOptions = struct {
|
||||||
|
window_title: [*:0]const u8 = "Game",
|
||||||
|
window_width: u31 = 640,
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||||
|
defer _ = debug_allocator.deinit();
|
||||||
|
|
||||||
|
// TODO: Use tracy TracingAllocator
|
||||||
|
if (builtin.mode == .Debug) {
|
||||||
|
self.allocator = debug_allocator.allocator();
|
||||||
|
} else {
|
||||||
|
self.allocator = std.heap.smp_allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracy.setThreadName("Main");
|
||||||
|
|
||||||
|
if (builtin.os.tag == .linux) {
|
||||||
|
var sa: std.posix.Sigaction = .{
|
||||||
|
.handler = .{ .handler = posixSignalHandler },
|
||||||
|
.mask = std.posix.sigemptyset(),
|
||||||
|
.flags = std.posix.SA.RESTART,
|
||||||
|
};
|
||||||
|
std.posix.sigaction(std.posix.SIG.INT, &sa, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
sapp.run(.{
|
||||||
|
.init_userdata_cb = sokolInitCallback,
|
||||||
|
.frame_userdata_cb = sokolFrameCallback,
|
||||||
|
.cleanup_userdata_cb = sokolCleanupCallback,
|
||||||
|
.event_userdata_cb = sokolEventCallback,
|
||||||
|
.user_data = self,
|
||||||
|
.width = opts.window_width,
|
||||||
|
.height = opts.window_height,
|
||||||
|
.icon = .{ .sokol_default = true },
|
||||||
|
.window_title = opts.window_title,
|
||||||
|
.logger = .{ .func = sokolLogCallback },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolInit(self: *Engine) !void {
|
||||||
|
const zone = tracy.initZone(@src(), .{ });
|
||||||
|
defer zone.deinit();
|
||||||
|
|
||||||
|
try Gfx.init(.{
|
||||||
|
.allocator = self.allocator,
|
||||||
|
.logger = .{ .func = sokolLogCallback }
|
||||||
|
});
|
||||||
|
|
||||||
|
self.assets = try Assets.init();
|
||||||
|
self.game = try Game.init(self.allocator, &self.assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolCleanup(self: *Engine) void {
|
||||||
|
const zone = tracy.initZone(@src(), .{ });
|
||||||
|
defer zone.deinit();
|
||||||
|
|
||||||
|
self.game.deinit();
|
||||||
|
self.assets.deinit();
|
||||||
|
Gfx.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolFrame(self: *Engine) !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 zone = tracy.initZone(@src(), .{ });
|
||||||
|
defer zone.deinit();
|
||||||
|
|
||||||
|
Gfx.beginFrame();
|
||||||
|
defer Gfx.endFrame();
|
||||||
|
|
||||||
|
{
|
||||||
|
const window_size: Vec2 = .init(sapp.widthf(), sapp.heightf());
|
||||||
|
const ctx = ScreenScalar.push(window_size, self.game.canvas_size);
|
||||||
|
defer ctx.pop();
|
||||||
|
|
||||||
|
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 self.game.debug();
|
||||||
|
|
||||||
|
self.input.pressed_keys = .initEmpty();
|
||||||
|
self.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;
|
||||||
|
|
||||||
|
switch (e) {
|
||||||
|
.key_pressed => |opts| {
|
||||||
|
if (!opts.repeat) {
|
||||||
|
input.pressed_keys_at.put(opts.code, self.timePassed());
|
||||||
|
input.pressed_keys.insert(opts.code);
|
||||||
|
input.down_keys.insert(opts.code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.key_released => |key_code| {
|
||||||
|
input.down_keys.remove(key_code);
|
||||||
|
input.released_keys.insert(key_code);
|
||||||
|
input.pressed_keys_at.remove(key_code);
|
||||||
|
},
|
||||||
|
.mouse_leave => {
|
||||||
|
var iter = input.down_keys.iterator();
|
||||||
|
while (iter.next()) |key_code| {
|
||||||
|
input.released_keys.insert(key_code);
|
||||||
|
}
|
||||||
|
input.down_keys = .initEmpty();
|
||||||
|
input.pressed_keys_at = .init(.{});
|
||||||
|
|
||||||
|
input.mouse = .empty;
|
||||||
|
},
|
||||||
|
.mouse_enter => |pos| {
|
||||||
|
input.mouse.position = pos;
|
||||||
|
},
|
||||||
|
.mouse_move => |pos| {
|
||||||
|
input.mouse.position = pos;
|
||||||
|
},
|
||||||
|
.mouse_pressed => |opts| {
|
||||||
|
input.mouse.position = opts.position;
|
||||||
|
input.mouse.buttons.insert(opts.button);
|
||||||
|
},
|
||||||
|
.mouse_released => |opts| {
|
||||||
|
input.mouse.position = opts.position;
|
||||||
|
input.mouse.buttons.remove(opts.button);
|
||||||
|
},
|
||||||
|
else => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolEvent(self: *Engine, 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 = {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.mouse_inside = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
blk: switch (e.type) {
|
||||||
|
.MOUSE_DOWN => {
|
||||||
|
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||||
|
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_pressed = .{
|
||||||
|
.button = mouse_button,
|
||||||
|
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.MOUSE_UP => {
|
||||||
|
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||||
|
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_released = .{
|
||||||
|
.button = mouse_button,
|
||||||
|
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.MOUSE_MOVE => {
|
||||||
|
if (!self.mouse_inside) {
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mouse_inside = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.MOUSE_ENTER => {
|
||||||
|
if (!self.mouse_inside) {
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mouse_inside = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.RESIZED => {
|
||||||
|
if (self.mouse_inside) {
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_leave = {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.event(Event{
|
||||||
|
.window_resize = {}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.mouse_inside = false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.MOUSE_LEAVE => {
|
||||||
|
if (self.mouse_inside) {
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_leave = {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mouse_inside = false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.MOUSE_SCROLL => {
|
||||||
|
try self.event(Event{
|
||||||
|
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.KEY_DOWN => {
|
||||||
|
try self.event(Event{
|
||||||
|
.key_pressed = .{
|
||||||
|
.code = @enumFromInt(@intFromEnum(e.key_code)),
|
||||||
|
.repeat = e.key_repeat
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.KEY_UP => {
|
||||||
|
try self.event(Event{
|
||||||
|
.key_released = @enumFromInt(@intFromEnum(e.key_code))
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.CHAR => {
|
||||||
|
try self.event(Event{
|
||||||
|
.char = @intCast(e.char_code)
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.QUIT_REQUESTED => {
|
||||||
|
// TODO: handle quit request. Maybe show confirmation window in certain cases.
|
||||||
|
},
|
||||||
|
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: {
|
||||||
|
log.err("sokolEvent() failed: {}", .{e});
|
||||||
|
if (@errorReturnTrace()) |trace| {
|
||||||
|
std.debug.dumpStackTrace(trace.*);
|
||||||
|
}
|
||||||
|
break :blk false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (consume_event) {
|
||||||
|
sapp.consumeEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolCleanupCallback(userdata: ?*anyopaque) callconv(.c) void {
|
||||||
|
const engine: *Engine = @alignCast(@ptrCast(userdata));
|
||||||
|
|
||||||
|
engine.sokolCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolInitCallback(userdata: ?*anyopaque) callconv(.c) void {
|
||||||
|
const engine: *Engine = @alignCast(@ptrCast(userdata));
|
||||||
|
|
||||||
|
engine.sokolInit() catch |e| {
|
||||||
|
log.err("sokolInit() failed: {}", .{e});
|
||||||
|
if (@errorReturnTrace()) |trace| {
|
||||||
|
std.debug.dumpStackTrace(trace.*);
|
||||||
|
}
|
||||||
|
sapp.requestQuit();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolFrameCallback(userdata: ?*anyopaque) callconv(.c) void {
|
||||||
|
const engine: *Engine = @alignCast(@ptrCast(userdata));
|
||||||
|
|
||||||
|
engine.sokolFrame() catch |e| {
|
||||||
|
log.err("sokolFrame() failed: {}", .{e});
|
||||||
|
if (@errorReturnTrace()) |trace| {
|
||||||
|
std.debug.dumpStackTrace(trace.*);
|
||||||
|
}
|
||||||
|
sapp.requestQuit();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolLogFmt(log_level: u32, comptime format: []const u8, args: anytype) void {
|
||||||
|
const log_sokol = std.log.scoped(.sokol);
|
||||||
|
|
||||||
|
if (log_level == 0) {
|
||||||
|
log_sokol.err(format, args);
|
||||||
|
} else if (log_level == 1) {
|
||||||
|
log_sokol.err(format, args);
|
||||||
|
} else if (log_level == 2) {
|
||||||
|
log_sokol.warn(format, args);
|
||||||
|
} else {
|
||||||
|
log_sokol.info(format, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {
|
||||||
|
return std.mem.span(c_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [*c]const u8, line_nr: u32, filename: [*c]const u8, user_data: ?*anyopaque) callconv(.c) void {
|
||||||
|
_ = 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 "")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sokolLogFmt(
|
||||||
|
log_level,
|
||||||
|
"[{s}][id:{}] {s}",
|
||||||
|
.{
|
||||||
|
cStrToZig(tag orelse "-"),
|
||||||
|
log_item,
|
||||||
|
cStrToZig(message orelse "")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn posixSignalHandler(sig: i32) callconv(.c) void {
|
||||||
|
_ = sig;
|
||||||
|
sapp.requestQuit();
|
||||||
|
}
|
||||||
63
src/engine/screen_scaler.zig
Normal file
63
src/engine/screen_scaler.zig
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
const Gfx = @import("./graphics.zig");
|
||||||
|
|
||||||
|
const Math = @import("./math.zig");
|
||||||
|
const Vec2 = Math.Vec2;
|
||||||
|
const rgb = Math.rgb;
|
||||||
|
|
||||||
|
const ScreenScalar = @This();
|
||||||
|
|
||||||
|
window_size: Vec2,
|
||||||
|
translation: Vec2,
|
||||||
|
scale: f32,
|
||||||
|
|
||||||
|
pub fn push(window_size: Vec2, canvas_size: Vec2) ScreenScalar {
|
||||||
|
// TODO: Render to a lower resolution instead of scaling.
|
||||||
|
// To avoid pixel bleeding in spritesheet artifacts
|
||||||
|
const scale = @floor(@min(
|
||||||
|
window_size.x / canvas_size.x,
|
||||||
|
window_size.y / canvas_size.y,
|
||||||
|
));
|
||||||
|
|
||||||
|
var translation: Vec2 = Vec2.sub(window_size, canvas_size.multiplyScalar(scale)).multiplyScalar(0.5);
|
||||||
|
translation.x = @round(translation.x);
|
||||||
|
translation.y = @round(translation.y);
|
||||||
|
|
||||||
|
Gfx.pushTransform(translation, scale);
|
||||||
|
|
||||||
|
return ScreenScalar{
|
||||||
|
.window_size = window_size,
|
||||||
|
.translation = translation,
|
||||||
|
.scale = scale
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(self: ScreenScalar) void {
|
||||||
|
Gfx.popTransform();
|
||||||
|
|
||||||
|
const bg_color = rgb(0, 0, 0);
|
||||||
|
const filler_size = self.translation;
|
||||||
|
|
||||||
|
Gfx.drawRectangle(
|
||||||
|
.init(0, 0),
|
||||||
|
.init(self.window_size.x, filler_size.y),
|
||||||
|
bg_color
|
||||||
|
);
|
||||||
|
|
||||||
|
Gfx.drawRectangle(
|
||||||
|
.init(0, self.window_size.y - filler_size.y),
|
||||||
|
.init(self.window_size.x, filler_size.y),
|
||||||
|
bg_color
|
||||||
|
);
|
||||||
|
|
||||||
|
Gfx.drawRectangle(
|
||||||
|
.init(0, 0),
|
||||||
|
.init(filler_size.x, self.window_size.y),
|
||||||
|
bg_color
|
||||||
|
);
|
||||||
|
|
||||||
|
Gfx.drawRectangle(
|
||||||
|
.init(self.window_size.x - filler_size.x, 0),
|
||||||
|
.init(filler_size.x, self.window_size.y),
|
||||||
|
bg_color
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,9 +1,8 @@
|
|||||||
const GenerationalArrayList = @import("./generational_array_list.zig").GenerationalArrayList;
|
const GenerationalArrayList = @import("./generational_array_list.zig").GenerationalArrayList;
|
||||||
|
|
||||||
const Gfx = @import("./graphics.zig");
|
const Engine = @import("./engine/root.zig");
|
||||||
|
const Vec2 = Engine.Math.Vec2;
|
||||||
const Math = @import("./math.zig");
|
const Gfx = Engine.Graphics;
|
||||||
const Vec2 = Math.Vec2;
|
|
||||||
|
|
||||||
const Entity = @This();
|
const Entity = @This();
|
||||||
|
|
||||||
|
|||||||
52
src/game.zig
52
src/game.zig
@ -2,28 +2,30 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const Math = @import("./math.zig");
|
const Engine = @import("./engine/root.zig");
|
||||||
const Vec2 = Math.Vec2;
|
const imgui = Engine.imgui;
|
||||||
const Vec4 = Math.Vec4;
|
const Gfx = Engine.Graphics;
|
||||||
const rgb = Math.rgb;
|
const Vec2 = Engine.Math.Vec2;
|
||||||
const rgb_hex = Math.rgb_hex;
|
const Vec4 = Engine.Math.Vec4;
|
||||||
|
const rgb = Engine.Math.rgb;
|
||||||
|
const rgb_hex = Engine.Math.rgb_hex;
|
||||||
|
|
||||||
const Timer = @import("./timer.zig");
|
const Timer = @import("./timer.zig");
|
||||||
const Window = @import("./window.zig");
|
|
||||||
const imgui = @import("./imgui.zig");
|
|
||||||
const Gfx = @import("./graphics.zig");
|
|
||||||
const Entity = @import("./entity.zig");
|
const Entity = @import("./entity.zig");
|
||||||
|
const Assets = @import("./assets.zig");
|
||||||
|
|
||||||
const tiled = @import("tiled");
|
const tiled = @import("tiled");
|
||||||
|
|
||||||
const Game = @This();
|
const Game = @This();
|
||||||
|
|
||||||
|
const KeyState = Engine.Input.KeyState;
|
||||||
|
|
||||||
pub const Input = struct {
|
pub const Input = struct {
|
||||||
dt: f64,
|
dt: f64,
|
||||||
move_up: Window.KeyState,
|
move_up: KeyState,
|
||||||
move_down: Window.KeyState,
|
move_down: KeyState,
|
||||||
move_left: Window.KeyState,
|
move_left: KeyState,
|
||||||
move_right: Window.KeyState,
|
move_right: KeyState,
|
||||||
restart: bool
|
restart: bool
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,7 +82,8 @@ finale: bool = false,
|
|||||||
finale_timer: ?Timer.Id = null,
|
finale_timer: ?Timer.Id = null,
|
||||||
finale_counter: u32 = 0,
|
finale_counter: u32 = 0,
|
||||||
|
|
||||||
pub fn init(gpa: Allocator) !Game {
|
pub fn init(gpa: Allocator, assets: *Assets) !Game {
|
||||||
|
_ = assets; // autofix
|
||||||
var self = Game{
|
var self = Game{
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
.canvas_size = (Vec2.init(20, 15)),
|
.canvas_size = (Vec2.init(20, 15)),
|
||||||
@ -323,17 +326,16 @@ fn moveEntity(self: *Game, entity_id: Entity.Id, dir: Vec2) bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getInput(self: *Game, window: *Window) Input {
|
pub fn getInput(self: *Game, frame: Engine.Frame) Input {
|
||||||
_ = self; // autofix
|
_ = self; // autofix
|
||||||
const dt = @as(f32, @floatFromInt(window.frame_dt_ns)) / std.time.ns_per_s;
|
const input = frame.input;
|
||||||
|
|
||||||
return Input{
|
return Input{
|
||||||
.dt = dt,
|
.dt = frame.dt,
|
||||||
.move_up = window.getKeyState(.W),
|
.move_up = input.getKeyState(frame, .W),
|
||||||
.move_down = window.getKeyState(.S),
|
.move_down = input.getKeyState(frame, .S),
|
||||||
.move_left = window.getKeyState(.A),
|
.move_left = input.getKeyState(frame, .A),
|
||||||
.move_right = window.getKeyState(.D),
|
.move_right = input.getKeyState(frame, .D),
|
||||||
.restart = window.isKeyPressed(.R)
|
.restart = input.isKeyPressed(.R)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,7 +412,7 @@ pub fn tickLevel(self: *Game, input: Input) !void {
|
|||||||
|
|
||||||
var move: Vec2 = .init(0, 0);
|
var move: Vec2 = .init(0, 0);
|
||||||
if (can_move) {
|
if (can_move) {
|
||||||
const repeat_options = Window.KeyState.RepeatOptions{
|
const repeat_options = KeyState.RepeatOptions{
|
||||||
.first_at = 0.3,
|
.first_at = 0.3,
|
||||||
.period = 0.1
|
.period = 0.1
|
||||||
};
|
};
|
||||||
@ -541,7 +543,9 @@ pub fn tickFinale(self: *Game) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *Game, input: Input) !void {
|
pub fn tick(self: *Game, frame: Engine.Frame) !void {
|
||||||
|
const input = self.getInput(frame);
|
||||||
|
|
||||||
const bg_color = rgb_hex("#222323").?;
|
const bg_color = rgb_hex("#222323").?;
|
||||||
|
|
||||||
Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), bg_color);
|
Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), bg_color);
|
||||||
|
|||||||
117
src/main.zig
117
src/main.zig
@ -1,118 +1,7 @@
|
|||||||
const std = @import("std");
|
const Engine = @import("./engine/root.zig");
|
||||||
const tracy = @import("tracy");
|
|
||||||
const Gfx = @import("./graphics.zig");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const Window = @import("./window.zig");
|
|
||||||
const Event = Window.Event;
|
|
||||||
const MouseButton = Window.MouseButton;
|
|
||||||
|
|
||||||
const Math = @import("./math.zig");
|
|
||||||
const Vec2 = Math.Vec2;
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
const slog = sokol.log;
|
|
||||||
const sg = sokol.gfx;
|
|
||||||
const sapp = sokol.app;
|
|
||||||
const sglue = sokol.glue;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.main);
|
|
||||||
|
|
||||||
var gpa: std.mem.Allocator = undefined;
|
|
||||||
|
|
||||||
var window: Window = undefined;
|
|
||||||
var event_queue_full_shown = false;
|
|
||||||
|
|
||||||
fn signalHandler(sig: i32) callconv(.c) void {
|
|
||||||
_ = sig;
|
|
||||||
sapp.requestQuit();
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn init() void {
|
|
||||||
var zone = tracy.initZone(@src(), .{ });
|
|
||||||
defer zone.deinit();
|
|
||||||
|
|
||||||
Window.init(&window, gpa) catch |e| {
|
|
||||||
log.err("init() failed: {}", .{e});
|
|
||||||
sapp.requestQuit();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn frame() void {
|
|
||||||
tracy.frameMark();
|
|
||||||
|
|
||||||
const zone = tracy.initZone(@src(), .{ });
|
|
||||||
defer zone.deinit();
|
|
||||||
|
|
||||||
window.frame() catch |e| {
|
|
||||||
log.err("frame() failed: {}", .{e});
|
|
||||||
if (@errorReturnTrace()) |trace| {
|
|
||||||
std.debug.dumpStackTrace(trace.*);
|
|
||||||
}
|
|
||||||
sapp.requestQuit();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn cleanup() void {
|
|
||||||
const zone = tracy.initZone(@src(), .{ });
|
|
||||||
defer zone.deinit();
|
|
||||||
|
|
||||||
window.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn event(e_ptr: [*c]const sapp.Event) void {
|
|
||||||
const zone = tracy.initZone(@src(), .{ });
|
|
||||||
defer zone.deinit();
|
|
||||||
|
|
||||||
const consumed_event = window.event(e_ptr) catch |e| switch (e) {
|
|
||||||
error.EventQueueFull => blk: {
|
|
||||||
if (!event_queue_full_shown) {
|
|
||||||
log.warn("Event queue is full! Frame is taking too long to process", .{});
|
|
||||||
event_queue_full_shown = true;
|
|
||||||
}
|
|
||||||
break :blk false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (consumed_event) {
|
|
||||||
event_queue_full_shown = false;
|
|
||||||
sapp.consumeEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
var engine: Engine = undefined;
|
||||||
defer _ = debug_allocator.deinit();
|
try engine.run(.{});
|
||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
|
||||||
gpa = debug_allocator.allocator();
|
|
||||||
} else {
|
|
||||||
gpa = std.heap.smp_allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use tracy TracingAllocator
|
|
||||||
|
|
||||||
tracy.setThreadName("Main");
|
|
||||||
|
|
||||||
if (builtin.os.tag == .linux) {
|
|
||||||
var sa: std.posix.Sigaction = .{
|
|
||||||
.handler = .{ .handler = signalHandler },
|
|
||||||
.mask = std.posix.sigemptyset(),
|
|
||||||
.flags = std.posix.SA.RESTART,
|
|
||||||
};
|
|
||||||
std.posix.sigaction(std.posix.SIG.INT, &sa, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
sapp.run(.{
|
|
||||||
.init_cb = init,
|
|
||||||
.frame_cb = frame,
|
|
||||||
.cleanup_cb = cleanup,
|
|
||||||
.event_cb = event,
|
|
||||||
.width = 640,
|
|
||||||
.height = 480,
|
|
||||||
.icon = .{ .sokol_default = true },
|
|
||||||
.window_title = "Game",
|
|
||||||
.logger = .{ .func = Window.sokolLogCallback },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
551
src/window.zig
551
src/window.zig
@ -1,551 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
const sapp = sokol.app;
|
|
||||||
|
|
||||||
const Gfx = @import("./graphics.zig");
|
|
||||||
const tiled = @import("tiled");
|
|
||||||
|
|
||||||
const Math = @import("./math.zig");
|
|
||||||
const Vec2 = Math.Vec2;
|
|
||||||
const rgb = Math.rgb;
|
|
||||||
|
|
||||||
const Game = @import("./game.zig");
|
|
||||||
|
|
||||||
const Window = @This();
|
|
||||||
|
|
||||||
const log = std.log.scoped(.window);
|
|
||||||
|
|
||||||
pub const MouseButton = enum {
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
middle,
|
|
||||||
|
|
||||||
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?MouseButton {
|
|
||||||
return switch(mouse_button) {
|
|
||||||
.LEFT => MouseButton.left,
|
|
||||||
.RIGHT => MouseButton.right,
|
|
||||||
.MIDDLE => MouseButton.middle,
|
|
||||||
else => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 Event = union(enum) {
|
|
||||||
mouse_pressed: struct {
|
|
||||||
button: MouseButton,
|
|
||||||
position: Vec2,
|
|
||||||
},
|
|
||||||
mouse_released: struct {
|
|
||||||
button: MouseButton,
|
|
||||||
position: Vec2,
|
|
||||||
},
|
|
||||||
mouse_move: Vec2,
|
|
||||||
mouse_enter: Vec2,
|
|
||||||
mouse_leave,
|
|
||||||
mouse_scroll: Vec2,
|
|
||||||
key_pressed: struct {
|
|
||||||
code: KeyCode,
|
|
||||||
repeat: bool
|
|
||||||
},
|
|
||||||
key_released: KeyCode,
|
|
||||||
window_resize,
|
|
||||||
char: u21,
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Nanoseconds = i128;
|
|
||||||
|
|
||||||
gpa: Allocator,
|
|
||||||
events: std.ArrayList(Event),
|
|
||||||
mouse_inside: bool = false,
|
|
||||||
game: Game,
|
|
||||||
|
|
||||||
last_frame_at_ns: Nanoseconds,
|
|
||||||
frame_dt_ns: Nanoseconds,
|
|
||||||
time_ns: Nanoseconds,
|
|
||||||
|
|
||||||
down_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
|
||||||
pressed_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
|
||||||
released_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
|
||||||
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds) = .init(.{}),
|
|
||||||
|
|
||||||
pub fn init(self: *Window, gpa: Allocator) !void {
|
|
||||||
var events: std.ArrayList(Event) = .empty;
|
|
||||||
errdefer events.deinit(gpa);
|
|
||||||
try events.ensureTotalCapacityPrecise(gpa, 50);
|
|
||||||
|
|
||||||
try Gfx.init(.{
|
|
||||||
.allocator = gpa,
|
|
||||||
.logger = .{ .func = sokolLogCallback }
|
|
||||||
});
|
|
||||||
|
|
||||||
var game = try Game.init(gpa);
|
|
||||||
errdefer game.deinit();
|
|
||||||
|
|
||||||
self.* = Window{
|
|
||||||
.gpa = gpa,
|
|
||||||
.events = events,
|
|
||||||
.last_frame_at_ns = std.time.nanoTimestamp(),
|
|
||||||
.frame_dt_ns = 0,
|
|
||||||
.time_ns = 0,
|
|
||||||
.game = game
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Window) void {
|
|
||||||
const gpa = self.gpa;
|
|
||||||
|
|
||||||
self.game.deinit();
|
|
||||||
self.events.deinit(gpa);
|
|
||||||
|
|
||||||
Gfx.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn frame(self: *Window) !void {
|
|
||||||
const now = std.time.nanoTimestamp();
|
|
||||||
self.frame_dt_ns = now - self.last_frame_at_ns;
|
|
||||||
self.last_frame_at_ns = now;
|
|
||||||
self.time_ns += self.frame_dt_ns;
|
|
||||||
|
|
||||||
Gfx.beginFrame();
|
|
||||||
defer Gfx.endFrame();
|
|
||||||
|
|
||||||
self.pressed_keys = .initEmpty();
|
|
||||||
self.released_keys = .initEmpty();
|
|
||||||
for (self.events.items) |e| {
|
|
||||||
switch (e) {
|
|
||||||
.key_pressed => |opts| {
|
|
||||||
if (!opts.repeat) {
|
|
||||||
self.pressed_keys_at.put(opts.code, self.time_ns);
|
|
||||||
self.pressed_keys.insert(opts.code);
|
|
||||||
self.down_keys.insert(opts.code);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.key_released => |key_code| {
|
|
||||||
self.down_keys.remove(key_code);
|
|
||||||
self.released_keys.insert(key_code);
|
|
||||||
self.pressed_keys_at.remove(key_code);
|
|
||||||
},
|
|
||||||
.mouse_leave => {
|
|
||||||
var iter = self.down_keys.iterator();
|
|
||||||
while (iter.next()) |key_code| {
|
|
||||||
self.released_keys.insert(key_code);
|
|
||||||
}
|
|
||||||
self.down_keys = .initEmpty();
|
|
||||||
self.pressed_keys_at = .init(.{});
|
|
||||||
},
|
|
||||||
else => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.events.clearRetainingCapacity();
|
|
||||||
|
|
||||||
// TODO: Render to a lower resolution instead of scaling.
|
|
||||||
// To avoid pixel bleeding in spritesheet artifacts
|
|
||||||
const window_size: Vec2 = .init(sapp.widthf(), sapp.heightf());
|
|
||||||
const scale = @floor(@min(
|
|
||||||
window_size.x / self.game.canvas_size.x,
|
|
||||||
window_size.y / self.game.canvas_size.y,
|
|
||||||
));
|
|
||||||
|
|
||||||
var filler_size: Vec2 = Vec2.sub(window_size, self.game.canvas_size.multiplyScalar(scale)).multiplyScalar(0.5);
|
|
||||||
filler_size.x = @round(filler_size.x);
|
|
||||||
filler_size.y = @round(filler_size.y);
|
|
||||||
|
|
||||||
const input = self.game.getInput(self);
|
|
||||||
|
|
||||||
{
|
|
||||||
Gfx.pushTransform(filler_size, scale);
|
|
||||||
defer Gfx.popTransform();
|
|
||||||
|
|
||||||
try self.game.tick(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bg_color = rgb(0, 0, 0);
|
|
||||||
|
|
||||||
Gfx.drawRectangle(
|
|
||||||
.init(0, 0),
|
|
||||||
.init(window_size.x, filler_size.y),
|
|
||||||
bg_color
|
|
||||||
);
|
|
||||||
|
|
||||||
Gfx.drawRectangle(
|
|
||||||
.init(0, window_size.y - filler_size.y),
|
|
||||||
.init(window_size.x, filler_size.y),
|
|
||||||
bg_color
|
|
||||||
);
|
|
||||||
|
|
||||||
Gfx.drawRectangle(
|
|
||||||
.init(0, 0),
|
|
||||||
.init(filler_size.x, window_size.y),
|
|
||||||
bg_color
|
|
||||||
);
|
|
||||||
|
|
||||||
Gfx.drawRectangle(
|
|
||||||
.init(window_size.x - filler_size.x, 0),
|
|
||||||
.init(filler_size.x, window_size.y),
|
|
||||||
bg_color
|
|
||||||
);
|
|
||||||
|
|
||||||
try self.game.debug();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isKeyDown(self: *Window, key_code: KeyCode) bool {
|
|
||||||
return self.down_keys.contains(key_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getKeyDownDuration(self: *Window, 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 = self.time_ns - pressed_at_ns;
|
|
||||||
|
|
||||||
return @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_s;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isKeyPressed(self: *Window, key_code: KeyCode) bool {
|
|
||||||
return self.pressed_keys.contains(key_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isKeyReleased(self: *Window, key_code: KeyCode) bool {
|
|
||||||
return self.released_keys.contains(key_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getKeyState(self: *Window, 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(key_code)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn appendEvent(self: *Window, e: Event) !void {
|
|
||||||
self.events.appendBounded(e) catch return error.EventQueueFull;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event(self: *Window, e_ptr: [*c]const sapp.Event) !bool {
|
|
||||||
if (Gfx.event(e_ptr)) {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_leave = {}
|
|
||||||
});
|
|
||||||
self.mouse_inside = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const e = e_ptr.*;
|
|
||||||
blk: switch (e.type) {
|
|
||||||
.MOUSE_DOWN => {
|
|
||||||
const mouse_button = Window.MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
|
||||||
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_pressed = .{
|
|
||||||
.button = mouse_button,
|
|
||||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.MOUSE_UP => {
|
|
||||||
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
|
||||||
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_released = .{
|
|
||||||
.button = mouse_button,
|
|
||||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.MOUSE_MOVE => {
|
|
||||||
if (!self.mouse_inside) {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.mouse_inside = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.MOUSE_ENTER => {
|
|
||||||
if (!self.mouse_inside) {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.mouse_inside = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.RESIZED => {
|
|
||||||
if (self.mouse_inside) {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_leave = {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.window_resize = {}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.mouse_inside = false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.MOUSE_LEAVE => {
|
|
||||||
if (self.mouse_inside) {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_leave = {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.mouse_inside = false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.MOUSE_SCROLL => {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.KEY_DOWN => {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.key_pressed = .{
|
|
||||||
.code = @enumFromInt(@intFromEnum(e.key_code)),
|
|
||||||
.repeat = e.key_repeat
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.KEY_UP => {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.key_released = @enumFromInt(@intFromEnum(e.key_code))
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.CHAR => {
|
|
||||||
try self.appendEvent(Event{
|
|
||||||
.char = @intCast(e.char_code)
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
.QUIT_REQUESTED => {
|
|
||||||
// TODO: handle quit request. Maybe show confirmation window in certain cases.
|
|
||||||
},
|
|
||||||
else => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {
|
|
||||||
return std.mem.span(c_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sokolLogFmt(log_level: u32, comptime format: []const u8, args: anytype) void {
|
|
||||||
const log_sokol = std.log.scoped(.sokol);
|
|
||||||
|
|
||||||
if (log_level == 0) {
|
|
||||||
log_sokol.err(format, args);
|
|
||||||
} else if (log_level == 1) {
|
|
||||||
log_sokol.err(format, args);
|
|
||||||
} else if (log_level == 2) {
|
|
||||||
log_sokol.warn(format, args);
|
|
||||||
} else {
|
|
||||||
log_sokol.info(format, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [*c]const u8, line_nr: u32, filename: [*c]const u8, user_data: ?*anyopaque) callconv(.c) void {
|
|
||||||
_ = user_data;
|
|
||||||
|
|
||||||
if (filename != null) {
|
|
||||||
const format = "[{s}][id:{}] {s}:{}: {s}";
|
|
||||||
const args = .{
|
|
||||||
cStrToZig(tag orelse "-"),
|
|
||||||
log_item,
|
|
||||||
std.fs.path.basename(cStrToZig(filename orelse "-")),
|
|
||||||
line_nr,
|
|
||||||
cStrToZig(message orelse "")
|
|
||||||
};
|
|
||||||
|
|
||||||
sokolLogFmt(log_level, format, args);
|
|
||||||
} else {
|
|
||||||
const format = "[{s}][id:{}] {s}";
|
|
||||||
const args = .{
|
|
||||||
cStrToZig(tag orelse "-"),
|
|
||||||
log_item,
|
|
||||||
cStrToZig(message orelse "")
|
|
||||||
};
|
|
||||||
|
|
||||||
sokolLogFmt(log_level, format, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user