add gun with mouse control

This commit is contained in:
Rokas Puzonas 2026-01-29 22:01:56 +02:00
parent 2a55252942
commit 1220b36531
7 changed files with 224 additions and 101 deletions

View File

@ -7,6 +7,8 @@ const Engine = @import("./engine/root.zig");
const STBImage = @import("stb_image"); const STBImage = @import("stb_image");
const Gfx = Engine.Graphics; const Gfx = Engine.Graphics;
const Audio = Engine.Audio; const Audio = Engine.Audio;
const Vec2 = Engine.Vec2;
const Rect = Engine.Math.Rect;
const Assets = @This(); const Assets = @This();
@ -18,18 +20,34 @@ const FontName = enum {
const EnumArray = std.EnumArray(FontName, Gfx.Font.Id); const EnumArray = std.EnumArray(FontName, Gfx.Font.Id);
}; };
pub const Tilemap = struct {
texture: Gfx.TextureId,
tile_size: Engine.Vec2,
pub fn getTileUV(self: Tilemap, tile_x: f32, tile_y: f32) Rect {
const texture_info = Engine.Graphics.getTextureInfo(self.texture);
const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height);
return .{
.pos = Vec2.init(tile_x, tile_y).multiply(self.tile_size).divide(tilemap_size),
.size = self.tile_size.divide(tilemap_size),
};
}
};
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
font_id: FontName.EnumArray, font_id: FontName.EnumArray,
wood01: Audio.Data.Id, wood01: Audio.Data.Id,
map: tiled.Tilemap, map: tiled.Tilemap,
tilesets: tiled.Tileset.List, tilesets: tiled.Tileset.List,
tileset_texture: Gfx.TextureId,
players_texture: Gfx.TextureId,
tile_size: Engine.Vec2,
player_size: Engine.Vec2,
move_sound: []Audio.Data.Id, move_sound: []Audio.Data.Id,
terrain_tilemap: Tilemap,
players_tilemap: Tilemap,
weapons_tilemap: Tilemap,
pub fn init(gpa: std.mem.Allocator) !Assets { pub fn init(gpa: std.mem.Allocator) !Assets {
var arena = std.heap.ArenaAllocator.init(gpa); var arena = std.heap.ArenaAllocator.init(gpa);
errdefer arena.deinit(); errdefer arena.deinit();
@ -88,6 +106,16 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
} }
}); });
const weapons_tileset = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Weapons/tilemap_packed.png"));
defer weapons_tileset.deinit();
const weapons_texture = try Gfx.addTexture(&.{
.{
.width = weapons_tileset.width,
.height = weapons_tileset.height,
.rgba = weapons_tileset.rgba8_pixels
}
});
const move_c = try Audio.load(.{ const move_c = try Audio.load(.{
.data = @embedFile("assets/kenney_desert-shooter-pack_1.0/Sounds/move-c.ogg"), .data = @embedFile("assets/kenney_desert-shooter-pack_1.0/Sounds/move-c.ogg"),
.format = .vorbis, .format = .vorbis,
@ -104,11 +132,19 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
.wood01 = wood01, .wood01 = wood01,
.map = map, .map = map,
.tilesets = tilesets, .tilesets = tilesets,
.tileset_texture = tileset_texture, .move_sound = move_sound,
.tile_size = .init(16, 16), .terrain_tilemap = .{
.players_texture = players_texture, .texture = tileset_texture,
.player_size = .init(24, 24), .tile_size = .initFromInt(u32, tileset.tile_width, tileset.tile_height)
.move_sound = move_sound },
.players_tilemap = .{
.texture = players_texture,
.tile_size = .init(24, 24)
},
.weapons_tilemap = .{
.texture = weapons_texture,
.tile_size = .init(24, 24)
},
}; };
} }

View File

