add key repeat

This commit is contained in:
Rokas Puzonas 2026-08-18 00:04:25 +03:00
parent d91fd0f4dc
commit a153a3f04a
3 changed files with 91 additions and 50 deletions

View File

@ -280,16 +280,16 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
const input = plt.input;
var dir: Vec2 = .init(0, 0);
if (input.isKeyPressed(.S)) {
if (input.isKeyPressedOrRepeat(.S)) {
dir.y += 1;
}
if (input.isKeyPressed(.W)) {
if (input.isKeyPressedOrRepeat(.W)) {
dir.y -= 1;
}
if (input.isKeyPressed(.D)) {
if (input.isKeyPressedOrRepeat(.D)) {
dir.x += 1;
}
if (input.isKeyPressed(.A)) {
if (input.isKeyPressedOrRepeat(.A)) {
dir.x -= 1;
}
if (dir.x != 0) {
@ -297,13 +297,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
}
self.player_pos = self.player_pos.add(dir);
if (input.isKeyPressed(.F)) {
Audio.stop(self.footstep_sound);
}
if (input.isKeyPressed(.E)) {
Audio.stop(self.footstep_sound);
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .loop = true, .bus = self.sfx_bus });
if (dir.x != 0 or dir.y != 0) {
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus });
}
var new_direction = self.player_direction;
@ -325,7 +320,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
const frames = self.player_sprites.getDirection(self.player_direction);
if (dir.x != 0 or dir.y != 0) {
self.animation_timer += plt.dt;
self.animation_timer += plt.input.delta_time;
const animatin_time = std.time.ns_per_s / 5;
while (self.animation_timer >= animatin_time) {
self.player_frame_index = (self.player_frame_index + 1) % @as(u32, @intCast(frames.len));
@ -354,11 +349,6 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
}
Gfx.drawSprite(frames[self.player_frame_index], self.player_pos.multiply(tile_size), tile_size, .white);
Gfx.drawText(self.roboto_font, .{
.pos = .init(300, 100),
.text = try std.fmt.allocPrint(plt.frame, "Hello, World! {:.0}", .{ plt.time() }),
.height = 64,
});
}
pub fn deinit(self: *App, plt: Platform.Deinit) void {

View File

@ -6,9 +6,16 @@ const sokol = @import("sokol");
const Math = @import("math");
const Vec2 = Math.Vec2;
const Nanoseconds = @import("./root.zig").Nanoseconds;
const Input = @This();
key_code_mapping: std.EnumMap(KeyCode, u21),
// TODO: Make this configurable from the app
const key_repeat_first: Nanoseconds = 0.4 * std.time.ns_per_s;
const key_repeat_delay: Nanoseconds = 0.2 * std.time.ns_per_s;
now: Nanoseconds,
delta_time: Nanoseconds,
window_size: Vec2,
focused: bool,
@ -18,8 +25,13 @@ mouse_position: ?Vec2,
mouse_delta: Vec2,
mouse_scroll: Vec2,
key_code_mapping: std.EnumMap(KeyCode, u21),
last_key_repeat_at: std.EnumMap(KeyCode, Nanoseconds),
pub fn init(window_size: Vec2) Input {
return Input{
.now = 0,
.delta_time = 0,
.key_code_mapping = .{},
.focused = false,
.keyboard = .empty,
@ -27,7 +39,8 @@ pub fn init(window_size: Vec2) Input {
.mouse_position = null,
.mouse_delta = .init(0, 0),
.mouse_scroll = .init(0, 0),
.window_size = window_size
.window_size = window_size,
.last_key_repeat_at = .{}
};
}
@ -40,7 +53,33 @@ pub fn isKeyReleased(self: Input, key: KeyCode) bool {
}
pub fn isKeyDown(self: Input, key: KeyCode) bool {
return self.keyboard.down.contains(key);
return self.keyboard.pressed_at.contains(key);
}
pub fn getKeyDown(self: Input, key: KeyCode) ?Nanoseconds {
return self.keyboard.getDown(key, self.now);
}
pub fn isKeyRepeat(self: *Input, key: KeyCode) bool {
const pressed_at = self.keyboard.pressed_at.get(key) orelse return false;
var next_repeat_at: Nanoseconds = undefined;
if (self.last_key_repeat_at.get(key)) |last_repeat_at| {
next_repeat_at = last_repeat_at + key_repeat_delay;
} else {
next_repeat_at = pressed_at + key_repeat_first;
}
if (self.now >= next_repeat_at) {
self.last_key_repeat_at.put(key, next_repeat_at);
return true;
} else {
return false;
}
}
pub fn isKeyPressedOrRepeat(self: *Input, key: KeyCode) bool {
return self.isKeyPressed(key) or self.isKeyRepeat(key);
}
pub fn isMousePressed(self: Input, button: MouseButton) bool {
@ -52,7 +91,11 @@ pub fn isMouseReleased(self: Input, button: MouseButton) bool {
}
pub fn isMouseDown(self: Input, button: MouseButton) bool {
return self.mouse_buttons.down.contains(button);
return self.mouse_buttons.pressed_at.contains(button);
}
pub fn getMouseDown(self: Input, button: MouseButton) ?Nanoseconds {
return self.mouse_buttons.getDown(button, self.now);
}
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
@ -212,28 +255,35 @@ fn KeyStateType(T: type) type {
return struct {
pressed: std.EnumSet(T),
released: std.EnumSet(T),
down: std.EnumSet(T),
pressed_at: std.EnumMap(T, Nanoseconds),
pub const empty = @This(){
.pressed = .empty,
.released = .empty,
.down = .empty,
.pressed_at = .{},
};
pub fn press(self: *@This(), key: T) void {
pub fn press(self: *@This(), key: T, now: Nanoseconds) void {
self.pressed.insert(key);
self.down.insert(key);
self.pressed_at.put(key, now);
}
pub fn release(self: *@This(), key: T) void {
self.released.insert(key);
self.down.remove(key);
self.pressed_at.remove(key);
}
pub fn getDown(self: @This(), key: T, now: Nanoseconds) ?Nanoseconds {
if (self.pressed_at.get(key)) |pressed_at| {
return now - pressed_at;
}
return null;
}
pub fn releaseAll(self: *@This()) void {
var iter = self.down.iterator();
while (iter.next()) |key| {
self.release(key);
var iter = self.pressed_at.iterator();
while (iter.next()) |e| {
self.release(e.key);
}
}
};

View File

@ -105,10 +105,11 @@ fn PlatformType(App: type) type {
_ = self.frame_arena.reset(.free_all); // TODO: make arena reset mode configurable
const now = Io.Timestamp.now(self.io, .awake);
const t = self.started_at.durationTo(now);
const dt = self.last_frame_at.durationTo(now);
self.last_frame_at = now;
self.input.now = @intCast(self.started_at.durationTo(now).nanoseconds);
self.input.delta_time = @intCast(self.last_frame_at.durationTo(now).nanoseconds);
if (self.input.keyboard.pressed.contains(.F4)) {
self.show_imgui = !self.show_imgui;
}
@ -126,8 +127,6 @@ fn PlatformType(App: type) type {
if (@hasDecl(App, "frame")) {
const plt = Frame{
.t = t.nanoseconds,
.dt = dt.nanoseconds,
.gpa = self.gpa,
.arena = self.arena.allocator(),
.io = self.io,
@ -195,7 +194,9 @@ fn PlatformType(App: type) type {
return false;
}
input.keyboard.press(key);
const now = self.started_at.durationTo(Io.Timestamp.now(self.io, .awake));
input.keyboard.press(key, @intCast(now.nanoseconds));
self.last_key_pressed = key;
},
.key_released => |key| {
@ -204,13 +205,16 @@ fn PlatformType(App: type) type {
}
input.keyboard.release(key);
input.last_key_repeat_at.remove(key);
},
.mouse_pressed => |button| {
if (input.mouse_position == null or input.isMouseDown(button)) {
return false;
}
input.mouse_buttons.press(button);
const now = self.started_at.durationTo(Io.Timestamp.now(self.io, .awake));
input.mouse_buttons.press(button, @intCast(now.nanoseconds));
},
.mouse_released => |button| {
if (input.mouse_position == null or !input.isMouseDown(button)) {
@ -274,8 +278,8 @@ fn PlatformType(App: type) type {
}
assert(input.mouse_position == null);
assert(input.keyboard.down.eql(.empty));
assert(input.mouse_buttons.down.eql(.empty));
assert(input.keyboard.pressed_at.count() == 0);
assert(input.mouse_buttons.pressed_at.count() == 0);
input.focused = false;
}
}
@ -306,13 +310,13 @@ fn PlatformType(App: type) type {
}
if (e == .unfocused) {
var key_iter = input.keyboard.down.iterator();
while (key_iter.next()) |key| {
self.appendEvent(.{ .key_released = key });
var key_iter = input.keyboard.pressed_at.iterator();
while (key_iter.next()) |key_pressed_at| {
self.appendEvent(.{ .key_released = key_pressed_at.key });
}
var mouse_buttons_iter = input.mouse_buttons.down.iterator();
while (mouse_buttons_iter.next()) |button| {
self.appendEvent(.{ .mouse_released = button });
var mouse_buttons_iter = input.mouse_buttons.pressed_at.iterator();
while (mouse_buttons_iter.next()) |key_pressed_at| {
self.appendEvent(.{ .mouse_released = key_pressed_at.key });
}
self.appendEvent(.{ .mouse_leave = {} });
}
@ -566,12 +570,9 @@ pub const Deinit = struct {
io: Io,
};
pub const Nanoseconds = i96;
pub const Nanoseconds = i64;
pub const Frame = struct {
t: Nanoseconds,
dt: Nanoseconds,
gpa: Allocator,
arena: Allocator,
frame: Allocator,
@ -580,16 +581,16 @@ pub const Frame = struct {
input: *Input,
input_events: []Input.Event,
fn nanosecondsToSeconds(nanoseconds: i96) f32 {
fn nanosecondsToSeconds(nanoseconds: Nanoseconds) f32 {
return @floatCast(@as(f64, @floatFromInt(nanoseconds)) / std.time.ns_per_s);
}
pub fn deltaTime(self: Frame) f32 {
return nanosecondsToSeconds(self.dt);
return nanosecondsToSeconds(self.input.delta_time);
}
pub fn time(self: Frame) f32 {
return nanosecondsToSeconds(self.t);
return nanosecondsToSeconds(self.input.now);
}
};