commit 937693b16eca5cc149964709277155b2f1672149 Author: Rokas Puzonas Date: Sat Dec 6 15:37:34 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c766ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +zig-out +.zig-cache +.cache +compile_commands.json diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..d5f85b7 --- /dev/null +++ b/build.zig @@ -0,0 +1,164 @@ +const std = @import("std"); +const zcc = @import("compile_commands"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{ }); + const optimize = b.standardOptimizeOption(.{}); + + const tracy_enabled = b.option(bool, "tracy", "Enable tracy profiling") orelse (optimize == .Debug); + const imgui_enabled = b.option(bool, "imgui", "Enable ImGui integration") orelse (optimize == .Debug); + + var targets: std.ArrayList(*std.Build.Step.Compile) = .empty; + + const mod = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }); + mod.addIncludePath(b.path("./src/")); + + var cflags: std.ArrayList([]const u8) = .empty; + + const raylib_dep = b.dependency("raylib", .{ + .target = target, + .optimize = optimize + }); + mod.linkLibrary(raylib_dep.artifact("raylib")); + + const tracy_dep = b.dependency("tracy", .{ + .tracy_enable = tracy_enabled, + }); + mod.linkLibrary(tracy_dep.artifact("tracy")); + + const incbin_dep = b.dependency("incbin", .{}); + mod.addIncludePath(incbin_dep.path(".")); + + if (imgui_enabled) { + if (try buildImGui(b, .{ + .target = target, + .optimize = optimize, + .raylib = raylib_dep.artifact("raylib") + })) |imgui| { + mod.linkLibrary(imgui); + try targets.append(b.allocator, imgui); + } + + try cflags.append(b.allocator, "-DIMGUI_ENABLED"); + } + + { + var src_dir = try std.fs.cwd().openDir("src", .{ .iterate = true }); + defer src_dir.close(); + + var iter = src_dir.iterate(); + while (try iter.next()) |entry| { + if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".c")) { + mod.addCSourceFile(.{ + .file = b.path(b.pathJoin(&.{ "src", entry.name })), + .flags = cflags.items + }); + } + } + } + + const exe = b.addExecutable(.{ + .name = "gaem", + .root_module = mod + }); + try targets.append(b.allocator, exe); + + if (target.result.os.tag == .windows) { + const show_console = b.option(bool, "console", "Show windows console") orelse (optimize == .Debug); + exe.subsystem = if (show_console) .Console else .Windows; + } + + b.installArtifact(exe); + + { + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the program"); + run_step.dependOn(&run_cmd.step); + } + + _ = zcc.createStep(b, "cdb", try targets.toOwnedSlice(b.allocator)); +} + +const ImGuiOptions = struct { + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + raylib: *std.Build.Step.Compile +}; + +fn buildImGui(b: *std.Build, opts: ImGuiOptions) !?*std.Build.Step.Compile { + const imgui = b.lazyDependency("imgui", .{}) orelse return null; + const cimgui = b.lazyDependency("cimgui", .{}) orelse return null; + const rlimgui = b.lazyDependency("rlimgui", .{}) orelse return null; + + const cpp_flags = .{ + "-fno-exceptions", + "-fno-rtti", + "-std=c++11", + "-DNO_FONT_AWESOME" + }; + + const imgui_lib = b.addLibrary(.{ + .name = "imgui", + .root_module = b.createModule(.{ + .target = opts.target, + .optimize = opts.optimize, + .link_libcpp = true, + }) + }); + + imgui_lib.root_module.addCSourceFiles(.{ + .root = imgui.path("."), + .files = &.{ + "imgui.cpp", + "imgui_demo.cpp", + "imgui_draw.cpp", + "imgui_tables.cpp", + "imgui_widgets.cpp", + }, + .flags = &cpp_flags + }); + imgui_lib.root_module.addIncludePath(imgui.path(".")); + + // Needed for cimgui + imgui_lib.installHeadersDirectory(imgui.path("."), "imgui", .{}); + // Needed for rlimgui + imgui_lib.installHeadersDirectory(imgui.path("."), ".", .{}); + + const lib = b.addLibrary(.{ + .name = "imgui_raylib_cimgui", + .root_module = b.createModule(.{ + .target = opts.target, + .optimize = opts.optimize, + .link_libcpp = true, + }) + }); + lib.root_module.linkLibrary(imgui_lib); + + lib.root_module.addCSourceFile(.{ + .file = cimgui.path("cimgui.cpp"), + .flags = &cpp_flags + }); + lib.root_module.addIncludePath(cimgui.path(".")); + + lib.root_module.addCSourceFile(.{ + .file = rlimgui.path("rlImGui.cpp"), + .flags = &cpp_flags, + }); + lib.root_module.addIncludePath(rlimgui.path(".")); + lib.root_module.linkLibrary(opts.raylib); + + lib.installHeader(rlimgui.path("rlImGui.h"), "rlImGui.h"); + lib.installHeader(cimgui.path("cimgui.h"), "cimgui.h"); + + return lib; +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..3bb6f0b --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,39 @@ +.{ + .name = .raylib_template, + .version = "0.0.1", + .dependencies = .{ + .raylib = .{ + .url = "git+https://github.com/raysan5/raylib.git#6a048b7afeada62f9071969ba277ad14e0dce256", + .hash = "raylib-5.6.0-dev-whq8uM0UCAWTJ_Wd2f8GItDMnDg3hMImKT-nk78bscfL", + }, + .tracy = .{ + .path = "./libs/tracy" + }, + .incbin = .{ + .url = "git+https://github.com/graphitemaster/incbin.git#22061f51fe9f2f35f061f85c2b217b55dd75310d", + .hash = "N-V-__8AABmOAAB00zlm0f7NkMnWJxJD7g3y1PNsbgO7SCiQ", + }, + .compile_commands = .{ + .url = "https://github.com/the-argus/zig-compile-commands/archive/f74e2d13e43fafab3a71e19557a0e1cfbf0f2e1b.tar.gz", + .hash = "zig_compile_commands-0.0.1-OZg5-a3CAACM-h32Kjb1obTMqrKGs9YoDhorVZ8-LGle", + }, + .imgui = .{ + .url = "https://github.com/ocornut/imgui/archive/6d910d5487d11ca567b61c7824b0c78c569d62f0.tar.gz", + .hash = "N-V-__8AALp9cwA8tEuEno2YCZyivsMaobnF-Z7qZGY3qBOt", + .lazy = true + }, + .rlimgui = .{ + .url = "https://github.com/raylib-extras/rlImGui/archive/dc7f97679a024eee8f5f009e77cc311748200415.tar.gz", + .hash = "N-V-__8AAC-taQCKZ-3IjRuWJ-5Dc47QKHDPEmqlwXhvjBGq", + .lazy = true + }, + .cimgui = .{ + .url = "https://github.com/cimgui/cimgui/archive/bfd30140a9c5832b5e0dcf179d6e1e5c69373d5a.tar.gz", + .hash = "N-V-__8AAOY-OACWjhmHu289AXFy9Fro3w_30mGFYL8FTq71", + .lazy = true + } + }, + .minimum_zig_version = "0.15.2", + .paths = .{""}, + .fingerprint = 0xd9c063dda43688bf, +} diff --git a/libs/tracy/build.zig b/libs/tracy/build.zig new file mode 100644 index 0000000..775c688 --- /dev/null +++ b/libs/tracy/build.zig @@ -0,0 +1,124 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const options = b.addOptions(); + const tracy_enable = option(b, options, bool, "tracy_enable", "Enable profiling", true); + const tracy_on_demand = option(b, options, bool, "tracy_on_demand", "On-demand profiling", false); + const tracy_callstack = callstack: { + const opt = b.option(u8, "tracy_callstack", "Enforce callstack collection for tracy regions"); + options.addOption(?u8, "tracy_callstack", opt); + break :callstack opt; + }; + const tracy_no_callstack = option(b, options, bool, "tracy_no_callstack", "Disable all callstack related functionality", false); + const tracy_no_callstack_inlines = option(b, options, bool, "tracy_no_callstack_inlines", "Disables the inline functions in callstacks", false); + const tracy_only_localhost = option(b, options, bool, "tracy_only_localhost", "Only listen on the localhost interface", false); + const tracy_no_broadcast = option(b, options, bool, "tracy_no_broadcast", "Disable client discovery by broadcast to local network", false); + const tracy_only_ipv4 = option(b, options, bool, "tracy_only_ipv4", "Tracy will only accept connections on IPv4 addresses (disable IPv6)", false); + const tracy_no_code_transfer = option(b, options, bool, "tracy_no_code_transfer", "Disable collection of source code", false); + const tracy_no_context_switch = option(b, options, bool, "tracy_no_context_switch", "Disable capture of context switches", false); + const tracy_no_exit = option(b, options, bool, "tracy_no_exit", "Client executable does not exit until all profile data is sent to server", false); + const tracy_no_sampling = option(b, options, bool, "tracy_no_sampling", "Disable call stack sampling", false); + const tracy_no_verify = option(b, options, bool, "tracy_no_verify", "Disable zone validation for C API", false); + const tracy_no_vsync_capture = option(b, options, bool, "tracy_no_vsync_capture", "Disable capture of hardware Vsync events", false); + const tracy_no_frame_image = option(b, options, bool, "tracy_no_frame_image", "Disable the frame image support and its thread", false); + // @FIXME: For some reason system tracing crashes the program, will need to investigate + // panics during some drawf thing within libbacktrace (c++) + const tracy_no_system_tracing = option(b, options, bool, "tracy_no_system_tracing", "Disable systrace sampling", true); + const tracy_delayed_init = option(b, options, bool, "tracy_delayed_init", "Enable delayed initialization of the library (init on first call)", false); + const tracy_manual_lifetime = option(b, options, bool, "tracy_manual_lifetime", "Enable the manual lifetime management of the profile", false); + const tracy_fibers = option(b, options, bool, "tracy_fibers", "Enable fibers support", false); + const tracy_no_crash_handler = option(b, options, bool, "tracy_no_crash_handler", "Disable crash handling", false); + const tracy_timer_fallback = option(b, options, bool, "tracy_timer_fallback", "Use lower resolution timers", false); + const shared = option(b, options, bool, "shared", "Build the tracy client as a shared libary", false); + + const tracy_source_dep = b.dependency("tracy_source", .{}); + + const mod = b.addModule("tracy", .{ + .target = target, + .optimize = optimize, + }); + + // Avoid building Tracy completely if it is disabled. + + mod.addIncludePath(tracy_source_dep.path("public")); + + if (target.result.os.tag == .windows) { + mod.linkSystemLibrary("dbghelp", .{ .needed = true }); + mod.linkSystemLibrary("ws2_32", .{ .needed = true }); + } + mod.link_libcpp = true; + + const config_header = b.addConfigHeader(.{ .include_path = "TracyConfig.h" }, .{}); + + inline for (.{ + .{ tracy_enable, "TRACY_ENABLE", "" }, + .{ tracy_on_demand, "TRACY_ON_DEMAND", "" }, + .{ tracy_callstack != null, "TRACY_CALLSTACK", b.fmt("{?d}", .{tracy_callstack}) }, + .{ tracy_no_callstack, "TRACY_NO_CALLSTACK", "" }, + .{ tracy_no_callstack_inlines, "TRACY_NO_CALLSTACK_INLINES", "" }, + .{ tracy_only_localhost, "TRACY_ONLY_LOCALHOST", "" }, + .{ tracy_no_broadcast, "TRACY_NO_BROADCAST", "" }, + .{ tracy_only_ipv4, "TRACY_ONLY_IPV4", "" }, + .{ tracy_no_code_transfer, "TRACY_NO_CODE_TRANSFER", "" }, + .{ tracy_no_context_switch, "TRACY_NO_CONTEXT_SWITCH", "" }, + .{ tracy_no_exit, "TRACY_NO_EXIT", "" }, + .{ tracy_no_sampling, "TRACY_NO_SAMPLING", "" }, + .{ tracy_no_verify, "TRACY_NO_VERIFY", "" }, + .{ tracy_no_vsync_capture, "TRACY_NO_VSYNC_CAPTURE", ""}, + .{ tracy_no_frame_image, "TRACY_NO_FRAME_IMAGE", ""}, + .{ tracy_no_system_tracing, "TRACY_NO_SYSTEM_TRACING", ""}, + .{ tracy_delayed_init, "TRACY_DELAYED_INIT", ""}, + .{ tracy_manual_lifetime, "TRACY_MANUAL_LIFETIME", ""}, + .{ tracy_fibers, "TRACY_FIBERS", "" }, + .{ tracy_no_crash_handler, "TRACY_NO_CRASH_HANDLER", "" }, + .{ tracy_timer_fallback, "TRACY_TIMER_FALLBACK", "" }, + .{ shared and target.result.os.tag == .windows, "TRACY_EXPORTS", "" } + }) |triplet| { + const enabled, const name, const value = triplet; + if (enabled) { + mod.addCMacro(name, value); + config_header.addValue(name, []const u8, value); + } + } + + mod.addCSourceFile(.{ + .file = tracy_source_dep.path("public/TracyClient.cpp"), + .flags = &.{} + }); + + const lib = b.addLibrary(.{ + .linkage = if (shared) .dynamic else .static, + .name = "tracy", + .root_module = mod, + }); + + const include_extensions = &.{".h",".hpp"}; + lib.installHeadersDirectory(tracy_source_dep.path("public/tracy"), "tracy", .{ + .include_extensions = include_extensions, + }); + lib.installHeadersDirectory(tracy_source_dep.path("public/common"), "common", .{ + .include_extensions = include_extensions, + }); + lib.installHeadersDirectory(tracy_source_dep.path("public/client"), "client", .{ + .include_extensions = include_extensions, + }); + lib.installConfigHeader(config_header); + + b.installArtifact(lib); +} + +pub fn option( + b: *std.Build, + options: *std.Build.Step.Options, + comptime T: type, + name_raw: []const u8, + description_raw: []const u8, + default: T, +) T { + const opt = b.option(T, name_raw, description_raw) orelse default; + options.addOption(T, name_raw, opt); + return opt; +} diff --git a/libs/tracy/build.zig.zon b/libs/tracy/build.zig.zon new file mode 100644 index 0000000..aebfe68 --- /dev/null +++ b/libs/tracy/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .tracy, + .version = "0.0.1", + .minimum_zig_version = "0.15.1", + .dependencies = .{ + .tracy_source = .{ + .url = "https://github.com/wolfpld/tracy/archive/refs/tags/v0.13.0.zip", + .hash = "N-V-__8AAHW7KwHH_eNEviRWLkRfsxpWFrdOYM9f8VeCin4q", + }, + }, + .paths = .{""}, + .fingerprint = 0x255a89ee3ed46b42, +} diff --git a/src/entity_id.c b/src/entity_id.c new file mode 100644 index 0000000..7d2a3c0 --- /dev/null +++ b/src/entity_id.c @@ -0,0 +1,36 @@ +#include "entity_id.h" + +bool entity_id_eql(EntityId lhs, EntityId rhs) +{ + return lhs.packed == rhs.packed; +} + +bool entity_id_is_nil(EntityId id) +{ + return entity_id_eql(id, ENTITY_ID_NIL); +} + +uint32_t entity_id_get_index(EntityId id) +{ + return (id.packed & 0x00FFFFFF); +} + +uint8_t entity_id_get_generation(EntityId id) +{ + return (id.packed >> 24); +} + +struct UnpackedEntityId entity_id_unpack(EntityId id) +{ + return (struct UnpackedEntityId){ + .generation = entity_id_get_generation(id), + .index = entity_id_get_index(id) + }; +} + +EntityId entity_id_pack(struct UnpackedEntityId unpacked_id) +{ + return (EntityId){ + .packed = (unpacked_id.generation << 24) | (unpacked_id.index & 0x00FFFFFF) + }; +} diff --git a/src/entity_id.h b/src/entity_id.h new file mode 100644 index 0000000..c28f5dd --- /dev/null +++ b/src/entity_id.h @@ -0,0 +1,27 @@ +#pragma once + +#include "main.h" + +#define ENTITY_ID_INDEX_BITS 24 +#define ENTITY_ID_GENERATION_BITS 8 + +#define MAX_INDEX ((1 << ENTITY_ID_INDEX_BITS) - 1) +#define MAX_GENERATION ((1 << ENTITY_ID_GENERATION_BITS) - 1) + +#define ENTITY_ID_NIL (EntityId){ 0 } + +typedef struct { + uint32_t packed; +} EntityId; + +struct UnpackedEntityId { + uint32_t index; + uint8_t generation; +}; + +bool entity_id_eql(EntityId lhs, EntityId rhs); +bool entity_id_is_nil(EntityId id); +EntityId entity_id_pack(struct UnpackedEntityId unpacked_id); +struct UnpackedEntityId entity_id_unpack(EntityId id); +uint32_t entity_id_get_index(EntityId id); +uint8_t entity_id_get_generation(EntityId id); diff --git a/src/entity_list.c b/src/entity_list.c new file mode 100644 index 0000000..3543649 --- /dev/null +++ b/src/entity_list.c @@ -0,0 +1,142 @@ +#include "entity_list.h" + +void entity_list_ensure_capacity(struct EntityList *list, size_t expected_capacity) +{ + if (expected_capacity > list->capacity) { + size_t larger_capacity = MAX(MAX(list->capacity*2, 8), expected_capacity); + struct EntitySlot *larger_items = realloc(list->items, larger_capacity * sizeof(*larger_items)); + if (larger_items == NULL) { + PANIC_OOM(); + } + + for (size_t i = list->capacity; i < larger_capacity; i++) { + larger_items[i].used = false; + larger_items[i].generation = 0; + } + + list->items = larger_items; + list->capacity = larger_capacity; + } +} + +void entity_list_copy(struct EntityList *destination, struct EntityList *source) +{ + entity_list_ensure_capacity(destination, source->len); + + if (source->len > 0) { + memcpy( + destination->items, + source->items, + sizeof(*source->items) * source->len + ); + } + destination->len = source->len; +} + +struct Entity *entity_list_add(struct EntityList *list) +{ + struct Entity *result = NULL; + + for (size_t i = 1; i < list->len; i++) { + struct EntitySlot *slot = &list->items[i]; + if (!slot->used) { + slot->used = true; + result = &slot->entity; + break; + } + } + + if (result == NULL) { + uint32_t index = MAX(list->len, 1); + ASSERT(index <= MAX_INDEX); + + entity_list_ensure_capacity(list, index + 1); + list->len = index + 1; + + struct EntitySlot *slot = &list->items[index]; + ASSERT(!slot->used); + slot->used = true; + + result = &slot->entity; + } + + ASSERT(result != NULL); + memset(result, 0, sizeof(*result)); + + list->used_count++; + + return result; +} + +bool entity_list_remove(struct EntityList *list, struct Entity *entity) +{ + ASSERT(entity != NULL); + + struct EntitySlot *slot = container_of(entity, struct EntitySlot, entity); + if (!slot->used) { + return false; + } + + slot->used = false; + slot->generation++; + + ASSERT(list->used_count > 0); + list->used_count--; + + return true; +} + +bool entity_list_remove_by_id(struct EntityList *list, EntityId entity_id) +{ + struct Entity *entity = entity_list_get(list, entity_id); + if (!entity) { + return false; + } + + return entity_list_remove(list, entity); +} + +bool entity_list_exists(struct EntityList *list, EntityId entity_id) +{ + struct UnpackedEntityId unpacked = entity_id_unpack(entity_id); + if (unpacked.index >= list->len) { + return false; + } + + struct EntitySlot *entity_slot = &list->items[unpacked.index]; + if (!entity_slot->used) { + return false; + } + + return entity_slot->generation == unpacked.generation; +} + +struct Entity *entity_list_get(struct EntityList *list, EntityId entity_id) +{ + if (entity_list_exists(list, entity_id)) { + struct UnpackedEntityId unpacked = entity_id_unpack(entity_id); + return &list->items[unpacked.index].entity; + } + + return NULL; +} + +EntityId entity_list_get_id(struct EntityList *list, struct Entity *entity) +{ + ASSERT(entity != NULL); + + struct EntitySlot *slot = container_of(entity, struct EntitySlot, entity); + uint32_t offset = (void*)slot - (void*)list->items; + uint32_t index = offset / sizeof(struct EntitySlot); + + return entity_id_pack((struct UnpackedEntityId){ + .generation = slot->generation, + .index = index + }); +} + +void entity_list_free(struct EntityList *list) +{ + free(list->items); + *list = (struct EntityList){ 0 }; +} diff --git a/src/entity_list.h b/src/entity_list.h new file mode 100644 index 0000000..1f547f4 --- /dev/null +++ b/src/entity_list.h @@ -0,0 +1,37 @@ +#pragma once + +#include "main.h" +#include "entity_id.h" + +struct Entity { +}; + +struct EntitySlot { + bool used; + uint8_t generation; + struct Entity entity; +}; + +struct EntityList { + struct EntitySlot *items; + size_t len; + size_t capacity; + size_t used_count; +}; + +void entity_list_ensure_capacity(struct EntityList *list, size_t expected_capacity); +void entity_list_copy(struct EntityList *destination, struct EntityList *source); +void entity_list_free(struct EntityList *list); +struct Entity *entity_list_add(struct EntityList *list); +bool entity_list_exists(struct EntityList *list, EntityId entity_id); +struct Entity *entity_list_get(struct EntityList *list, EntityId entity_id); +EntityId entity_list_get_id(struct EntityList *list, struct Entity *entity); +bool entity_list_remove(struct EntityList *list, struct Entity *entity); +bool entity_list_remove_by_id(struct EntityList *list, EntityId entity_id); + +#define entity_list_foreach(list, _entity) for ( \ + size_t i = 1; \ + i < (list)->len && (_entity = &(list)->items[i].entity, true); \ + i++ \ + ) \ + if (!(list)->items[i].used) {} else diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..5fb3e37 --- /dev/null +++ b/src/game.c @@ -0,0 +1,37 @@ +#include "game.h" + +void game_init(struct Game *game) +{ + *game = (struct Game){ + .canvas_size = { 800, 600 } + }; +} + +void game_free(struct Game *game) +{ +} + +void game_tick(struct Game *game) +{ + TracyCZoneN(game_tick, "game_tick", true); + + Vector2 dir = { 0 }; + if (IsKeyDown(KEY_W)) { + dir.y -= 1; + } + if (IsKeyDown(KEY_S)) { + dir.y += 1; + } + if (IsKeyDown(KEY_D)) { + dir.x += 1; + } + if (IsKeyDown(KEY_A)) { + dir.x -= 1; + } + + game->player_position = Vector2Add(game->player_position, Vector2Scale(dir, 50 * game->input.dt)); + + DrawRectangleV(game->player_position, (Vector2){ 50, 50 }, RED); + + TracyCZoneEnd(game_tick) +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..bcfc568 --- /dev/null +++ b/src/game.h @@ -0,0 +1,22 @@ +#pragma once + +#include "main.h" +#include "entity_list.h" + +struct Input { + float dt; + Vector2 mouse; +}; + +struct Game { + Vector2 canvas_size; + struct EntityList entities; + + Vector2 player_position; + + struct Input input; +}; + +void game_init(struct Game *game); +void game_free(struct Game *game); +void game_tick(struct Game *game); diff --git a/src/game_debug.c b/src/game_debug.c new file mode 100644 index 0000000..9a5cbec --- /dev/null +++ b/src/game_debug.c @@ -0,0 +1,58 @@ +#include "game_debug.h" + +#ifdef IMGUI_ENABLED +#define NO_FONT_AWESOME +#include + +#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS +#include + +void game_debug_init() +{ + rlImGuiBeginInitImGui(); + + ImGuiIO *io = igGetIO_ContextPtr(igGetCurrentContext()); + io->IniFilename = NULL; + + igStyleColorsDark(NULL); + + rlImGuiEndInitImGui(); +} + +void game_debug_deinit() +{ + rlImGuiShutdown(); +} + +void game_debug_show(struct Game *game) +{ + rlImGuiBegin(); + + igSetNextWindowSize((ImVec2_c){ 400, 200 }, ImGuiCond_Once); + if (igBegin("Debug", NULL, ImGuiWindowFlags_None)) { + char label[64] = { 0 }; + snprintf(label, sizeof(label), "FPS: %.1f (%.2fms)", 1.0f / game->input.dt, game->input.dt * 1000); + igTextEx(label, NULL, ImGuiTextFlags_None); + + igTextEx("Hello, World", NULL, ImGuiTextFlags_None); + } + igEnd(); + + rlImGuiEnd(); +} + +#else + +void game_debug_init() +{ +} + +void game_debug_deinit() +{ +} + +void game_debug_show(struct Game *game) +{ +} + +#endif diff --git a/src/game_debug.h b/src/game_debug.h new file mode 100644 index 0000000..2a83a4b --- /dev/null +++ b/src/game_debug.h @@ -0,0 +1,8 @@ +#pragma once + +#include "main.h" +#include "game.h" + +void game_debug_init(); +void game_debug_deinit(); +void game_debug_show(struct Game *game); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..5286a8d --- /dev/null +++ b/src/main.c @@ -0,0 +1,133 @@ +#include "main.h" +#include "resources.h" +#include "game.h" +#include "game_debug.h" + +struct Resources g_resources = { 0 }; + +struct WindowTransform { + Vector2 offset; + float scale; +}; + +static void begin_window_scaling(struct WindowTransform *transform, Vector2 virtual_screen_size) +{ + Vector2 screen_size = { + GetScreenWidth(), + GetScreenHeight() + }; + + float scale = MIN( + screen_size.x / virtual_screen_size.x, + screen_size.y / virtual_screen_size.y + ); + transform->scale = scale; + + Vector2 filler_size = Vector2Scale(Vector2Subtract(screen_size, Vector2Scale(virtual_screen_size, transform->scale)), 0.5); + transform->offset = filler_size; + + rlPushMatrix(); + rlTranslatef( + filler_size.x, + filler_size.y, + 0 + ); + rlScalef(scale, scale, 1); +} + +static void end_window_scaling(struct WindowTransform *transform, Color color) +{ + rlPopMatrix(); + + Vector2 filler_size = transform->offset; + + DrawRectangleRec( + (struct Rectangle){ + .x = 0, + .y = 0, + .width = GetScreenWidth(), + .height = filler_size.y + }, + color + ); + + DrawRectangleRec( + (struct Rectangle){ + .x = 0, + .y = GetScreenHeight() - filler_size.y, + .width = GetScreenWidth(), + .height = filler_size.y + }, + color + ); + + DrawRectangleRec( + (struct Rectangle){ + .x = 0, + .y = 0, + .width = filler_size.x, + .height = GetScreenHeight() + }, + color + ); + + DrawRectangleRec( + (struct Rectangle){ + .x = GetScreenWidth() - filler_size.x, + .y = 0, + .width = filler_size.x, + .height = GetScreenHeight() + }, + color + ); +} + +int main(void) +{ + InitWindow(800, 600, "gaem"); + InitAudioDevice(); + SetWindowState(FLAG_WINDOW_RESIZABLE); + SetTargetFPS(60); + + resources_init(&g_resources); + + struct Game game = { 0 }; + game_init(&game); + + game_debug_init(); + + while (!WindowShouldClose()) + { + TracyCFrameMark; + + // TODO: Can this be called every frame? Will it impact performance? Needs research. + SetWindowMinSize(game.canvas_size.x, game.canvas_size.y); + + float dt = GetFrameTime(); + BeginDrawing(); + ClearBackground(RAYWHITE); + + struct WindowTransform transform = { 0 }; + begin_window_scaling(&transform, game.canvas_size); + Vector2 mouse = GetMousePosition(); + mouse = Vector2Subtract(mouse, transform.offset); + mouse = Vector2Scale(mouse, 1/transform.scale); + + game.input = (struct Input){ + .dt = dt, + .mouse = mouse + }; + game_tick(&game); + end_window_scaling(&transform, BLACK); + + game_debug_show(&game); + + EndDrawing(); + } + + game_free(&game); + game_debug_deinit(); + CloseWindow(); + + return 0; +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..1a22a23 --- /dev/null +++ b/src/main.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define INCBIN_STYLE INCBIN_STYLE_SNAKE +#define INCBIN_PREFIX +#include + +#define PANIC(message, ...) \ + do { \ + printf("%s:%d "message"\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while (false) + +#define ASSERT(condition) if (!(condition)) PANIC("Assertion failed: %s", #condition) +#define PANIC_OOM() PANIC("Out of memory") +#define UNREACHABLE() PANIC("Reached unreachable") + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) + +extern struct Resources g_resources; + +Color rgb(uint8_t r, uint8_t g, uint8_t b); +Color hex(const char *str); diff --git a/src/resources.c b/src/resources.c new file mode 100644 index 0000000..277e843 --- /dev/null +++ b/src/resources.c @@ -0,0 +1,5 @@ +#include "main.h" + +void resources_init(struct Resources *resources) +{ +} diff --git a/src/resources.h b/src/resources.h new file mode 100644 index 0000000..b6f52da --- /dev/null +++ b/src/resources.h @@ -0,0 +1,6 @@ +#pragma once + +struct Resources { +}; + +void resources_init(struct Resources *resources); diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..80952ce --- /dev/null +++ b/src/utils.c @@ -0,0 +1,39 @@ +#include "main.h" + +Color rgb(uint8_t r, uint8_t g, uint8_t b) +{ + return (Color){ + .r = r, + .g = g, + .b = b, + .a = 255 + }; +} + +static uint8_t parse_hex_nibble(char nibble) +{ + if ('0' <= nibble && nibble <= '9') { + return nibble - '0'; + } else if ('a' <= nibble && nibble <= 'f') { + return nibble - 'a'; + } else if ('A' <= nibble && nibble <= 'F') { + return nibble - 'A'; + } else { + UNREACHABLE(); + } +} + +static uint8_t parse_hex_byte(const char *two_bytes) +{ + return (parse_hex_nibble(two_bytes[0]) << 4) | parse_hex_nibble(two_bytes[1]); +} + +Color hex(const char *str) +{ + ASSERT(strlen(str) == 7); + ASSERT(str[0] == '#'); + uint8_t r = parse_hex_byte(&str[1]); + uint8_t g = parse_hex_byte(&str[3]); + uint8_t b = parse_hex_byte(&str[5]); + return rgb(r, g, b); +}