@ -14,6 +14,7 @@ const GraphicsSystem = @import("./graphics.zig");
const TextureId = GraphicsSystem.TextureId; const TextureId = GraphicsSystem.TextureId;
const GraphicsCommand = GraphicsSystem.Command; const GraphicsCommand = GraphicsSystem.Command;
const Font = GraphicsSystem.Font; const Font = GraphicsSystem.Font;
const Sprite = GraphicsSystem.Sprite;
const Math = @import("./math.zig"); const Math = @import("./math.zig");
const Rect = Math.Rect; const Rect = Math.Rect;
@ -110,6 +111,7 @@ audio: Audio,
graphics: Graphics, graphics: Graphics,
show_debug: bool, show_debug: bool,
hide_cursor: bool,
pub fn init(self: *Frame, gpa: std.mem.Allocator) void { pub fn init(self: *Frame, gpa: std.mem.Allocator) void {
self.* = Frame{ self.* = Frame{
@ -119,7 +121,8 @@ pub fn init(self: *Frame, gpa: std.mem.Allocator) void {
.input = .empty, .input = .empty,
.audio = .empty, .audio = .empty,
.graphics = .empty, .graphics = .empty,
.show_debug = false .show_debug = false,
.hide_cursor = false
}; };
} }
@ -131,6 +134,10 @@ pub fn deltaTime(self: Frame) f32 {
return @as(f32, @floatFromInt(self.dt_ns)) / std.time.ns_per_s; return @as(f32, @floatFromInt(self.dt_ns)) / std.time.ns_per_s;
} }
pub fn time(self: Frame) f64 {
return @as(f64, @floatFromInt(self.time_ns)) / std.time.ns_per_s;
}
pub fn isKeyDown(self: Frame, key_code: KeyCode) bool { pub fn isKeyDown(self: Frame, key_code: KeyCode) bool {
return self.input.down_keys.contains(key_code); return self.input.down_keys.contains(key_code);
} }
@ -214,15 +221,6 @@ pub fn popScissor(self: *Frame) void {
}); });
} }
const DrawRectangleOptions = struct {
rect: Rect,
color: Vec4,
texture: ?struct {
id: TextureId,
uv: Rect,
} = null
};
pub fn drawRectangle(self: *Frame, opts: GraphicsCommand.DrawRectangle) void { pub fn drawRectangle(self: *Frame, opts: GraphicsCommand.DrawRectangle) void {
self.pushGraphicsCommand(.{ .draw_rectangle = opts }); self.pushGraphicsCommand(.{ .draw_rectangle = opts });
} }

View File

