implement asset loading from bundle
This commit is contained in:
parent
6b48af7ea9
commit
483daaf91e
@ -53,33 +53,60 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
|
||||
}
|
||||
|
||||
const engine_lib = b.createModule(.{
|
||||
.root_source_file = b.path("src/lib/root.zig")
|
||||
.root_source_file = b.path("src/game_lib/root.zig")
|
||||
});
|
||||
engine_lib.addOptions("build_options", build_options);
|
||||
opts.root_module.addImport("engine", engine_lib);
|
||||
|
||||
const runtime_module = createRuntimeModule(b, .{
|
||||
const dep_stb = b.dependency("stb", .{});
|
||||
engine_lib.addImport("stb_image", dep_stb.module("stb_image"));
|
||||
|
||||
opts.root_module.addImport("engine_lib", engine_lib);
|
||||
|
||||
const engine_module = createEngineModule(b, .{
|
||||
.target = opts.target,
|
||||
.optimize = opts.optimize,
|
||||
.has_tracy = has_tracy,
|
||||
.has_imgui = has_imgui
|
||||
});
|
||||
runtime_module.root_module.addImport("lib", engine_lib);
|
||||
engine_module.root_module.addImport("engine_lib", engine_lib);
|
||||
if (statically_linked) {
|
||||
runtime_module.root_module.addImport("game", opts.root_module);
|
||||
engine_module.root_module.addImport("game", opts.root_module);
|
||||
}
|
||||
|
||||
const assset_bundler_tool = buildAssetBundler(
|
||||
b, b.graph.host, .ReleaseSafe,
|
||||
engine_module.root_module,
|
||||
opts.root_module
|
||||
);
|
||||
|
||||
const asset_bundler_step = b.addRunArtifact(assset_bundler_tool);
|
||||
asset_bundler_step.addDirectoryArg(opts.asset_dir);
|
||||
const assets_bundle_file = asset_bundler_step.addOutputFileArg("assets.bin");
|
||||
|
||||
const runtime_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/runtime/main.zig"),
|
||||
.target = opts.target,
|
||||
.optimize = opts.optimize,
|
||||
});
|
||||
runtime_mod.addImport("engine", engine_module.root_module);
|
||||
if (!load_assets_at_runtime) {
|
||||
// TODO: Add compression on asset bundle
|
||||
runtime_mod.addAnonymousImport("asset_bundle", .{
|
||||
.root_source_file = assets_bundle_file
|
||||
});
|
||||
}
|
||||
|
||||
var run_cmd_step: *std.Build.Step = undefined;
|
||||
if (isWasm) {
|
||||
const build_step = buildWasm(b, .{
|
||||
.name = "index",
|
||||
.root_module = runtime_module.root_module,
|
||||
.dep_sokol = runtime_module.dep_sokol,
|
||||
.root_module = runtime_mod,
|
||||
.dep_sokol = engine_module.dep_sokol,
|
||||
.outer_builder = outer_builder
|
||||
});
|
||||
outer_builder.getInstallStep().dependOn(build_step);
|
||||
|
||||
const dep_sokol = runtime_module.dep_sokol;
|
||||
const dep_sokol = engine_module.dep_sokol;
|
||||
const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
|
||||
const emrun_step = sokol.emRunStep(outer_builder, .{ .name = "index", .emsdk = dep_emsdk });
|
||||
emrun_step.step.dependOn(build_step);
|
||||
@ -87,10 +114,10 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
|
||||
|
||||
// TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload
|
||||
} else {
|
||||
runtime_module.root_module.link_libc = true;
|
||||
runtime_mod.link_libc = true;
|
||||
const exe = buildNative(b, .{
|
||||
.name = opts.name,
|
||||
.root_module = runtime_module.root_module,
|
||||
.root_module = runtime_mod,
|
||||
.win32_has_console = opts.win32_has_console,
|
||||
.win32_png_icon = opts.win32_png_icon
|
||||
});
|
||||
@ -123,9 +150,15 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
|
||||
|
||||
const run_step = outer_builder.step("run", "Run game");
|
||||
run_step.dependOn(run_cmd_step);
|
||||
|
||||
{
|
||||
const install_assets_bin = outer_builder.addInstallFileWithDir(assets_bundle_file, .bin, "assets.bin");
|
||||
const assets_step = outer_builder.step("assets", "Build assets file");
|
||||
assets_step.dependOn(&install_assets_bin.step);
|
||||
}
|
||||
}
|
||||
|
||||
const RuntimeModule = struct {
|
||||
const EngineModule = struct {
|
||||
root_module: *std.Build.Module,
|
||||
dep_sokol: *std.Build.Dependency,
|
||||
|
||||
@ -138,9 +171,9 @@ const RuntimeModule = struct {
|
||||
};
|
||||
};
|
||||
|
||||
fn createRuntimeModule(b: *std.Build, opts: RuntimeModule.Options) RuntimeModule {
|
||||
fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule {
|
||||
const mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/runtime/main.zig"),
|
||||
.root_source_file = b.path("src/engine/root.zig"),
|
||||
.target = opts.target,
|
||||
.optimize = opts.optimize,
|
||||
.link_libc = true
|
||||
@ -199,7 +232,7 @@ fn createRuntimeModule(b: *std.Build, opts: RuntimeModule.Options) RuntimeModule
|
||||
|
||||
mod.addIncludePath(dep_sokol_c.path("util"));
|
||||
mod.addCSourceFile(.{
|
||||
.file = b.path("src/runtime/fontstash/sokol_fontstash_impl.c"),
|
||||
.file = b.path("src/engine/fontstash/sokol_fontstash_impl.c"),
|
||||
.flags = cflags.items
|
||||
});
|
||||
}
|
||||
@ -216,12 +249,36 @@ fn createRuntimeModule(b: *std.Build, opts: RuntimeModule.Options) RuntimeModule
|
||||
// // TODO: Define buid config for wasm
|
||||
// }
|
||||
|
||||
return RuntimeModule{
|
||||
return EngineModule{
|
||||
.root_module = mod,
|
||||
.dep_sokol = dep_sokol
|
||||
};
|
||||
}
|
||||
|
||||
fn buildAssetBundler(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
engine: *std.Build.Module,
|
||||
game: *std.Build.Module
|
||||
) *std.Build.Step.Compile {
|
||||
const mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("tools/asset-bundler.zig"),
|
||||
});
|
||||
mod.addImport("engine", engine);
|
||||
mod.addImport("game", game);
|
||||
// mod.addImport("asset_bundle", b.createModule(.{
|
||||
// .root_source_file = b.path("src/runtime/asset_bundle.zig")
|
||||
// }));
|
||||
|
||||
return b.addExecutable(.{
|
||||
.name = "asset-bundler",
|
||||
.root_module = mod,
|
||||
});
|
||||
}
|
||||
|
||||
fn buildPngToIconTool(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
@ -310,7 +367,7 @@ fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step {
|
||||
.use_webgl2 = true,
|
||||
.use_emmalloc = true,
|
||||
.use_filesystem = false,
|
||||
.shell_file_path = b.path("src/runtime/shell.html"),
|
||||
.shell_file_path = b.path("src/engine/shell.html"),
|
||||
}) catch unreachable;
|
||||
|
||||
return &link_step.step;
|
||||
|
||||
166
engine/src/engine/asset_bundle.zig
Normal file
166
engine/src/engine/asset_bundle.zig
Normal file
@ -0,0 +1,166 @@
|
||||
const std = @import("std");
|
||||
const Lib = @import("engine_lib");
|
||||
|
||||
const AssetBundle = @This();
|
||||
|
||||
fonts: std.ArrayList(Font),
|
||||
sounds: std.ArrayList(Sound),
|
||||
textures: std.ArrayList(Texture),
|
||||
data: std.ArrayList(u8),
|
||||
|
||||
const DataSegment = extern struct {
|
||||
offset: u64,
|
||||
len: u64,
|
||||
};
|
||||
|
||||
const Font = extern struct {
|
||||
asset_id: Lib.Asset.Id,
|
||||
data: DataSegment
|
||||
};
|
||||
|
||||
const Sound = extern struct {
|
||||
asset_id: Lib.Asset.Id,
|
||||
data: DataSegment
|
||||
};
|
||||
|
||||
const Texture = extern struct {
|
||||
asset_id: Lib.Asset.Id,
|
||||
info: Lib.Texture.Info,
|
||||
data: DataSegment
|
||||
};
|
||||
|
||||
const Header = extern struct {
|
||||
magic: Magic,
|
||||
fonts_len: u32,
|
||||
sounds_len: u32,
|
||||
textures_len: u32,
|
||||
data_len: u32,
|
||||
|
||||
pub const Magic = enum(u32) {
|
||||
v1 = 0x961C8C65,
|
||||
_,
|
||||
};
|
||||
};
|
||||
|
||||
const endianness: std.builtin.Endian = .little;
|
||||
|
||||
pub fn init() AssetBundle {
|
||||
return AssetBundle{
|
||||
.fonts = .empty,
|
||||
.sounds = .empty,
|
||||
.textures = .empty,
|
||||
.data = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AssetBundle, gpa: std.mem.Allocator) void {
|
||||
self.sounds.deinit(gpa);
|
||||
self.textures.deinit(gpa);
|
||||
self.fonts.deinit(gpa);
|
||||
self.data.deinit(gpa);
|
||||
}
|
||||
|
||||
pub fn getData(self: AssetBundle, data: DataSegment) []const u8 {
|
||||
return self.data.items[data.offset..][0..data.len];
|
||||
}
|
||||
|
||||
fn insertData(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
data: []const u8
|
||||
) !DataSegment {
|
||||
const data_offset = self.data.items.len;
|
||||
try self.data.appendSlice(gpa, data);
|
||||
|
||||
return DataSegment{
|
||||
.offset = data_offset,
|
||||
.len = data.len
|
||||
};
|
||||
}
|
||||
|
||||
pub fn appendSound(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
id: Lib.Asset.Id,
|
||||
data: []const u8
|
||||
) !void {
|
||||
try self.sounds.append(gpa, Sound{
|
||||
.asset_id = id,
|
||||
.data = try self.insertData(gpa, data)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn appendFont(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
id: Lib.Asset.Id,
|
||||
data: []const u8
|
||||
) !void {
|
||||
try self.fonts.append(gpa, Font{
|
||||
.asset_id = id,
|
||||
.data = try self.insertData(gpa, data)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn appendTexture(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
id: Lib.Asset.Id,
|
||||
data: []const u8,
|
||||
info: Lib.Texture.Info
|
||||
) !void {
|
||||
try self.textures.append(gpa, Texture{
|
||||
.asset_id = id,
|
||||
.info = info,
|
||||
.data = try self.insertData(gpa, data)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn write(self: AssetBundle, writer: *std.Io.Writer) !void {
|
||||
try writer.writeStruct(Header{
|
||||
.magic = .v1,
|
||||
.fonts_len = @intCast(self.fonts.items.len),
|
||||
.sounds_len = @intCast(self.sounds.items.len),
|
||||
.textures_len = @intCast(self.textures.items.len),
|
||||
.data_len = @intCast(self.data.items.len)
|
||||
}, endianness);
|
||||
|
||||
try writer.writeSliceEndian(Font, self.fonts.items, endianness);
|
||||
try writer.writeSliceEndian(Sound, self.sounds.items, endianness);
|
||||
try writer.writeSliceEndian(Texture, self.textures.items, endianness);
|
||||
try writer.writeAll(self.data.items);
|
||||
}
|
||||
|
||||
pub fn read(self: *AssetBundle, gpa: std.mem.Allocator, reader: *std.Io.Reader) !void {
|
||||
const header = try reader.takeStruct(Header, endianness);
|
||||
if (header.magic != .v1) {
|
||||
return error.InvalidMagic;
|
||||
}
|
||||
|
||||
try self.fonts.ensureTotalCapacity(gpa, header.fonts_len);
|
||||
try self.sounds.ensureTotalCapacity(gpa, header.sounds_len);
|
||||
try self.textures.ensureTotalCapacity(gpa, header.textures_len);
|
||||
try self.data.ensureTotalCapacity(gpa, header.data_len);
|
||||
|
||||
self.fonts.clearRetainingCapacity();
|
||||
self.sounds.clearRetainingCapacity();
|
||||
self.textures.clearRetainingCapacity();
|
||||
self.data.clearRetainingCapacity();
|
||||
|
||||
try reader.readSliceEndian(
|
||||
Font,
|
||||
self.fonts.addManyAsSliceAssumeCapacity(header.fonts_len),
|
||||
endianness
|
||||
);
|
||||
try reader.readSliceEndian(
|
||||
Sound,
|
||||
self.sounds.addManyAsSliceAssumeCapacity(header.sounds_len),
|
||||
endianness
|
||||
);
|
||||
try reader.readSliceEndian(
|
||||
Texture,
|
||||
self.textures.addManyAsSliceAssumeCapacity(header.textures_len),
|
||||
endianness
|
||||
);
|
||||
try reader.readSliceAll(self.data.addManyAsSliceAssumeCapacity(header.data_len));
|
||||
}
|
||||
@ -4,7 +4,7 @@ const assert = std.debug.assert;
|
||||
|
||||
const tracy = @import("tracy");
|
||||
|
||||
const Lib = @import("lib");
|
||||
const Lib = @import("engine_lib");
|
||||
const Math = Lib.Math;
|
||||
const STBVorbis = @import("stb_vorbis");
|
||||
|
||||
@ -215,7 +215,7 @@ const ThreadState = struct {
|
||||
temp_channel_buffer: []f32,
|
||||
|
||||
pub const SoundInstance = struct {
|
||||
sound_id: Lib.SoundId,
|
||||
sound_id: Lib.Sound.Id,
|
||||
volume: f32 = 0,
|
||||
cursor: u32 = 0,
|
||||
};
|
||||
@ -257,7 +257,7 @@ const SokolSetupOptions = struct {
|
||||
|
||||
const SoundList = Lib.GenerationalArrayList(u8, u8, Sound);
|
||||
comptime {
|
||||
assert(@bitSizeOf(SoundList.Id) == @bitSizeOf(Lib.SoundId));
|
||||
assert(@bitSizeOf(SoundList.Id) == @bitSizeOf(Lib.Sound.Id));
|
||||
}
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
@ -353,7 +353,7 @@ pub fn deinit(self: *AudioSystem) void {
|
||||
self.sounds.deinit(self.gpa);
|
||||
}
|
||||
|
||||
pub fn addSound(self: *AudioSystem) !Lib.SoundId {
|
||||
pub fn addSound(self: *AudioSystem) !Lib.Sound.Id {
|
||||
self.sounds_mutex.lock();
|
||||
defer self.sounds_mutex.unlock();
|
||||
|
||||
@ -363,7 +363,7 @@ pub fn addSound(self: *AudioSystem) !Lib.SoundId {
|
||||
return @enumFromInt(sound_id.asInt());
|
||||
}
|
||||
|
||||
pub fn removeSound(self: *AudioSystem, id: Lib.SoundId) void {
|
||||
pub fn removeSound(self: *AudioSystem, id: Lib.Sound.Id) void {
|
||||
self.sounds_mutex.lock();
|
||||
defer self.sounds_mutex.unlock();
|
||||
|
||||
@ -388,7 +388,7 @@ pub const DataOptions = struct {
|
||||
playback_style: ?PlaybackStyle = null,
|
||||
};
|
||||
|
||||
pub fn setSoundData(self: *AudioSystem, id: Lib.SoundId, opts: DataOptions) !void {
|
||||
pub fn setSoundData(self: *AudioSystem, id: Lib.Sound.Id, opts: DataOptions) !void {
|
||||
const sound = self.sounds.get(.fromInt(@intFromEnum(id))) orelse return error.SoundNotFound;
|
||||
|
||||
assert(opts.format == .vorbis);
|
||||
@ -441,10 +441,6 @@ pub fn setSoundData(self: *AudioSystem, id: Lib.SoundId, opts: DataOptions) !voi
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self: *AudioSystem, id: Lib.SoundId) Sound {
|
||||
return self.sounds.items[@intFromEnum(id)];
|
||||
}
|
||||
|
||||
fn sokolStream(self: *AudioSystem, buffer: [*c]f32, num_frames: u32, num_channels: u32) void {
|
||||
if (!self.running.load(.seq_cst)) {
|
||||
return;
|
||||
@ -482,7 +478,14 @@ fn sokolStream(self: *AudioSystem, buffer: [*c]f32, num_frames: u32, num_channel
|
||||
@memset(buffer[0..@intCast(num_frames * num_channels)], 0);
|
||||
|
||||
for (thread_state.instances.items) |*instance| {
|
||||
const sound = self.sounds.get(.fromInt(@intFromEnum(instance.sound_id))) orelse continue;
|
||||
const sound = self.sounds.get(.fromInt(@intFromEnum(instance.sound_id))) orelse {
|
||||
log.warn("Attempt to play that doesn't exist", .{});
|
||||
continue;
|
||||
};
|
||||
if (sound.* == .nil) {
|
||||
log.warn("Attempt to play .nil sound", .{});
|
||||
continue;
|
||||
}
|
||||
assert(sound.getSampleRate() == sample_rate); // TODO:
|
||||
|
||||
const sample_count = sound.streamChannels(.{
|
||||
@ -6,7 +6,7 @@ const sapp = sokol.app;
|
||||
const simgui = sokol.imgui;
|
||||
const sgl = sokol.gl;
|
||||
|
||||
const Lib = @import("lib");
|
||||
const Lib = @import("engine_lib");
|
||||
const Math = Lib.Math;
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
@ -57,8 +57,13 @@ const Texture = struct {
|
||||
const Data = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba: [*]u8
|
||||
rgba: [*]u8,
|
||||
};
|
||||
|
||||
fn destroy(self: *Texture) void {
|
||||
sg.destroyView(self.view);
|
||||
sg.destroyImage(self.image);
|
||||
}
|
||||
};
|
||||
|
||||
const Font = struct {
|
||||
@ -74,11 +79,19 @@ const Font = struct {
|
||||
|
||||
const FontArray = Lib.GenerationalArrayList(u8, u8, Font);
|
||||
comptime {
|
||||
assert(@bitSizeOf(FontArray.Id) == @bitSizeOf(Lib.FontId));
|
||||
assert(@bitSizeOf(FontArray.Id) == @bitSizeOf(Lib.Font.Id));
|
||||
assert(FontArray.Id.none.asInt() == @intFromEnum(Lib.Font.Id.nil));
|
||||
}
|
||||
|
||||
const TexturesArray = Lib.GenerationalArrayList(u8, u8, Texture);
|
||||
comptime {
|
||||
// TODO: This is PITA, can this be refactored?
|
||||
assert(@bitSizeOf(TexturesArray.Id) == @bitSizeOf(Lib.Texture.Id));
|
||||
assert(TexturesArray.Id.none.asInt() == @intFromEnum(Lib.Texture.Id.nil));
|
||||
}
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
textures: std.ArrayList(Texture) = .empty,
|
||||
textures: TexturesArray = .empty,
|
||||
fonts: FontArray = .empty,
|
||||
|
||||
scale_stack_buffer: [32]Vec2 = undefined,
|
||||
@ -188,20 +201,24 @@ pub fn setupSokol(self: *Graphics) !void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Graphics, gpa: std.mem.Allocator) void {
|
||||
if (self.sokol_state) |*sokol_state| {
|
||||
imgui.shutdown();
|
||||
sokol_state.font_context.deinit();
|
||||
sgl.shutdown();
|
||||
sg.shutdown();
|
||||
}
|
||||
|
||||
var fonts_iter = self.fonts.iterator();
|
||||
while (fonts_iter.nextItem()) |font| {
|
||||
font.deinit(self.gpa);
|
||||
}
|
||||
self.fonts.deinit(gpa);
|
||||
|
||||
var textures_iter = self.textures.iterator();
|
||||
while (textures_iter.nextItem()) |texture| {
|
||||
texture.destroy();
|
||||
}
|
||||
self.textures.deinit(gpa);
|
||||
|
||||
if (self.sokol_state) |*sokol_state| {
|
||||
imgui.shutdown();
|
||||
sokol_state.font_context.deinit();
|
||||
sgl.shutdown();
|
||||
sg.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawCommand(self: *Graphics, command: Command) void {
|
||||
@ -293,17 +310,6 @@ pub fn drawCommands(self: *Graphics, commands: []const Command) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refreshFonts(self: *Graphics) !void {
|
||||
const sokol_state = self.sokol_state orelse return;
|
||||
|
||||
var fonts_iter = self.fonts.iterator();
|
||||
while (fonts_iter.nextItem()) |font| {
|
||||
if (font.fontstash_id == null and font.data != null) {
|
||||
font.fontstash_id = try sokol_state.font_context.addFont("", font.data.?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn beginFrame(self: *Graphics) !void {
|
||||
assert(self.sokol_state != null);
|
||||
const sokol_state = self.sokol_state.?;
|
||||
@ -378,16 +384,24 @@ const Vertex = struct {
|
||||
uv: Vec2
|
||||
};
|
||||
|
||||
fn drawQuad(self: *Graphics, quad: [4]Vertex, color: Vec4, texture_id: Lib.TextureId) void {
|
||||
fn drawQuad(self: *Graphics, quad: [4]Vertex, color: Vec4, texture_id: Lib.Texture.Id) void {
|
||||
assert(self.sokol_state != null);
|
||||
const sokol_state = self.sokol_state.?;
|
||||
|
||||
var view: ?sg.View = null;
|
||||
if (self.textures.get(.fromInt(@intFromEnum(texture_id)))) |texture| {
|
||||
view = texture.view;
|
||||
} else {
|
||||
log.warn("Attempt to use texture that doesn't exist, id={}", .{texture_id});
|
||||
}
|
||||
|
||||
sgl.enableTexture();
|
||||
defer sgl.disableTexture();
|
||||
|
||||
const view = self.textures.items[@intFromEnum(texture_id)].view;
|
||||
// TODO: Make sampler configurable
|
||||
sgl.texture(view, sokol_state.nearest_sampler);
|
||||
if (view != null) {
|
||||
sgl.texture(view.?, sokol_state.nearest_sampler);
|
||||
}
|
||||
|
||||
sgl.beginQuads();
|
||||
defer sgl.end();
|
||||
@ -465,17 +479,9 @@ fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
);
|
||||
}
|
||||
|
||||
fn makeView(image: sg.Image) !sg.View {
|
||||
const image_view = sg.makeView(.{
|
||||
.texture = .{ .image = image }
|
||||
});
|
||||
if (image_view.id == sg.invalid_id) {
|
||||
return error.InvalidView;
|
||||
}
|
||||
return image_view;
|
||||
}
|
||||
// ---------------------- Textures ----------------- //
|
||||
|
||||
fn makeImageWithMipMaps(mipmaps: []const Texture.Data) !sg.Image {
|
||||
fn initImageWithMipMaps(image: sg.Image, mipmaps: []const Texture.Data) !void {
|
||||
if (mipmaps.len == 0) {
|
||||
return error.NoMipMaps;
|
||||
}
|
||||
@ -490,7 +496,7 @@ fn makeImageWithMipMaps(mipmaps: []const Texture.Data) !sg.Image {
|
||||
});
|
||||
}
|
||||
|
||||
const image = sg.makeImage(.{
|
||||
sg.initImage(image, .{
|
||||
.width = @intCast(mipmaps[0].width),
|
||||
.height = @intCast(mipmaps[0].height),
|
||||
.pixel_format = .RGBA8,
|
||||
@ -500,35 +506,60 @@ fn makeImageWithMipMaps(mipmaps: []const Texture.Data) !sg.Image {
|
||||
.num_mipmaps = @intCast(mip_levels.items.len),
|
||||
.data = data
|
||||
});
|
||||
if (image.id == sg.invalid_id) {
|
||||
return error.InvalidImage;
|
||||
if (sg.queryImageState(image) != .VALID) {
|
||||
return error.InitImage;
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
pub fn loadTexture(self: *Graphics, gpa: std.mem.Allocator, mipmaps: []const Texture.Data) !Lib.TextureId {
|
||||
const image = try makeImageWithMipMaps(mipmaps);
|
||||
errdefer sg.deallocImage(image);
|
||||
|
||||
const view = try makeView(image);
|
||||
errdefer sg.deallocView(view);
|
||||
|
||||
assert(mipmaps.len > 0);
|
||||
const index = self.textures.items.len;
|
||||
try self.textures.append(gpa, .{
|
||||
.image = image,
|
||||
.view = view,
|
||||
.info = .{
|
||||
.width = mipmaps[0].width,
|
||||
.height = mipmaps[0].height,
|
||||
}
|
||||
pub fn addTexture(self: *Graphics) !Lib.Texture.Id {
|
||||
const texture_id = try self.textures.insert(self.gpa, .{
|
||||
.image = .{ .id = sg.invalid_id },
|
||||
.view = .{ .id = sg.invalid_id }
|
||||
});
|
||||
|
||||
return @enumFromInt(index);
|
||||
return @enumFromInt(texture_id.asInt());
|
||||
}
|
||||
|
||||
pub fn addFont(self: *Graphics) !Lib.FontId {
|
||||
pub fn removeTexture(self: *Graphics, id: Lib.Texture.Id) void {
|
||||
_ = self.textures.remove(id);
|
||||
}
|
||||
|
||||
pub fn setTextureData(self: *Graphics, id: Lib.Texture.Id, data: Texture.Data) !void {
|
||||
if (self.sokol_state == null) {
|
||||
return error.SokolNotSetup;
|
||||
}
|
||||
|
||||
const texture = self.textures.get(.fromInt(@intFromEnum(id))) orelse return;
|
||||
|
||||
if (texture.image.id == sg.invalid_id) {
|
||||
texture.image = sg.allocImage();
|
||||
if (texture.image.id == sg.invalid_id) {
|
||||
return error.AllocImage;
|
||||
}
|
||||
}
|
||||
|
||||
if (texture.view.id == sg.invalid_id) {
|
||||
texture.view = sg.allocView();
|
||||
if (texture.view.id == sg.invalid_id) {
|
||||
return error.AllocView;
|
||||
}
|
||||
}
|
||||
|
||||
sg.uninitView(texture.view);
|
||||
sg.uninitImage(texture.image);
|
||||
|
||||
try initImageWithMipMaps(texture.image, &.{ data });
|
||||
|
||||
sg.initView(texture.view, .{
|
||||
.texture = .{ .image = texture.image }
|
||||
});
|
||||
if (sg.queryViewState(texture.view) != .VALID) {
|
||||
return error.InitView;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Fonts ----------------- //
|
||||
|
||||
pub fn addFont(self: *Graphics) !Lib.Font.Id {
|
||||
const font_id = try self.fonts.insert(self.gpa, .{
|
||||
.fontstash_id = null,
|
||||
.data = null
|
||||
@ -536,18 +567,23 @@ pub fn addFont(self: *Graphics) !Lib.FontId {
|
||||
return @enumFromInt(font_id.asInt());
|
||||
}
|
||||
|
||||
pub fn removeFont(self: *Graphics, id: Lib.FontId) void {
|
||||
pub fn removeFont(self: *Graphics, id: Lib.Font.Id) void {
|
||||
_ = self.fonts.remove(.fromInt(@intFromEnum(id)));
|
||||
}
|
||||
|
||||
pub fn setFontData(self: *Graphics, id: Lib.FontId, data: []const u8) !void {
|
||||
if (self.fonts.get(.fromInt(@intFromEnum(id)))) |font| {
|
||||
pub fn setFontData(self: *Graphics, id: Lib.Font.Id, data: []const u8) !void {
|
||||
const sokol_state = self.sokol_state orelse return error.SokolNotSetup;
|
||||
|
||||
const font = self.fonts.get(.fromInt(@intFromEnum(id))) orelse return;
|
||||
|
||||
const new_data = try self.gpa.dupe(u8, data);
|
||||
errdefer self.gpa.free(new_data);
|
||||
|
||||
const new_fontstash_id = try sokol_state.font_context.addFont("", new_data);
|
||||
|
||||
if (font.data) |old_data| {
|
||||
self.gpa.free(old_data);
|
||||
}
|
||||
font.data = new_data;
|
||||
font.fontstash_id = null;
|
||||
}
|
||||
font.fontstash_id = new_fontstash_id;
|
||||
}
|
||||
@ -9,7 +9,7 @@ const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
const simgui = sokol.imgui;
|
||||
|
||||
const Lib = @import("lib");
|
||||
const Lib = @import("engine_lib");
|
||||
const build_options = Lib.build_options;
|
||||
const enabled = build_options.has_imgui;
|
||||
|
||||
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const lib = @import("lib");
|
||||
const lib = @import("engine_lib");
|
||||
const Frame = lib.Frame;
|
||||
const Nanoseconds = lib.Nanoseconds;
|
||||
const Vec2 = lib.Math.Vec2;
|
||||
@ -5,7 +5,7 @@ const assert = std.debug.assert;
|
||||
const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
|
||||
pub const Input = @import("./input.zig");
|
||||
const Input = @import("./input.zig");
|
||||
|
||||
const ScreenScalar = @import("./screen_scaler.zig");
|
||||
pub const imgui = @import("./imgui.zig");
|
||||
@ -17,7 +17,9 @@ const STBImage = @import("stb_image");
|
||||
|
||||
const Gfx = Graphics;
|
||||
|
||||
const Lib = @import("lib");
|
||||
pub const Lib = @import("engine_lib");
|
||||
pub const AssetBundle = @import("./asset_bundle.zig");
|
||||
|
||||
const build_options = Lib.build_options;
|
||||
const debug_keybinds = (builtin.mode == .Debug);
|
||||
const GameCallbacks = Lib.Callbacks;
|
||||
@ -343,21 +345,22 @@ const GameState = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn runGameInit(self: *GameState, init_fn: GameCallbacks.InitFn, initial: Init, assets: []const Lib.Asset) void {
|
||||
fn runGameInit(
|
||||
self: *GameState,
|
||||
init_fn: GameCallbacks.InitFn,
|
||||
initial: Init,
|
||||
asset_registry: Lib.Asset.Registry
|
||||
) void {
|
||||
assert(self.frame == null);
|
||||
const opts = Lib.Init{
|
||||
.gpa = self.gpa,
|
||||
.seed = initial.seed,
|
||||
.assets = .{
|
||||
.items = assets
|
||||
}
|
||||
.assets = asset_registry
|
||||
};
|
||||
self.state = init_fn(&opts);
|
||||
|
||||
self.frame = .init(self.gpa, initial.screen_size);
|
||||
self.frame.?.assets = .{
|
||||
.items = assets
|
||||
};
|
||||
self.frame.?.assets = asset_registry;
|
||||
}
|
||||
|
||||
fn runGameDeinit(self: *GameState, deinit_fn: GameCallbacks.DeinitFn) void {
|
||||
@ -476,13 +479,157 @@ const Recording = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const AssetManager = struct {
|
||||
const LoadAsset = union(enum) {
|
||||
font: struct {
|
||||
id: Lib.Font.Id,
|
||||
path: []const u8
|
||||
},
|
||||
sound: struct {
|
||||
id: Lib.Sound.Id,
|
||||
path: []const u8
|
||||
},
|
||||
texture: struct {
|
||||
id: Lib.Texture.Id,
|
||||
path: []const u8
|
||||
},
|
||||
|
||||
pub fn clone(self: LoadAsset, arena: std.mem.Allocator) !LoadAsset {
|
||||
return switch (self) {
|
||||
.font => |opts| LoadAsset{
|
||||
.font = .{
|
||||
.id = opts.id,
|
||||
.path = try arena.dupe(u8, opts.path)
|
||||
}
|
||||
},
|
||||
.sound => |opts| LoadAsset{
|
||||
.sound = .{
|
||||
.id = opts.id,
|
||||
.path = try arena.dupe(u8, opts.path)
|
||||
}
|
||||
},
|
||||
.texture => |opts| LoadAsset{
|
||||
.texture = .{
|
||||
.id = opts.id,
|
||||
.path = try arena.dupe(u8, opts.path)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
assets: std.ArrayList(Lib.Asset),
|
||||
textures: std.ArrayList(Lib.Texture),
|
||||
|
||||
dir: ?std.fs.Dir,
|
||||
queued_loads_arena: std.heap.ArenaAllocator,
|
||||
queued_loads: std.ArrayList(LoadAsset),
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator, dir: ?std.fs.Dir) AssetManager {
|
||||
return AssetManager{
|
||||
.gpa = gpa,
|
||||
.assets = .empty,
|
||||
.textures = .empty,
|
||||
.dir = dir,
|
||||
.queued_loads_arena = .init(gpa),
|
||||
.queued_loads = .empty
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AssetManager) void {
|
||||
self.queued_loads_arena.deinit();
|
||||
self.assets.deinit(self.gpa);
|
||||
self.textures.deinit(self.gpa);
|
||||
}
|
||||
|
||||
pub fn initRegistry(self: AssetManager) Lib.Asset.Registry {
|
||||
return Lib.Asset.Registry{
|
||||
.assets = self.assets.items,
|
||||
.textures = self.textures.items
|
||||
};
|
||||
}
|
||||
|
||||
pub fn enqueueLoad(self: *AssetManager, load: LoadAsset) !void {
|
||||
const arena = self.queued_loads_arena.allocator();
|
||||
|
||||
try self.queued_loads.append(arena, try load.clone(arena));
|
||||
}
|
||||
|
||||
pub fn performQueuedLoads(self: *AssetManager, graphics: *Graphics, audio: *Audio) !void {
|
||||
if (self.dir == null) {
|
||||
if (self.queued_loads.items.len > 0) {
|
||||
return error.MissingAssetDirectory;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const dir = self.dir.?;
|
||||
for (self.queued_loads.items) |queued_load| {
|
||||
switch (queued_load) {
|
||||
.font => |opts| {
|
||||
const font_data = try readFileAll(self.gpa, dir, opts.path);
|
||||
defer self.gpa.free(font_data);
|
||||
try graphics.setFontData(opts.id, font_data);
|
||||
},
|
||||
.sound => |opts| {
|
||||
const sound_data = try readFileAll(self.gpa, dir, opts.path);
|
||||
defer self.gpa.free(sound_data);
|
||||
try audio.setSoundData(opts.id, .{
|
||||
.data = sound_data,
|
||||
.format = .vorbis
|
||||
});
|
||||
},
|
||||
.texture => |opts| {
|
||||
const image_data = try readFileAll(self.gpa, dir, opts.path);
|
||||
defer self.gpa.free(image_data);
|
||||
|
||||
const image = try STBImage.load(image_data);
|
||||
defer image.deinit();
|
||||
|
||||
// TODO: Assert that loaded data matches precomputed texture info
|
||||
try graphics.setTextureData(opts.id, .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.rgba = image.rgba8_pixels,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.queued_loads_arena.reset(.free_all);
|
||||
self.queued_loads = .empty;
|
||||
}
|
||||
|
||||
fn hasTexture(self: AssetManager, texture_id: Lib.Texture.Id) bool {
|
||||
for (self.textures.items) |texture| {
|
||||
if (texture.id == texture_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn assertAssetsLoaded(self: AssetManager) void {
|
||||
for (self.assets.items) |asset| {
|
||||
switch (asset.kind) {
|
||||
.texture => |texture_id| {
|
||||
assert(self.hasTexture(texture_id));
|
||||
},
|
||||
else => {} // TODO:
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Engine = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
graphics: Graphics,
|
||||
audio: Audio,
|
||||
|
||||
assets: std.ArrayList(Lib.Asset),
|
||||
asset_bundle: ?AssetBundle = null,
|
||||
asset_manager: AssetManager,
|
||||
game_linking: GameLinking,
|
||||
game_state: GameState,
|
||||
|
||||
@ -501,6 +648,7 @@ recording_tick: u64,
|
||||
const RunOptions = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
game_library_path: ?[]const u8 = null,
|
||||
asset_bundle_bytes: ?[]const u8 = null,
|
||||
do_recording: bool = (builtin.mode == .Debug),
|
||||
screen_width: u32 = 640,
|
||||
screen_height: u32 = 480,
|
||||
@ -514,11 +662,30 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
game_linking = try .initStatic();
|
||||
}
|
||||
|
||||
var maybe_asset_bundle: ?AssetBundle = null;
|
||||
if (opts.asset_bundle_bytes) |asset_bundle_bytes| {
|
||||
var asset_bundle = AssetBundle.init();
|
||||
errdefer asset_bundle.deinit(opts.allocator);
|
||||
|
||||
var reader = std.Io.Reader.fixed(asset_bundle_bytes);
|
||||
asset_bundle.read(opts.allocator, &reader) catch |e| {
|
||||
log.err("Failed to load asset bundle: {}", .{e});
|
||||
return error.FailedToLoadAssetBundle;
|
||||
};
|
||||
|
||||
maybe_asset_bundle = asset_bundle;
|
||||
}
|
||||
|
||||
const initial = GameState.Init{
|
||||
.seed = @bitCast(std.time.milliTimestamp()),
|
||||
.screen_size = .initFromInt(u32, opts.screen_width, opts.screen_height)
|
||||
};
|
||||
|
||||
var asset_dir: ?std.fs.Dir = null;
|
||||
if (build_options.load_assets_at_runtime) {
|
||||
asset_dir = try std.fs.cwd().openDir(build_options.asset_dir, .{ .iterate = true });
|
||||
}
|
||||
|
||||
self.* = Engine{
|
||||
.allocator = opts.allocator,
|
||||
.graphics = try .init(.{
|
||||
@ -543,64 +710,100 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
.play_recording = false,
|
||||
.recording_tick = 0,
|
||||
.seed = initial.seed,
|
||||
.assets = .empty
|
||||
.asset_manager = .init(opts.allocator, asset_dir),
|
||||
.asset_bundle = maybe_asset_bundle,
|
||||
};
|
||||
|
||||
if (build_options.load_assets_at_runtime) {
|
||||
var dir = try std.fs.cwd().openDir(build_options.asset_dir, .{ .iterate = true });
|
||||
defer dir.close();
|
||||
const dir = asset_dir.?;
|
||||
|
||||
var loader: Lib.Asset.Loader = .init(opts.allocator, dir);
|
||||
defer loader.deinit();
|
||||
game_linking.callbacks.loadAssets(&loader);
|
||||
for (loader.assets.items) |asset_desc| {
|
||||
switch (asset_desc.kind) {
|
||||
.font => |path| {
|
||||
const f = try dir.openFile(path, .{});
|
||||
defer f.close();
|
||||
|
||||
var wa: std.Io.Writer.Allocating = .init(self.allocator);
|
||||
defer wa.deinit();
|
||||
|
||||
var buffer: [4 * 4096]u8 = undefined;
|
||||
var reader = f.reader(&buffer);
|
||||
_ = try reader.interface.stream(&wa.writer, .unlimited);
|
||||
|
||||
const font_id = try self.graphics.addFont();
|
||||
try self.graphics.setFontData(font_id, wa.written());
|
||||
try self.assets.append(self.allocator, .{
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = asset_desc.id,
|
||||
.kind = .{ .font = font_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.enqueueLoad(.{
|
||||
.font = .{
|
||||
.id = font_id,
|
||||
.path = path
|
||||
}
|
||||
});
|
||||
},
|
||||
.texture => |path| {
|
||||
_ = path; // autofix
|
||||
.texture => |texture| {
|
||||
const texture_id = try self.graphics.addTexture();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = asset_desc.id,
|
||||
.kind = .{ .texture = texture_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.enqueueLoad(.{
|
||||
.texture = .{
|
||||
.id = texture_id,
|
||||
.path = texture.path
|
||||
}
|
||||
});
|
||||
try self.asset_manager.textures.append(self.asset_manager.gpa, .{
|
||||
.id = texture_id,
|
||||
.info = texture.info
|
||||
});
|
||||
},
|
||||
.sound => |path| {
|
||||
const f = try dir.openFile(path, .{});
|
||||
defer f.close();
|
||||
|
||||
var wa: std.Io.Writer.Allocating = .init(self.allocator);
|
||||
defer wa.deinit();
|
||||
|
||||
var buffer: [4 * 4096]u8 = undefined;
|
||||
var reader = f.reader(&buffer);
|
||||
_ = try reader.interface.stream(&wa.writer, .unlimited);
|
||||
|
||||
const sound_id = try self.audio.addSound();
|
||||
try self.audio.setSoundData(sound_id, .{
|
||||
.data = wa.written(),
|
||||
.format = .vorbis
|
||||
});
|
||||
try self.assets.append(self.allocator, .{
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = asset_desc.id,
|
||||
.kind = .{ .sound = sound_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.enqueueLoad(.{
|
||||
.sound = .{
|
||||
.id = sound_id,
|
||||
.path = path
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.game_state.runGameInit(game_linking.callbacks.init, initial, self.assets.items);
|
||||
if (maybe_asset_bundle) |asset_bundle| {
|
||||
for (asset_bundle.fonts.items) |font| {
|
||||
const font_id = try self.graphics.addFont();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = font.asset_id,
|
||||
.kind = .{ .font = font_id }
|
||||
});
|
||||
}
|
||||
|
||||
for (asset_bundle.textures.items) |texture| {
|
||||
const texture_id = try self.graphics.addTexture();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = texture.asset_id,
|
||||
.kind = .{ .texture = texture_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.textures.append(self.asset_manager.gpa, .{
|
||||
.id = texture_id,
|
||||
.info = texture.info
|
||||
});
|
||||
}
|
||||
|
||||
for (asset_bundle.sounds.items) |sound| {
|
||||
const sound_id = try self.audio.addSound();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = sound.asset_id,
|
||||
.kind = .{ .sound = sound_id }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.game_state.runGameInit(game_linking.callbacks.init, initial, self.asset_manager.initRegistry());
|
||||
|
||||
if (opts.do_recording) {
|
||||
self.recording = .init(opts.allocator, initial);
|
||||
@ -660,6 +863,20 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
});
|
||||
}
|
||||
|
||||
fn readFileAll(gpa: std.mem.Allocator, dir: std.fs.Dir, sub_path: []const u8) ![]u8 {
|
||||
const f = try dir.openFile(sub_path, .{});
|
||||
defer f.close();
|
||||
|
||||
var wa: std.Io.Writer.Allocating = .init(gpa);
|
||||
defer wa.deinit();
|
||||
|
||||
var buffer: [4 * 4096]u8 = undefined;
|
||||
var reader = f.reader(&buffer);
|
||||
_ = try reader.interface.stream(&wa.writer, .unlimited);
|
||||
|
||||
return try wa.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn sokolInit(self: *Engine) !void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
@ -667,7 +884,44 @@ fn sokolInit(self: *Engine) !void {
|
||||
try self.graphics.setupSokol();
|
||||
self.audio.setupSokol();
|
||||
|
||||
try self.graphics.refreshFonts();
|
||||
try self.asset_manager.performQueuedLoads(&self.graphics, &self.audio);
|
||||
if (self.asset_bundle) |*asset_bundle| {
|
||||
const registry = self.asset_manager.initRegistry();
|
||||
|
||||
for (asset_bundle.fonts.items) |font| {
|
||||
const font_id = registry.getFont(font.asset_id);
|
||||
assert(font_id != Lib.Font.Id.nil);
|
||||
try self.graphics.setFontData(font_id, asset_bundle.getData(font.data));
|
||||
}
|
||||
|
||||
for (asset_bundle.textures.items) |texture| {
|
||||
const texture_id = registry.getTexture(texture.asset_id);
|
||||
assert(texture_id != Lib.Texture.Id.nil);
|
||||
|
||||
const image = try STBImage.load(asset_bundle.getData(texture.data));
|
||||
defer image.deinit();
|
||||
|
||||
try self.graphics.setTextureData(texture_id, .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.rgba = image.rgba8_pixels
|
||||
});
|
||||
}
|
||||
|
||||
for (asset_bundle.sounds.items) |sound| {
|
||||
const sound_id = registry.getSound(sound.asset_id);
|
||||
assert(sound_id != Lib.Sound.Id.nil);
|
||||
try self.audio.setSoundData(sound_id, .{
|
||||
.data = asset_bundle.getData(sound.data),
|
||||
.format = .vorbis
|
||||
});
|
||||
}
|
||||
|
||||
asset_bundle.deinit(self.allocator);
|
||||
self.asset_bundle = null;
|
||||
}
|
||||
|
||||
self.asset_manager.assertAssetsLoaded();
|
||||
}
|
||||
|
||||
fn sokolCleanup(self: *Engine) void {
|
||||
@ -684,7 +938,7 @@ fn sokolCleanup(self: *Engine) void {
|
||||
self.audio.deinit();
|
||||
self.graphics.deinit(self.allocator);
|
||||
self.game_linking.deinit(self.allocator);
|
||||
self.assets.deinit(self.allocator);
|
||||
self.asset_manager.deinit();
|
||||
}
|
||||
|
||||
fn sokolFrame(self: *Engine) !void {
|
||||
@ -759,7 +1013,7 @@ fn sokolFrame(self: *Engine) !void {
|
||||
self.game_state.deinit();
|
||||
|
||||
self.game_state = .init(self.allocator);
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, initial, self.assets.items);
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, initial, self.asset_manager.initRegistry());
|
||||
|
||||
if (self.recording) |recording| {
|
||||
recording.deinit();
|
||||
@ -811,7 +1065,7 @@ fn showDebugWindow(self: *Engine) !void {
|
||||
self.game_state.deinit();
|
||||
|
||||
self.game_state = .init(self.allocator);
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, recording.initial, self.assets.items);
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, recording.initial, self.asset_manager.initRegistry());
|
||||
|
||||
self.play_recording = true;
|
||||
self.recording_tick = 0;
|
||||
@ -1,11 +1,11 @@
|
||||
const Gfx = @import("./graphics.zig");
|
||||
|
||||
const Math = @import("lib").Math;
|
||||
const Math = @import("engine_lib").Math;
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const rgb = Math.rgb;
|
||||
|
||||
const Lib = @import("lib");
|
||||
const Lib = @import("engine_lib");
|
||||
const Frame = Lib.Frame;
|
||||
|
||||
const ScreenScalar = @This();
|
||||
206
engine/src/game_lib/asset.zig
Normal file
206
engine/src/game_lib/asset.zig
Normal file
@ -0,0 +1,206 @@
|
||||
const std = @import("std");
|
||||
const log = std.log.scoped(.engine);
|
||||
|
||||
const STBImage = @import("stb_image");
|
||||
|
||||
const Font = @import("./font.zig");
|
||||
const Sound = @import("./sound.zig");
|
||||
const Texture = @import("./texture.zig");
|
||||
|
||||
const Asset = @This();
|
||||
|
||||
id: Id,
|
||||
kind: union(Type) {
|
||||
font: Font.Id,
|
||||
sound: Sound.Id,
|
||||
texture: Texture.Id,
|
||||
},
|
||||
|
||||
pub const Id = u32;
|
||||
pub const Type = enum {
|
||||
font,
|
||||
sound,
|
||||
texture
|
||||
};
|
||||
|
||||
pub const Loader = struct {
|
||||
const Descriptor = struct {
|
||||
const KindTexture = struct {
|
||||
path: []const u8,
|
||||
info: Texture.Info
|
||||
};
|
||||
|
||||
id: Id,
|
||||
kind: union(Type) {
|
||||
font: []const u8,
|
||||
sound: []const u8,
|
||||
texture: KindTexture
|
||||
}
|
||||
};
|
||||
|
||||
arena: std.heap.ArenaAllocator,
|
||||
dir: std.fs.Dir,
|
||||
assets: std.ArrayList(Descriptor),
|
||||
add_failed: bool,
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator, dir: std.fs.Dir) Loader {
|
||||
return Loader{
|
||||
.arena = .init(gpa),
|
||||
.dir = dir,
|
||||
.assets = .empty,
|
||||
.add_failed = false
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Loader) void {
|
||||
self.arena.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn readFile(self: *Loader, gpa: std.mem.Allocator, path: []const u8) ![]u8 {
|
||||
const f = try self.dir.openFile(path, .{});
|
||||
defer f.close();
|
||||
|
||||
var wa: std.Io.Writer.Allocating = .init(gpa);
|
||||
defer wa.deinit();
|
||||
|
||||
var buffer: [4 * 4096]u8 = undefined;
|
||||
var reader = f.reader(&buffer);
|
||||
_ = try reader.interface.stream(&wa.writer, .unlimited);
|
||||
|
||||
return try wa.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn addFont(self: *Loader, id: Asset.Id, path: []const u8) void {
|
||||
const allocator = self.arena.allocator();
|
||||
const path_dupe = allocator.dupe(u8, path) catch |e| {
|
||||
log.err("Failed to duplicate path: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
self.assets.append(allocator, Descriptor{
|
||||
.id = id,
|
||||
.kind = .{ .font = path_dupe }
|
||||
}) catch |e| {
|
||||
log.err("Failed add sound: {}", .{e});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addSound(self: *Loader, id: Asset.Id, path: []const u8) void {
|
||||
const allocator = self.arena.allocator();
|
||||
const path_dupe = allocator.dupe(u8, path) catch |e| {
|
||||
log.err("Failed to duplicate path: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
self.assets.append(allocator, Descriptor{
|
||||
.id = id,
|
||||
.kind = .{ .sound = path_dupe }
|
||||
}) catch |e| {
|
||||
log.err("Failed add sound: {}", .{e});
|
||||
};
|
||||
}
|
||||
|
||||
fn addTextureFallible(self: *Loader, id: Asset.Id, path: []const u8) !void {
|
||||
const allocator = self.arena.allocator();
|
||||
|
||||
var image_info: Texture.Info = undefined;
|
||||
{
|
||||
const file_contents = try self.readFile(allocator, path);
|
||||
defer allocator.free(file_contents);
|
||||
|
||||
const image = try STBImage.load(file_contents);
|
||||
defer image.deinit();
|
||||
|
||||
image_info = .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
};
|
||||
}
|
||||
|
||||
const path_dupe = allocator.dupe(u8, path) catch |e| {
|
||||
log.err("Failed to duplicate path: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
const texture = Descriptor.KindTexture{
|
||||
.path = path_dupe,
|
||||
.info = image_info
|
||||
};
|
||||
|
||||
self.assets.append(allocator, Descriptor{
|
||||
.id = id,
|
||||
.kind = .{ .texture = texture }
|
||||
}) catch |e| {
|
||||
log.err("Failed add sound: {}", .{e});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addTexture(self: *Loader, id: Asset.Id, path: []const u8) void {
|
||||
self.addTextureFallible(id, path) catch |e| {
|
||||
log.err("Failed to add texture: {}", .{e});
|
||||
self.add_failed = true;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Registry = struct {
|
||||
assets: []const Asset,
|
||||
textures: []const Texture,
|
||||
|
||||
pub const empty = Registry{
|
||||
.assets = &.{},
|
||||
.textures = &.{}
|
||||
};
|
||||
|
||||
pub fn getAsset(self: Registry, id: Asset.Id) ?Asset {
|
||||
// TODO: Make this O(1)?
|
||||
for (self.assets) |asset| {
|
||||
if (asset.id == id) {
|
||||
return asset;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getSound(self: Registry, id: Asset.Id) Sound.Id {
|
||||
const asset = self.getAsset(id) orelse return Sound.Id.nil;
|
||||
if (asset.kind != .sound) {
|
||||
return Sound.Id.nil;
|
||||
}
|
||||
return asset.kind.sound;
|
||||
}
|
||||
|
||||
pub fn getFont(self: Registry, id: Asset.Id) Font.Id {
|
||||
const asset = self.getAsset(id) orelse return Font.Id.nil;
|
||||
if (asset.kind != .font) {
|
||||
return Font.Id.nil;
|
||||
}
|
||||
return asset.kind.font;
|
||||
}
|
||||
|
||||
pub fn getTexture(self: Registry, id: Asset.Id) Texture.Id {
|
||||
const asset = self.getAsset(id) orelse return Texture.Id.nil;
|
||||
if (asset.kind != .texture) {
|
||||
return Texture.Id.nil;
|
||||
}
|
||||
return asset.kind.texture;
|
||||
}
|
||||
|
||||
pub fn getTextureInfo(self: Registry, id: Texture.Id) Texture.Info {
|
||||
if (id == .nil) {
|
||||
log.warn("Failed to access .nil texture info", .{});
|
||||
return .nil;
|
||||
}
|
||||
|
||||
// TODO: Make this O(1)?
|
||||
for (self.textures) |texture| {
|
||||
if (texture.id == id) {
|
||||
return texture.info;
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("Failed to find texture info, id={}", .{id});
|
||||
return .nil;
|
||||
}
|
||||
};
|
||||
9
engine/src/game_lib/font.zig
Normal file
9
engine/src/game_lib/font.zig
Normal file
@ -0,0 +1,9 @@
|
||||
const std = @import("std");
|
||||
|
||||
id: Id,
|
||||
|
||||
pub const Id = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const nil: Id = @enumFromInt(std.math.maxInt(@typeInfo(Id).@"enum".tag_type));
|
||||
};
|
||||
@ -6,8 +6,9 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
/// This array provides:
|
||||
/// - O(1) insertion
|
||||
/// - O(1) deletion
|
||||
/// - Unique IDs for every inserted item (assuming that generation doesn't overflow)
|
||||
/// TODO: Use a stack of unused indexes to implement O(1) insertion
|
||||
/// TODO: Implement a way to use a list *assumeCapacity() style of functions.
|
||||
pub fn GenerationalArrayList(
|
||||
Index: type,
|
||||
Generation: type,
|
||||
@ -367,6 +367,11 @@ pub const Rect = struct {
|
||||
.size = Vec2.zero
|
||||
};
|
||||
|
||||
pub const unit = Rect{
|
||||
.pos = Vec2.zero,
|
||||
.size = Vec2.init(1, 1)
|
||||
};
|
||||
|
||||
pub fn init(x: f32, y: f32, width: f32, height: f32) Rect {
|
||||
return Rect{
|
||||
.pos = Vec2.init(x, y),
|
||||
@ -5,6 +5,7 @@ pub const build_options = @import("build_options");
|
||||
pub const ImGui = @import("./imgui.zig");
|
||||
pub const Math = @import("./math.zig");
|
||||
pub const GenerationalArrayList = @import("./generational_array_list.zig").GenerationalArrayList;
|
||||
const STBImage = @import("stb_image");
|
||||
|
||||
const rgb = Math.rgb;
|
||||
const Rect = Math.Rect;
|
||||
@ -142,24 +143,13 @@ pub const MouseButton = enum {
|
||||
middle,
|
||||
};
|
||||
|
||||
pub const TextureId = enum(u16) {
|
||||
_,
|
||||
};
|
||||
|
||||
pub const FontId = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const nil: FontId = @enumFromInt(std.math.maxInt(@typeInfo(FontId).@"enum".tag_type));
|
||||
};
|
||||
|
||||
pub const SoundId = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const nil: SoundId = @enumFromInt(std.math.maxInt(@typeInfo(SoundId).@"enum".tag_type));
|
||||
};
|
||||
pub const Texture = @import("./texture.zig");
|
||||
pub const Font = @import("./font.zig");
|
||||
pub const Sound = @import("./sound.zig");
|
||||
pub const Asset = @import("./asset.zig");
|
||||
|
||||
pub const Sprite = struct {
|
||||
texture: TextureId,
|
||||
texture: Texture.Id,
|
||||
uv: Rect
|
||||
};
|
||||
|
||||
@ -221,7 +211,7 @@ pub const GraphicsCommand = union(enum) {
|
||||
pub const DrawText = struct {
|
||||
pos: Vec2,
|
||||
text: []const u8,
|
||||
font: FontId,
|
||||
font: Font.Id,
|
||||
size: f32,
|
||||
color: Vec4,
|
||||
};
|
||||
@ -240,7 +230,7 @@ pub const GraphicsCommand = union(enum) {
|
||||
|
||||
pub const AudioCommand = union(enum) {
|
||||
pub const Play = struct {
|
||||
id: SoundId,
|
||||
id: Sound.Id,
|
||||
volume: f32 = 1
|
||||
};
|
||||
|
||||
@ -412,7 +402,7 @@ pub const Frame = struct {
|
||||
}
|
||||
|
||||
pub const DrawTextOptions = struct {
|
||||
font: FontId,
|
||||
font: Font.Id,
|
||||
size: f32 = 16,
|
||||
color: Vec4 = rgb(255, 255, 255),
|
||||
};
|
||||
@ -491,121 +481,6 @@ pub const Init = struct {
|
||||
assets: Asset.Registry,
|
||||
};
|
||||
|
||||
pub const Asset = struct {
|
||||
id: Id,
|
||||
kind: union(Type) {
|
||||
font: FontId,
|
||||
sound: SoundId,
|
||||
texture: TextureId,
|
||||
},
|
||||
|
||||
pub const Id = u32;
|
||||
pub const Type = enum {
|
||||
font,
|
||||
sound,
|
||||
texture
|
||||
};
|
||||
|
||||
pub const Loader = struct {
|
||||
const Descriptor = struct {
|
||||
id: Id,
|
||||
kind: union(Type) {
|
||||
font: []const u8,
|
||||
sound: []const u8,
|
||||
texture: []const u8,
|
||||
}
|
||||
};
|
||||
|
||||
arena: std.heap.ArenaAllocator,
|
||||
dir: std.fs.Dir,
|
||||
assets: std.ArrayList(Descriptor),
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator, dir: std.fs.Dir) Loader {
|
||||
return Loader{
|
||||
.arena = .init(gpa),
|
||||
.dir = dir,
|
||||
.assets = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Loader) void {
|
||||
self.arena.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn addFont(self: *Loader, id: Asset.Id, path: []const u8) void {
|
||||
const allocator = self.arena.allocator();
|
||||
const path_dupe = allocator.dupe(u8, path) catch |e| {
|
||||
log.err("Failed to duplicate path: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
self.assets.append(allocator, Descriptor{
|
||||
.id = id,
|
||||
.kind = .{ .font = path_dupe }
|
||||
}) catch |e| {
|
||||
log.err("Failed add sound: {}", .{e});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addSound(self: *Loader, id: Asset.Id, path: []const u8) void {
|
||||
const allocator = self.arena.allocator();
|
||||
const path_dupe = allocator.dupe(u8, path) catch |e| {
|
||||
log.err("Failed to duplicate path: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
self.assets.append(allocator, Descriptor{
|
||||
.id = id,
|
||||
.kind = .{ .sound = path_dupe }
|
||||
}) catch |e| {
|
||||
log.err("Failed add sound: {}", .{e});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Registry = struct {
|
||||
items: []const Asset,
|
||||
|
||||
pub const empty = Registry{
|
||||
.items = &.{}
|
||||
};
|
||||
|
||||
pub fn get(self: Registry, id: Id) ?Asset {
|
||||
for (self.items) |asset| {
|
||||
if (asset.id == id) {
|
||||
return asset;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getSound(self: Registry, id: Id) SoundId {
|
||||
const asset = self.get(id) orelse return SoundId.nil;
|
||||
if (asset.kind != Type.sound) {
|
||||
return SoundId.nil;
|
||||
}
|
||||
return asset.kind.sound;
|
||||
}
|
||||
|
||||
pub fn getFont(self: Registry, id: Id) FontId {
|
||||
const asset = self.get(id) orelse return FontId.nil;
|
||||
if (asset.kind != Type.font) {
|
||||
return FontId.nil;
|
||||
}
|
||||
return asset.kind.font;
|
||||
}
|
||||
|
||||
pub fn getTexture(self: Registry, id: Id) TextureId {
|
||||
const asset = self.get(id) orelse return TextureId.nil;
|
||||
if (asset.kind != Type.font) {
|
||||
return TextureId.nil;
|
||||
}
|
||||
return asset.kind.font;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const Callbacks = struct {
|
||||
pub const State = *anyopaque;
|
||||
|
||||
9
engine/src/game_lib/sound.zig
Normal file
9
engine/src/game_lib/sound.zig
Normal file
@ -0,0 +1,9 @@
|
||||
const std = @import("std");
|
||||
|
||||
id: Id,
|
||||
|
||||
pub const Id = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const nil: Id = @enumFromInt(std.math.maxInt(@typeInfo(Id).@"enum".tag_type));
|
||||
};
|
||||
20
engine/src/game_lib/texture.zig
Normal file
20
engine/src/game_lib/texture.zig
Normal file
@ -0,0 +1,20 @@
|
||||
const std = @import("std");
|
||||
|
||||
id: Id,
|
||||
info: Info,
|
||||
|
||||
pub const Info = extern struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
pub const nil = Info{
|
||||
.width = 0,
|
||||
.height = 0
|
||||
};
|
||||
};
|
||||
|
||||
pub const Id = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const nil: Id = @enumFromInt(std.math.maxInt(@typeInfo(Id).@"enum".tag_type));
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Engine = @import("./engine.zig");
|
||||
const Engine = @import("engine");
|
||||
const build_options = Engine.Lib.build_options;
|
||||
|
||||
var engine: Engine = undefined;
|
||||
|
||||
@ -8,6 +9,11 @@ pub fn main() !void {
|
||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||
defer _ = debug_allocator.deinit();
|
||||
|
||||
var asset_bundle_bytes: ?[]const u8 = null;
|
||||
if (!build_options.load_assets_at_runtime) {
|
||||
asset_bundle_bytes = @embedFile("asset_bundle");
|
||||
}
|
||||
|
||||
// TODO: Use tracy TracingAllocator
|
||||
var allocator: std.mem.Allocator = undefined;
|
||||
if (builtin.cpu.arch.isWasm()) {
|
||||
@ -28,7 +34,8 @@ pub fn main() !void {
|
||||
|
||||
try engine.run(.{
|
||||
.allocator = allocator,
|
||||
.game_library_path = game_library_path
|
||||
.game_library_path = game_library_path,
|
||||
.asset_bundle_bytes = asset_bundle_bytes
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
79
engine/tools/asset-bundler.zig
Normal file
79
engine/tools/asset-bundler.zig
Normal file
@ -0,0 +1,79 @@
|
||||
const std = @import("std");
|
||||
const Game = @import("game");
|
||||
const Engine = @import("engine");
|
||||
const Lib = Engine.Lib;
|
||||
const AssetBundle = Engine.AssetBundle;
|
||||
|
||||
fn readFileAll(gpa: std.mem.Allocator, dir: std.fs.Dir, sub_path: []const u8) ![]u8 {
|
||||
const f = try dir.openFile(sub_path, .{});
|
||||
defer f.close();
|
||||
|
||||
var wa: std.Io.Writer.Allocating = .init(gpa);
|
||||
defer wa.deinit();
|
||||
|
||||
var buffer: [4 * 4096]u8 = undefined;
|
||||
var reader = f.reader(&buffer);
|
||||
_ = try reader.interface.stream(&wa.writer, .unlimited);
|
||||
|
||||
return try wa.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
if (args.len != 3) {
|
||||
std.debug.print("Usage: ./asset-bundler <asset-dir> <output>", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const cwd = std.fs.cwd();
|
||||
const asset_dir_path = args[1];
|
||||
const output_path = args[2];
|
||||
|
||||
const asset_dir = try cwd.openDir(asset_dir_path, .{ .iterate = true });
|
||||
|
||||
var loader: Lib.Asset.Loader = .init(allocator, asset_dir);
|
||||
defer loader.deinit();
|
||||
Game.loadAssets(&loader);
|
||||
|
||||
var bundle = AssetBundle.init();
|
||||
defer bundle.deinit(allocator);
|
||||
|
||||
for (loader.assets.items) |asset_desc| {
|
||||
const id = asset_desc.id;
|
||||
switch (asset_desc.kind) {
|
||||
.font => |path| {
|
||||
const font_data = try readFileAll(allocator, asset_dir, path);
|
||||
defer allocator.free(font_data);
|
||||
|
||||
try bundle.appendFont(allocator, id, font_data);
|
||||
},
|
||||
.sound => |path| {
|
||||
const sound_data = try readFileAll(allocator, asset_dir, path);
|
||||
defer allocator.free(sound_data);
|
||||
|
||||
try bundle.appendSound(allocator, id, sound_data);
|
||||
},
|
||||
.texture => |opts| {
|
||||
const texture_data = try readFileAll(allocator, asset_dir, opts.path);
|
||||
defer allocator.free(texture_data);
|
||||
|
||||
try bundle.appendTexture(allocator, id, texture_data, opts.info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const output_file = try std.fs.cwd().createFile(output_path, .{ });
|
||||
defer output_file.close();
|
||||
|
||||
var buffer: [4096 * 4]u8 = undefined;
|
||||
var writer = output_file.writer(&buffer);
|
||||
|
||||
try bundle.write(&writer.interface);
|
||||
try writer.interface.flush();
|
||||
}
|
||||
16
src/game.zig
16
src/game.zig
@ -2,8 +2,8 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Engine = @import("engine");
|
||||
const FontId = Engine.FontId;
|
||||
const Engine = @import("engine_lib");
|
||||
const FontId = Engine.Font.Id;
|
||||
const Vec2 = Engine.Math.Vec2;
|
||||
const rgb = Engine.Math.rgb;
|
||||
|
||||
@ -20,6 +20,7 @@ const AssetId = enum {
|
||||
font_bold,
|
||||
font_italic,
|
||||
sound_wood,
|
||||
texture_icon,
|
||||
};
|
||||
|
||||
const State = struct {
|
||||
@ -54,6 +55,8 @@ pub fn loadAssets(assets: *Engine.Asset.Loader) callconv(.c) void {
|
||||
assets.addFont(@intFromEnum(AssetId.font_italic), "roboto-font/Roboto-Italic.ttf");
|
||||
|
||||
assets.addSound(@intFromEnum(AssetId.sound_wood), "wood01.ogg");
|
||||
|
||||
assets.addTexture(@intFromEnum(AssetId.texture_icon), "icon.png");
|
||||
}
|
||||
|
||||
pub fn deinit(state_ptr: *anyopaque) callconv(.c) void {
|
||||
@ -98,13 +101,20 @@ pub fn tick(state_ptr: *anyopaque, frame: *Engine.Frame) callconv(.c) void {
|
||||
.rect = .{ .pos = .init(0, 0), .size = canvas_size },
|
||||
.color = rgb(20, 20, 20)
|
||||
});
|
||||
|
||||
const icon = frame.assets.getTexture(@intFromEnum(AssetId.texture_icon));
|
||||
|
||||
const size = Vec2.init(20, 20);
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{
|
||||
.pos = state.player.sub(size.divideScalar(2)),
|
||||
.size = size
|
||||
},
|
||||
.color = rgb(200, 2, 200)
|
||||
.color = rgb(200, 2, 200),
|
||||
.sprite = .{
|
||||
.texture = icon,
|
||||
.uv = .unit
|
||||
}
|
||||
});
|
||||
if (dir.x != 0 or dir.y != 0) {
|
||||
frame.drawRectanglOutline(state.player.sub(size.divideScalar(2)), size, rgb(20, 20, 20), 3);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user