@ -45,10 +45,9 @@ pub const Command = union(enum) {
pub const DrawRectangle = struct { pub const DrawRectangle = struct {
rect: Rect, rect: Rect,
color: Vec4, color: Vec4,
texture: ?struct { sprite: ?Sprite = null,
id: TextureId, rotation: f32 = 0,
uv: Rect, origin: Vec2 = .init(0, 0),
} = null
}; };
set_scissor: Rect, set_scissor: Rect,
@ -93,6 +92,11 @@ const Texture = struct {
pub const TextureId = Texture.Id; pub const TextureId = Texture.Id;
pub const TextureInfo = Texture.Info; pub const TextureInfo = Texture.Info;
pub const Sprite = struct {
texture: TextureId,
uv: Rect,
};
var gpa: std.mem.Allocator = undefined; var gpa: std.mem.Allocator = undefined;
var main_pipeline: sgl.Pipeline = .{}; var main_pipeline: sgl.Pipeline = .{};
@ -352,38 +356,38 @@ fn drawRectangle(opts: Command.DrawRectangle) void {
const pos = opts.rect.pos; const pos = opts.rect.pos;
const size = opts.rect.size; const size = opts.rect.size;
const top_left = pos; const top_left = Vec2.init(0, 0).rotateAround(opts.rotation, opts.origin);
const top_right = pos.add(.{ .x = size.x, .y = 0 }); const top_right = Vec2.init(size.x, 0).rotateAround(opts.rotation, opts.origin);
const bottom_right = pos.add(size); const bottom_right = size.rotateAround(opts.rotation, opts.origin);
const bottom_left = pos.add(.{ .x = 0, .y = size.y }); const bottom_left = Vec2.init(0, size.y).rotateAround(opts.rotation, opts.origin);
if (opts.texture) |texture| { if (opts.sprite) |sprite| {
const uv = texture.uv; const uv = sprite.uv;
const quad = [4]Vertex{ const quad = [4]Vertex{
.{ .{
.pos = top_left, .pos = pos.add(top_left),
.uv = .init(uv.left(), uv.top()) .uv = .init(uv.left(), uv.top())
}, },
.{ .{
.pos = top_right, .pos = pos.add(top_right),
.uv = .init(uv.right(), uv.top()) .uv = .init(uv.right(), uv.top())
}, },
.{ .{
.pos = bottom_right, .pos = pos.add(bottom_right),
.uv = .init(uv.right(), uv.bottom()) .uv = .init(uv.right(), uv.bottom())
}, },
.{ .{
.pos = bottom_left, .pos = pos.add(bottom_left),
.uv = .init(uv.left(), uv.bottom()) .uv = .init(uv.left(), uv.bottom())
} }
}; };
drawQuad(quad, opts.color, texture.id); drawQuad(quad, opts.color, sprite.texture);
} else { } else {
const quad = .{ const quad = .{
top_left, pos.add(top_left),
top_right, pos.add(top_right),
bottom_right, pos.add(bottom_right),
bottom_left pos.add(bottom_left)
}; };
drawQuadNoUVs(quad, opts.color); drawQuadNoUVs(quad, opts.color);
} }

View File

@ -63,6 +63,21 @@ pub const Vec2 = extern struct {
return Vec2.init(-self.y, self.x); return Vec2.init(-self.y, self.x);
} }
pub fn rotate(self: Vec2, angle: f32) Vec2 {
return init(
@cos(angle) * self.x - @sin(angle) * self.y,
@sin(angle) * self.x + @cos(angle) * self.y,
);
}
pub fn rotateAround(self: Vec2, angle: f32, origin: Vec2) Vec2 {
return self.sub(origin).rotate(angle).add(origin);
}
pub fn getAngle(self: Vec2) f32 {
return std.math.atan2(self.y, self.x);
}
pub fn flip(self: Vec2) Vec2 { pub fn flip(self: Vec2) Vec2 {
return Vec2.init(-self.x, -self.y); return Vec2.init(-self.x, -self.y);
} }
@ -120,7 +135,10 @@ pub const Vec2 = extern struct {
pub fn limitLength(self: Vec2, max_length: f32) Vec2 { pub fn limitLength(self: Vec2, max_length: f32) Vec2 {
const self_length = self.length(); const self_length = self.length();
if (self_length > max_length) { if (self_length > max_length) {
return Vec2.init(self.x / self_length * max_length, self.y / self_length * max_length); if (self_length == 0) {
return Vec2.init(0, 0);
}
return self.divideScalar(self_length / max_length);
} else { } else {
return self; return self;
} }
@ -131,7 +149,7 @@ pub const Vec2 = extern struct {
if (self_length == 0) { if (self_length == 0) {
return Vec2.init(0, 0); return Vec2.init(0, 0);
} }
return Vec2.init(self.x / self_length, self.y / self_length); return self.divideScalar(self_length);
} }
pub fn initScalar(value: f32) Vec2 { pub fn initScalar(value: f32) Vec2 {

View File

@ -39,6 +39,8 @@ game: Game,
assets: Assets, assets: Assets,
frame: Frame, frame: Frame,
canvas_size: ?Vec2 = null,
const RunOptions = struct { const RunOptions = struct {
window_title: [*:0]const u8 = "Game", window_title: [*:0]const u8 = "Game",
window_width: u31 = 640, window_width: u31 = 640,
@ -161,6 +163,16 @@ fn sokolFrame(self: *Engine) !void {
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf()); const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
var revert_mouse_position: ?Vec2 = null;
if (self.canvas_size) |canvas_size| {
if (self.frame.input.mouse.position) |mouse| {
const transform = ScreenScalar.init(screen_size, canvas_size);
revert_mouse_position = mouse;
self.frame.input.mouse.position = mouse.sub(transform.translation).divideScalar(transform.scale);
}
}
{ {
_ = frame.arena.reset(.retain_capacity); _ = frame.arena.reset(.retain_capacity);
const arena = frame.arena.allocator(); const arena = frame.arena.allocator();
@ -179,6 +191,7 @@ fn sokolFrame(self: *Engine) !void {
frame.time_ns = time_passed; frame.time_ns = time_passed;
frame.dt_ns = time_passed - self.last_frame_at; frame.dt_ns = time_passed - self.last_frame_at;
frame.hide_cursor = false;
try self.game.tick(&self.frame); try self.game.tick(&self.frame);
@ -186,15 +199,24 @@ fn sokolFrame(self: *Engine) !void {
frame.input.released_keys = .initEmpty(); frame.input.released_keys = .initEmpty();
} }
if (self.frame.graphics.canvas_size) |canvas_size| { if (self.canvas_size) |canvas_size| {
_ = ScreenScalar.apply( const transform = ScreenScalar.init(
&self.frame,
screen_size, screen_size,
canvas_size, canvas_size
rgb(0, 0, 0), );
transform.apply(
screen_size,
&self.frame,
rgb(0, 0, 0)
); );
} }
sapp.showMouse(!self.frame.hide_cursor);
// Canvas size modification must always be applied a frame later.
// So that mouse coordinate transformations are consistent.
self.canvas_size = self.frame.graphics.canvas_size;
{ {
Gfx.beginFrame(); Gfx.beginFrame();
defer Gfx.endFrame(frame.graphics.clear_color); defer Gfx.endFrame(frame.graphics.clear_color);
@ -210,6 +232,10 @@ fn sokolFrame(self: *Engine) !void {
for (frame.audio.commands.items) |command| { for (frame.audio.commands.items) |command| {
try Audio.mixer.commands.push(command); try Audio.mixer.commands.push(command);
} }
if (revert_mouse_position) |pos| {
self.frame.input.mouse.position = pos;
}
} }
fn showDebugWindow(frame: *Frame) !void { fn showDebugWindow(frame: *Frame) !void {

View File

@ -16,12 +16,7 @@ const ScreenScalar = @This();
translation: Vec2, translation: Vec2,
scale: f32, scale: f32,
pub fn apply( pub fn init(window_size: Vec2, canvas_size: Vec2) ScreenScalar {
frame: *Frame,
window_size: Vec2,
canvas_size: Vec2,
color: Vec4
) ScreenScalar {
// TODO: Render to a lower resolution instead of scaling. // TODO: Render to a lower resolution instead of scaling.
// To avoid pixel bleeding in spritesheet artifacts // To avoid pixel bleeding in spritesheet artifacts
const scale = @floor(@min( const scale = @floor(@min(
@ -33,6 +28,16 @@ pub fn apply(
translation.x = @round(translation.x); translation.x = @round(translation.x);
translation.y = @round(translation.y); translation.y = @round(translation.y);
return ScreenScalar{
.translation = translation,
.scale = scale
};
}
pub fn apply(self: ScreenScalar, window_size: Vec2, frame: *Frame, color: Vec4) void {
const scale = self.scale;
const translation = self.translation;
frame.prependGraphicsCommand(.{ frame.prependGraphicsCommand(.{
.push_transformation = .{ .push_transformation = .{
.translation = translation, .translation = translation,
@ -72,9 +77,4 @@ pub fn apply(
}, },
.color = color .color = color
}); });
return ScreenScalar{
.translation = translation,
.scale = scale
};
} }

View File

@ -1,15 +1,19 @@
const std = @import("std"); 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 clamp = std.math.clamp;
const Assets = @import("./assets.zig"); const Assets = @import("./assets.zig");
const Tilemap = Assets.Tilemap;
const Engine = @import("./engine/root.zig"); const Engine = @import("./engine/root.zig");
const Nanoseconds = Engine.Nanoseconds; const Nanoseconds = Engine.Nanoseconds;
const imgui = Engine.imgui; const imgui = Engine.imgui;
const Vec2 = Engine.Vec2; const Vec2 = Engine.Vec2;
const Vec4 = Engine.Math.Vec4;
const Rect = Engine.Math.Rect; const Rect = Engine.Math.Rect;
const rgb = Engine.Math.rgb; const rgb = Engine.Math.rgb;
const rgba = Engine.Math.rgba;
const Range = Engine.Math.Range; const Range = Engine.Math.Range;
const TextureId = Engine.Graphics.TextureId; const TextureId = Engine.Graphics.TextureId;
const AudioId = Engine.Audio.Data.Id; const AudioId = Engine.Audio.Data.Id;
@ -99,6 +103,7 @@ player: Vec2,
player_anim_state: Animation.State = .default, player_anim_state: Animation.State = .default,
last_faced_left: bool = false, last_faced_left: bool = false,
player_walk_sound: AudioBundle = .empty, player_walk_sound: AudioBundle = .empty,
hand_offset: Vec2 = .zero,
player_anim: Animation, player_anim: Animation,
@ -106,18 +111,15 @@ pub fn init(gpa: Allocator, seed: u64, assets: *Assets) !Game {
var arena = std.heap.ArenaAllocator.init(gpa); var arena = std.heap.ArenaAllocator.init(gpa);
errdefer arena.deinit(); errdefer arena.deinit();
const texture_info = Engine.Graphics.getTextureInfo(assets.players_texture);
const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height);
const tile_size = assets.player_size;
const player_anim = Animation{ const player_anim = Animation{
.texture = assets.players_texture, .texture = assets.players_tilemap.texture,
.frames = try arena.allocator().dupe(Animation.Frame, &.{ .frames = try arena.allocator().dupe(Animation.Frame, &.{
.{ .{
.uv = getUVFromTilemap(tilemap_size, tile_size, 0, 0), .uv = assets.players_tilemap.getTileUV(0, 0),
.duration = 0.1, .duration = 0.1,
}, },
.{ .{
.uv = getUVFromTilemap(tilemap_size, tile_size, 1, 0), .uv = assets.players_tilemap.getTileUV(1, 0),
.duration = 0.2, .duration = 0.2,
} }
}), }),
@ -157,24 +159,37 @@ fn findSpawnpoint(assets: *Assets) ?Vec2 {
return null; return null;
} }
fn getUVFromTilemap(tilemap_size: Vec2, tile_size: Vec2, tile_x: f32, tile_y: f32) Rect { const DrawTileOptions = struct {
return .{ pos: Vec2,
.pos = Vec2.init(tile_x, tile_y).multiply(tile_size).divide(tilemap_size), scale: Vec2 = .init(1, 1),
.size = tile_size.divide(tilemap_size), color: Vec4 = rgb(255, 255, 255),
}; rotation: f32 = 0,
} origin: Vec2 = .init(0, 0),
fn getUVFromTilemapByID(tilemap_size: Vec2, tile_size: Vec2, tile_id: u32) Rect { tilemap: Tilemap,
const tile_id_f32: f32 = @floatFromInt(tile_id); tile: Vec2
const width_in_tiles = tilemap_size.x / tile_size.x; };
const tile_x = @rem(tile_id_f32, width_in_tiles);
const tile_y = @divFloor(tile_id_f32, width_in_tiles); fn drawTile(frame: *Engine.Frame, opts: DrawTileOptions) void {
return getUVFromTilemap(tilemap_size, tile_size, tile_x, tile_y); frame.drawRectangle(.{
.rect = .{
.pos = opts.pos,
.size = opts.tilemap.tile_size.multiply(opts.scale)
},
.color = opts.color,
.rotation = opts.rotation,
.origin = opts.origin,
.sprite = .{
.texture = opts.tilemap.texture,
.uv = opts.tilemap.getTileUV(opts.tile.x, opts.tile.y)
}
});
} }
fn drawTilemap(self: *Game, frame: *Engine.Frame) void { fn drawTilemap(self: *Game, frame: *Engine.Frame) void {
const texture = self.assets.tileset_texture; const tilemap = self.assets.terrain_tilemap;
const texture_info = Engine.Graphics.getTextureInfo(texture); const texture_info = Engine.Graphics.getTextureInfo(tilemap.texture);
const tilemap_size = Vec2.initFromInt(u32,texture_info.width, texture_info.height);
const map = self.assets.map; const map = self.assets.map;
@ -193,19 +208,15 @@ fn drawTilemap(self: *Game, frame: *Engine.Frame) void {
const tile_gid = tile_layer.get(x, y) orelse continue; const tile_gid = tile_layer.get(x, y) orelse continue;
const tile = map.getTile(self.assets.tilesets, tile_gid) orelse continue; const tile = map.getTile(self.assets.tilesets, tile_gid) orelse continue;
const tilemap_size = Vec2.initFromInt(u32,texture_info.width, texture_info.height); const tile_id_f32: f32 = @floatFromInt(tile.id);
const tile_size = Vec2.initFromInt(u32, tile.tileset.tile_width, tile.tileset.tile_height); const width_in_tiles = tilemap_size.x / tilemap.tile_size.x;
const tile_x = @rem(tile_id_f32, width_in_tiles);
const tile_y = @divFloor(tile_id_f32, width_in_tiles);
frame.drawRectangle(.{ drawTile(frame, .{
.rect = Rect{ .pos = Vec2.initFromInt(i32, x, y).multiply(tilemap.tile_size),
.pos = Vec2.initFromInt(i32, x, y).multiply(tile_size), .tilemap = tilemap,
.size = tile_size .tile = .init(tile_x, tile_y)
},
.color = rgb(255, 255, 255),
.texture = .{
.id = self.assets.tileset_texture,
.uv = getUVFromTilemapByID(tilemap_size, tile_size, tile.id)
}
}); });
} }
} }
@ -222,10 +233,13 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
frame.show_debug = !frame.show_debug; frame.show_debug = !frame.show_debug;
} }
frame.pushTransform( frame.drawRectangle(.{
canvas_size.divideScalar(2).sub(self.player), .rect = .init(0, 0, canvas_size.x, canvas_size.y),
.init(1, 1) .color = rgb(20, 20, 20)
); });
const camera_offset = canvas_size.divideScalar(2).sub(self.player);
frame.pushTransform(camera_offset, .init(1, 1));
defer frame.popTransform(); defer frame.popTransform();
var dir = Vec2.init(0, 0); var dir = Vec2.init(0, 0);
@ -261,18 +275,13 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
self.drawTilemap(frame); self.drawTilemap(frame);
frame.drawRectangle(.{
.rect = .init(0, 0, canvas_size.x, canvas_size.y),
.color = rgb(20, 20, 20)
});
if (dir.x < 0) { if (dir.x < 0) {
self.last_faced_left = true; self.last_faced_left = true;
} else if (dir.x > 0) { } else if (dir.x > 0) {
self.last_faced_left = false; self.last_faced_left = false;
} }
var size = self.assets.player_size; var size = self.assets.players_tilemap.tile_size;
if (self.last_faced_left) { if (self.last_faced_left) {
size.x *= -1; size.x *= -1;
} }
@ -282,15 +291,47 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
.size = size, .size = size,
}, },
.color = rgb(255, 255, 255), .color = rgb(255, 255, 255),
.texture = .{ .sprite = .{
.id = self.player_anim.texture, .texture = self.player_anim.texture,
.uv = self.player_anim.frames[self.player_anim_state.frame_index].uv .uv = self.player_anim.frames[self.player_anim_state.frame_index].uv
} }
}); });
const max_hand_length = 32;
if (frame.input.mouse.position) |mouse_screen| {
const mouse = mouse_screen.sub(camera_offset);
const player_to_mouse = mouse.sub(self.player);
self.hand_offset = mouse.sub(self.player).limitLength(max_hand_length);
const opacity = clamp((player_to_mouse.length() - max_hand_length) / 16, 0, 1);
drawTile(frame, .{
.pos = mouse.sub(self.assets.weapons_tilemap.tile_size.multiplyScalar(0.5)),
.tilemap = self.assets.weapons_tilemap,
.color = rgba(255, 255, 255, opacity),
.tile = .init(4, 2)
});
frame.hide_cursor = true;
}
const hand = self.player.add(self.hand_offset);
var hand_flip_x: f32 = 1;
if (self.hand_offset.x < 0) {
hand_flip_x *= -1;
}
const hand_scale = Vec2.init(1, hand_flip_x);
const weapon_size = self.assets.weapons_tilemap.tile_size;
drawTile(frame, .{
.pos = hand.add(weapon_size.multiplyScalar(-0.5).multiply(hand_scale)),
.scale = hand_scale,
.tilemap = self.assets.weapons_tilemap,
.tile = .init(0, 0),
.origin = weapon_size.multiplyScalar(0.5).multiply(hand_scale),
.rotation = self.hand_offset.getAngle()
});
frame.drawText(self.player, "Player", .{ frame.drawText(self.player, "Player", .{
.font = regular_font, .font = regular_font,
.size = 1 .size = 16
}); });
} }