From e7c630411b71931b323e8fa5f93549fb17e1acb7 Mon Sep 17 00:00:00 2001 From: abux Date: Sat, 11 Jul 2026 18:45:05 +0100 Subject: [PATCH] HelloWorld --- .gitignore | 3 + build.zig | 66 ++++++++ build.zig.zon | 22 +++ src/assets/textures/player.png | Bin 0 -> 1613 bytes src/component/camera.zig | 0 src/component/camera_target.zig | 0 src/component/caster.zig | 2 + src/component/friction.zig | 1 + src/component/material.zig | 9 ++ src/component/player.zig | 0 src/component/projectile.zig | 0 src/component/root.zig | 19 +++ src/component/shape.zig | 0 src/component/timer.zig | 24 +++ src/component/transform.zig | 2 + src/component/velocity.zig | 1 + src/data/camera.zig | 4 + src/data/root.zig | 1 + src/event/root.zig | 0 src/main.zig | 45 ++++++ src/plugin/default.zig | 37 +++++ src/plugin/root.zig | 1 + src/resource/frame.zig | 7 + src/resource/renderer.zig | 3 + src/resource/root.zig | 8 + src/resource/textures.zig | 132 +++++++++++++++ src/resource/time.zig | 5 + src/resource/window.zig | 6 + src/system/core.zig | 107 ++++++++++++ src/system/frame.zig | 277 ++++++++++++++++++++++++++++++++ src/system/root.zig | 2 + src/template/root.zig | 1 + src/template/storage.zig | 85 ++++++++++ src/vendors/sdl3/sdl.h | 3 + 34 files changed, 873 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/assets/textures/player.png create mode 100644 src/component/camera.zig create mode 100644 src/component/camera_target.zig create mode 100644 src/component/caster.zig create mode 100644 src/component/friction.zig create mode 100644 src/component/material.zig create mode 100644 src/component/player.zig create mode 100644 src/component/projectile.zig create mode 100644 src/component/root.zig create mode 100644 src/component/shape.zig create mode 100644 src/component/timer.zig create mode 100644 src/component/transform.zig create mode 100644 src/component/velocity.zig create mode 100644 src/data/camera.zig create mode 100644 src/data/root.zig create mode 100644 src/event/root.zig create mode 100644 src/main.zig create mode 100644 src/plugin/default.zig create mode 100644 src/plugin/root.zig create mode 100644 src/resource/frame.zig create mode 100644 src/resource/renderer.zig create mode 100644 src/resource/root.zig create mode 100644 src/resource/textures.zig create mode 100644 src/resource/time.zig create mode 100644 src/resource/window.zig create mode 100644 src/system/core.zig create mode 100644 src/system/frame.zig create mode 100644 src/system/root.zig create mode 100644 src/template/root.zig create mode 100644 src/template/storage.zig create mode 100644 src/vendors/sdl3/sdl.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47e7af4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.zig-cache/ +zig-pkg/ +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..7d2e679 --- /dev/null +++ b/build.zig @@ -0,0 +1,66 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // --- TARGET --- + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // --- EXE --- + const exe = b.addExecutable(.{ + .name = "vampire_survivors", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + + .target = target, + .optimize = optimize, + + .imports = &.{}, + }), + }); + + b.installArtifact(exe); + + const run_step = b.step("run", "Run the app"); + + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const exe_tests = b.addTest(.{ + .root_module = exe.root_module, + }); + + const run_exe_tests = b.addRunArtifact(exe_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_exe_tests.step); + + // + // DEPENDENCIES + // + + const app = b.dependency("app", .{ + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("app", app.module("app")); + + // + // SYSTEM DEPENDENCIES + // + + const sdl = b.addTranslateC(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("./src/vendors/sdl3/sdl.h"), + }); + exe.root_module.addImport("sdl", sdl.createModule()); + exe.root_module.linkSystemLibrary("SDL3", .{}); + exe.root_module.linkSystemLibrary("SDL3_ttf", .{}); + exe.root_module.linkSystemLibrary("SDL3_image", .{}); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..3152acc --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,22 @@ +.{ + .name = .vampire_survivors, + + .version = "0.0.0", + + .fingerprint = 0x27782ab1dd9d7c0f, + + .minimum_zig_version = "0.16.0", + + .dependencies = .{ + .app = .{ + .url = "git+http://2.120.146.66:25569/abux/NYXA#93a4606c94f67b1f0a559c2efff3d8ff030b3300", + .hash = "app-0.0.0-AOV9_ea_AACRsUJ2lZqYiUkPed0op9qgFSWB6ihDSrll", + }, + }, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/assets/textures/player.png b/src/assets/textures/player.png new file mode 100644 index 0000000000000000000000000000000000000000..b2a6098b4553e032952b833324b5f0f98ab67049 GIT binary patch literal 1613 zcmV-T2D15yP)GrGTPFKr%;JJ(A>u~R4+<)%h@yY5 z=oh~TiXyl~P_r4cm`uzhneLhHey`-*d#9(<(}@EXJo>G-&OLQ%Y2*J}0fcO zk=gicXC_B-SOXO>56l6b?EE2cALs$2@mEi`93fct13g(J=3&nz6Ddz23v$kCe(;}23QU5x|Tkagkqr$NQiUq7b zXoPua^HSw*v(8gigqqO5f3)2AP>=+h5IDu!{0~GVn zJz*@I4%GYcb_f=*V$rDf5WGpu3|E{+(rY3B^Pw-}c9w#})jqpAT+y3bHVHuDQDswp zKa=y@L}sXB8dATIAlLo9zj(q_2L8$f^gIi9cI_S_2DSgBtGxoOYuHdb2 zI+RTU(1Kz>X{ICULvhzGyT^^WljhZaYePjb*0a9SQN<8>u?veDksH0^;0+Z%w$F*8|bI=a`?{+O-iFW*zz$owWd7QKPTA;@1ClDJqylr~1>X|gq^iuxk-K$k;2DqLF)ko^eq)h`* zFOcvwe!Z80Xb6#MVI6Uo?Eo9DxJOlQz{cW6_LtoWFp(S=3__038~o-^>6`<+z&_ zwB=rc9Cx3E`jGm)+H)FE!dz^@l*{p%mCK2pR{cWY)KwD*ZAL5zL>^a?i7JUj`TlBV zL2PKK9ECZW`Upkm1XTM|Tb9zR8VWh>fx4f}3d{$XUCy=QL9U2Ud#WWG?^O@PLQ2>S zK=b?a`Ok+GD@Ga%3`$Xd6w1A67>$ksSQktn1a-n=v@oX00000 LNkvXXu0mjfSOfg{ literal 0 HcmV?d00001 diff --git a/src/component/camera.zig b/src/component/camera.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/component/camera_target.zig b/src/component/camera_target.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/component/caster.zig b/src/component/caster.zig new file mode 100644 index 0000000..6cb2934 --- /dev/null +++ b/src/component/caster.zig @@ -0,0 +1,2 @@ +cooldown: f32 = 0.2, +current_cooldown: f32 = 0.0, diff --git a/src/component/friction.zig b/src/component/friction.zig new file mode 100644 index 0000000..ad4c0c4 --- /dev/null +++ b/src/component/friction.zig @@ -0,0 +1 @@ +value: f32 = 5.0, diff --git a/src/component/material.zig b/src/component/material.zig new file mode 100644 index 0000000..2e7a0ab --- /dev/null +++ b/src/component/material.zig @@ -0,0 +1,9 @@ +const Handle = @import("app").template.Handle; +const Texture = @import("../resource/textures.zig").Texture; + +pub const Style = union(enum) { + solid: @Vector(4, u8), + textured: Handle(Texture), +}; + +style: Style, diff --git a/src/component/player.zig b/src/component/player.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/component/projectile.zig b/src/component/projectile.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/component/root.zig b/src/component/root.zig new file mode 100644 index 0000000..6522d47 --- /dev/null +++ b/src/component/root.zig @@ -0,0 +1,19 @@ +// --- POSITION --- +pub const Transform = @import("transform.zig"); +pub const Velocity = @import("velocity.zig"); +pub const Friction = @import("friction.zig"); + +// --- STYLE --- +pub const Material = @import("material.zig"); + +// --- CAMERA --- +pub const Camera = @import("camera.zig"); +pub const CameraTarget = @import("camera_target.zig"); + +// --- TIME --- +pub const Timer = @import("timer.zig"); + +// --- MARKERS --- +pub const Player = @import("player.zig"); +pub const Caster = @import("caster.zig"); +pub const Projectile = @import("projectile.zig"); diff --git a/src/component/shape.zig b/src/component/shape.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/component/timer.zig b/src/component/timer.zig new file mode 100644 index 0000000..4ab8d3e --- /dev/null +++ b/src/component/timer.zig @@ -0,0 +1,24 @@ +const Self = @This(); + +// +// FIELDS +// + +duration: f32 = 0.0, +remaining: f32 = 0.0, + +repeat: bool = false, +finished: bool = false, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn set( + seconds: f32, + repeat: bool, +) Self { + return .{ + .duration = seconds, + .remaining = seconds, + .repeat = repeat, + }; +} diff --git a/src/component/transform.zig b/src/component/transform.zig new file mode 100644 index 0000000..811f0ec --- /dev/null +++ b/src/component/transform.zig @@ -0,0 +1,2 @@ +pos: @Vector(2, f32) = .{ 0, 0 }, +size: @Vector(2, f32) = .{ 32, 32 }, diff --git a/src/component/velocity.zig b/src/component/velocity.zig new file mode 100644 index 0000000..f2a3c70 --- /dev/null +++ b/src/component/velocity.zig @@ -0,0 +1 @@ +value: @Vector(2, f32) = .{ 0, 0 }, diff --git a/src/data/camera.zig b/src/data/camera.zig new file mode 100644 index 0000000..bf47b3e --- /dev/null +++ b/src/data/camera.zig @@ -0,0 +1,4 @@ +const component = @import("../component/root.zig"); + +value: *const component.Camera, +transform: *const component.Transform, diff --git a/src/data/root.zig b/src/data/root.zig new file mode 100644 index 0000000..fa6fe00 --- /dev/null +++ b/src/data/root.zig @@ -0,0 +1 @@ +pub const Camera = @import("camera.zig"); diff --git a/src/event/root.zig b/src/event/root.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..fc54c5b --- /dev/null +++ b/src/main.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const App = @import("app"); + +const plugin = @import("plugin/root.zig"); +const resource = @import("resource/root.zig"); +const template = @import("template/root.zig"); + +pub fn main(init: std.process.Init) !void { + // --- APP --- + var app: App = .init(init.gpa); + defer app.deinit(); + + // --- ADD DEFAULT PLUGIN & BUILD --- + try app.plugins.add(plugin.default); + try app.plugins.build(.{ .app = &app }); + + // --- LIFETIME --- + try app.schedules.run(App.stage.init, .{ .app = &app }); + defer app.schedules.run(App.stage.deinit, .{ .app = &app }) catch unreachable; + + // errdefer { + // app.schedules.run(App.stage.deinit, .{ .app = &app }) catch unreachable; + // } + + try app.schedules.run(App.stage.post_init, .{ .app = &app }); + + // --- MAIN GAME LOOP --- + while (true) { + // --- BREAK ON EXIT --- + if (app.events.consume(App.event.AppExit)) |_| { + break; + } + + // --- FRAME --- + try app.schedules.runMany(&.{ + App.stage.begin_frame, + App.stage.inputs, + App.stage.update, + App.stage.draw, + App.stage.end_frame, + }, .{ + .app = &app, + }); + } +} diff --git a/src/plugin/default.zig b/src/plugin/default.zig new file mode 100644 index 0000000..b245ebf --- /dev/null +++ b/src/plugin/default.zig @@ -0,0 +1,37 @@ +const App = @import("app"); + +const component = @import("../component/root.zig"); +const resource = @import("../resource/root.zig"); +const system = @import("../system/root.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const plugin: App.plugin.Plugin = .{ + .name = "Default", + .build = build, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn build( + _: *App.plugin.Plugin, + ctx: App.plugin.Context, +) !void { + // --- LIFETIME --- + try ctx.app.schedules.add(App.stage.init, system.core.init); + try ctx.app.schedules.add(App.stage.post_init, system.core.build); + try ctx.app.schedules.add(App.stage.deinit, system.core.deinit); + + // --- FRAME --- + try ctx.app.schedules.add(App.stage.begin_frame, system.frame.beginFrame); + try ctx.app.schedules.add(App.stage.end_frame, system.frame.endFrame); + + // --- UPDATE --- + try ctx.app.schedules.add(App.stage.inputs, system.frame.inputs); + try ctx.app.schedules.addMany(App.stage.update, &.{ + system.frame.updateTimers, + system.frame.updateTransforms, + system.frame.cast, + }); + try ctx.app.schedules.add(App.stage.draw, system.frame.draw); +} diff --git a/src/plugin/root.zig b/src/plugin/root.zig new file mode 100644 index 0000000..0c13c49 --- /dev/null +++ b/src/plugin/root.zig @@ -0,0 +1 @@ +pub const default = @import("default.zig").plugin; diff --git a/src/resource/frame.zig b/src/resource/frame.zig new file mode 100644 index 0000000..ea15294 --- /dev/null +++ b/src/resource/frame.zig @@ -0,0 +1,7 @@ +const data = @import("../data/root.zig"); + +// +// FIELDS +// + +camera: ?data.Camera = null, diff --git a/src/resource/renderer.zig b/src/resource/renderer.zig new file mode 100644 index 0000000..3b9bcfd --- /dev/null +++ b/src/resource/renderer.zig @@ -0,0 +1,3 @@ +const sdl = @import("sdl"); + +raw: *sdl.SDL_Renderer, diff --git a/src/resource/root.zig b/src/resource/root.zig new file mode 100644 index 0000000..1246d7e --- /dev/null +++ b/src/resource/root.zig @@ -0,0 +1,8 @@ +// --- CORE --- +pub const Window = @import("window.zig"); +pub const Renderer = @import("renderer.zig"); +pub const Frame = @import("frame.zig"); +pub const Time = @import("time.zig"); + +// --- ASSETS --- +pub const Textures = @import("textures.zig"); diff --git a/src/resource/textures.zig b/src/resource/textures.zig new file mode 100644 index 0000000..049f283 --- /dev/null +++ b/src/resource/textures.zig @@ -0,0 +1,132 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const Renderer = @import("../resource/renderer.zig"); +const Handle = @import("app").template.Handle; +const Storage = @import("../template/storage.zig").Storage; +const Self = @This(); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Size = union(enum) { + source, + fixed: [2]u32, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Filter = enum { + linear, + nearest, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Texture = struct { + path: []const u8, + size: [2]u32 = .{ 0, 0 }, + filter: Filter = .nearest, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const GPUTexture = struct { + raw: *sdl.SDL_Texture, +}; + +// +// FIELDS +// + +textures: Storage(Texture, GPUTexture), + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init(alloc: std.mem.Allocator) Self { + return .{ + .textures = .init(alloc), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + self.textures.deinit(); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn load( + self: *Self, + name: []const u8, + path: []const u8, +) !Handle(Texture) { + return try self.textures.put(name, .{ + .path = path, + }); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn get( + self: *Self, + value: Handle(Texture), +) ?*GPUTexture { + const record = self.textures.get(value) orelse return null; + return if (record.gpu) |*v| v else null; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn build( + self: *Self, + renderer: *const Renderer, +) !void { + for (self.textures.items()) |*record| { + // --- SKIP IF BUILD --- + if (record.gpu != null) continue; + + // --- LOAD SURFACE --- + const surface = sdl.IMG_Load( + record.cpu.path.ptr, + ) orelse { + std.debug.print("\x1b[31mFailed to load texture:\x1b[0m {s}\n", .{sdl.SDL_GetError()}); + return error.Texture; + }; + defer sdl.SDL_DestroySurface(surface); + + // --- CREATE TEXTURE --- + const texture = sdl.SDL_CreateTextureFromSurface( + renderer.raw, + surface, + ) orelse return error.Texture; + + // --- SET FILTER --- + _ = sdl.SDL_SetTextureScaleMode(texture, switch (record.cpu.filter) { + .linear => sdl.SDL_SCALEMODE_LINEAR, + .nearest => sdl.SDL_SCALEMODE_PIXELART, + }); + + // --- ASSIGN CPU --- + record.cpu.size = .{ + @intCast(surface.*.w), + @intCast(surface.*.h), + }; + + // --- ASSIGN GPU --- + record.gpu = .{ + .raw = texture, + }; + } + + // --- DEBUG --- + var it = self.textures.lookup.iterator(); + while (it.next()) |entry| { + const record = self.textures.get(entry.key_ptr.*) orelse continue; + + std.debug.print("\x1b[35m┏━ TEXTURE\n", .{}); + std.debug.print("\x1b[35m┃\x1b[0m Name: \"{s}\"\n", .{entry.value_ptr.*}); + std.debug.print("\x1b[35m┃\x1b[0m Path: \"{s}\"\n", .{record.cpu.path}); + std.debug.print("\x1b[35m┃\x1b[0m Size: {}x{}\n", .{ record.cpu.size[0], record.cpu.size[1] }); + std.debug.print("\x1b[35m┗━\x1b[0m\n", .{}); + } +} diff --git a/src/resource/time.zig b/src/resource/time.zig new file mode 100644 index 0000000..9cd9ee0 --- /dev/null +++ b/src/resource/time.zig @@ -0,0 +1,5 @@ +dt: f32 = 0.0, +now: u64 = 0, +last: u64 = 0, +frequency: u64 = 0, +target_dt: f32 = 1.0 / 60.0, diff --git a/src/resource/window.zig b/src/resource/window.zig new file mode 100644 index 0000000..cd19cc5 --- /dev/null +++ b/src/resource/window.zig @@ -0,0 +1,6 @@ +const sdl = @import("sdl"); + +title: []const u8 = "Vampire Survivors", +width: u32 = 800, +height: u32 = 600, +raw: *sdl.SDL_Window, diff --git a/src/system/core.zig b/src/system/core.zig new file mode 100644 index 0000000..72322ee --- /dev/null +++ b/src/system/core.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const App = @import("app"); + +const component = @import("../component/root.zig"); +const resource = @import("../resource/root.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init(ctx: App.scheduler.Context) !void { + // --- SDL3 INIT --- + if (!sdl.SDL_Init(sdl.SDL_INIT_VIDEO)) { + return error.SDL_Init; + } + + // --- WINDOW --- + const window = try ctx.app.resources.getOrSet(resource.Window{ + .raw = sdl.SDL_CreateWindow( + "Vampire Survivors", + 800, + 600, + sdl.SDL_WINDOW_RESIZABLE | sdl.SDL_WINDOW_HIGH_PIXEL_DENSITY, + ) orelse return error.Window, + }); + + // --- RENDERER --- + try ctx.app.resources.set(resource.Renderer{ + .raw = sdl.SDL_CreateRenderer( + window.raw, + null, + ) orelse return error.Renderer, + }); + + // --- FRAME --- + try ctx.app.resources.set(resource.Time{ + .now = sdl.SDL_GetPerformanceCounter(), + .last = sdl.SDL_GetPerformanceCounter(), + .frequency = sdl.SDL_GetPerformanceFrequency(), + }); + try ctx.app.resources.set(resource.Frame{}); + + // --- ASSETS --- + const textures = try ctx.app.resources.getOrSet(resource.Textures.init(ctx.app.alloc)); + + // --- DEBUG --- + std.debug.print("\x1b[35m┏━ WINDOW\n", .{}); + std.debug.print("\x1b[35m┃\x1b[0m Title: \"{s}\"\n", .{window.title}); + std.debug.print("\x1b[35m┃\x1b[0m Size: {}x{}\n", .{ window.width, window.height }); + std.debug.print("\x1b[35m┗━\x1b[0m\n", .{}); + + { // ENTITIES + // --- CAMERA --- + try ctx.app.world.spawn(.{ + component.Camera{}, + component.Transform{}, + }); + + // --- PLAYER --- + try ctx.app.world.spawn(.{ + component.Player{}, + component.Caster{}, + + component.Friction{}, + component.Velocity{}, + component.Transform{ .size = .{ 200, 200 } }, + component.Material{ .style = .{ + .textured = try textures.load("Player", "src/assets/textures/player.png"), + } }, + component.CameraTarget{}, + }); + + try ctx.app.world.spawn(.{ + component.Transform{ .pos = .{ 100, 100 } }, + component.Material{ .style = .{ + .solid = .{ 20, 200, 20, 255 }, + } }, + }); + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + window: *resource.Window, + renderer: *resource.Renderer, + textures: *resource.Textures, + }); + + res.textures.deinit(); + sdl.SDL_DestroyRenderer(res.renderer.raw); + sdl.SDL_DestroyWindow(res.window.raw); + sdl.SDL_Quit(); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn build(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + renderer: *resource.Renderer, + textures: *resource.Textures, + }); + + try res.textures.build( + res.renderer, + ); +} diff --git a/src/system/frame.zig b/src/system/frame.zig new file mode 100644 index 0000000..6b6606e --- /dev/null +++ b/src/system/frame.zig @@ -0,0 +1,277 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const App = @import("app"); + +const component = @import("../component/root.zig"); +const resource = @import("../resource/root.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn inputs(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + window: *resource.Window, + renderer: *resource.Renderer, + }); + _ = res; + + // --- POLL EVENTS --- + var raw_event: sdl.SDL_Event = undefined; + while (sdl.SDL_PollEvent(&raw_event)) { + switch (raw_event.type) { + sdl.SDL_EVENT_QUIT => try ctx.app.events.send(App.event.AppExit{}), + else => {}, + } + } + + const keyboard: [*c]const bool = sdl.SDL_GetKeyboardState(null); + + var query = ctx.app.world.query(struct { + player: *component.Player, + velocity: *component.Velocity, + }, .{}); + while (query.next()) |e| { + const speed: f32 = 100.0; + if (keyboard[sdl.SDL_SCANCODE_W]) e.velocity.value[1] = -speed; + if (keyboard[sdl.SDL_SCANCODE_S]) e.velocity.value[1] = speed; + if (keyboard[sdl.SDL_SCANCODE_A]) e.velocity.value[0] = -speed; + if (keyboard[sdl.SDL_SCANCODE_D]) e.velocity.value[0] = speed; + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn beginFrame(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + window: *resource.Window, + frame: *resource.Frame, + time: *resource.Time, + }); + + // + // TIME + // + + res.time.last = res.time.now; + res.time.now = sdl.SDL_GetPerformanceCounter(); + + res.time.dt = + @as(f32, @floatFromInt(res.time.now - res.time.last)) / + @as(f32, @floatFromInt(res.time.frequency)); + + // + // CAMERA + // + + var target_transform: ?*const component.Transform = null; + + { // GATHER CAMERAS TARGET + var query = ctx.app.world.query(struct { + transform: *component.Transform, + camera_target: ?*component.CameraTarget, + }, .{}); + while (query.next()) |e| { + if (e.camera_target) |_| { + target_transform = e.transform; + } + } + } + + { // GATHER CAMERAS + var query = ctx.app.world.query(struct { + camera: *component.Camera, + transform: *component.Transform, + }, .{}); + while (query.next()) |e| { + const window_width: f32 = @floatFromInt(res.window.width); + const window_height: f32 = @floatFromInt(res.window.height); + const target = if (target_transform) |v| v else continue; + + e.transform.pos[0] += ((window_width / 2.0) - (target.pos[0] + target.size[0] / 2.0) - e.transform.pos[0]) / 20.0; + e.transform.pos[1] += ((window_height / 2.0) - (target.pos[1] + target.size[1] / 2.0) - e.transform.pos[1]) / 20.0; + + // --- SET CURRENT FRAME CAMERA --- + res.frame.camera = .{ + .value = e.camera, + .transform = e.transform, + }; + } + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn endFrame(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + frame: *resource.Frame, + time: *resource.Time, + }); + + res.frame.camera = null; + + const frame_time = + @as(f32, @floatFromInt(sdl.SDL_GetPerformanceCounter() - res.time.now)) / + @as(f32, @floatFromInt(res.time.frequency)); + + if (frame_time < res.time.target_dt) { + const delay_ms = (res.time.target_dt - frame_time) * 1000.0; + sdl.SDL_Delay(@intFromFloat(delay_ms)); + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn updateTransforms(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + time: *resource.Time, + }); + + // --- QUERY --- + var query = ctx.app.world.query(struct { + transform: *component.Transform, + velocity: *component.Velocity, + friction: ?*component.Friction, + }, .{}); + while (query.next()) |e| { + // --- APPLY VELOCITY --- + e.transform.pos[0] += e.velocity.value[0] * res.time.dt; + e.transform.pos[1] += e.velocity.value[1] * res.time.dt; + + // --- APPLY FRICTION --- + if (e.friction) |friction| { + const damping = std.math.exp(-friction.value * res.time.dt); + e.velocity.value[0] *= damping; + e.velocity.value[1] *= damping; + } + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn updateTimers(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + time: *resource.Time, + }); + + var query = ctx.app.world.query(struct { + timer: *component.Timer, + }, .{}); + while (query.next()) |e| { + // --- # --- + if (e.timer.finished and !e.timer.repeat) + continue; + + // --- PROGRESS --- + e.timer.remaining -= + res.time.dt; + + if (e.timer.remaining <= 0.0) { + e.timer.finished = true; + + if (e.timer.repeat) { + e.timer.remaining = e.timer.duration; + e.timer.finished = false; + } + } + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn cast(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + time: *resource.Time, + }); + + // --- QUERY --- + var query = ctx.app.world.query(struct { + caster: *component.Caster, + transform: *component.Transform, + }, .{}); + while (query.next()) |e| { + // --- SUBTRACT COOLDOWN --- + e.caster.current_cooldown -= + @max(0, res.time.dt); + + // --- SKIP IF ON COOLDOWN --- + if (e.caster.current_cooldown >= 0) + continue; + + // --- SPAWN PROJECTILE --- + try ctx.app.world.spawn(.{ + component.Transform{ + .pos = e.transform.pos, + }, + component.Velocity{ + .value = .{ 400, 0 }, + }, + component.Material{ + .style = .{ .solid = .{ 200, 20, 20, 255 } }, + }, + component.Projectile{}, + }); + + // --- SET COOLDOWN --- + e.caster.current_cooldown = + e.caster.cooldown; + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn draw(ctx: App.scheduler.Context) !void { + const res = try ctx.app.resources.getMany(struct { + window: *resource.Window, + renderer: *resource.Renderer, + textures: *resource.Textures, + frame: *resource.Frame, + }); + + // --- SET CLEAR COLOR --- + _ = sdl.SDL_SetRenderDrawColor( + res.renderer.raw, + 10, + 10, + 10, + 255, + ); + + // --- DRAW --- + _ = sdl.SDL_RenderClear(res.renderer.raw); + defer _ = sdl.SDL_RenderPresent(res.renderer.raw); + + { // DRAW + var query = ctx.app.world.query(struct { + transform: *component.Transform, + material: *component.Material, + camera_target: ?*component.CameraTarget, + }, .{}); + while (query.next()) |e| { + const camera = if (res.frame.camera) |*v| v else continue; + + // --- SHAPE --- + var rect: sdl.SDL_FRect = .{ + .x = e.transform.pos[0] + camera.transform.pos[0], + .y = e.transform.pos[1] + camera.transform.pos[1], + .w = e.transform.size[0], + .h = e.transform.size[1], + }; + + // --- MATERIAL --- + switch (e.material.style) { + .solid => |albedo| { + _ = sdl.SDL_SetRenderDrawColor(res.renderer.raw, albedo[0], albedo[1], albedo[2], albedo[3]); + _ = sdl.SDL_RenderFillRect(res.renderer.raw, &rect); + }, + .textured => |handle| { + const gpu = res.textures.get(handle) orelse continue; + _ = sdl.SDL_RenderTexture( + res.renderer.raw, + gpu.raw, + null, + &rect, + ); + }, + } + } + } +} diff --git a/src/system/root.zig b/src/system/root.zig new file mode 100644 index 0000000..0329ef8 --- /dev/null +++ b/src/system/root.zig @@ -0,0 +1,2 @@ +pub const core = @import("core.zig"); +pub const frame = @import("frame.zig"); diff --git a/src/template/root.zig b/src/template/root.zig new file mode 100644 index 0000000..883557a --- /dev/null +++ b/src/template/root.zig @@ -0,0 +1 @@ +pub const Storage = @import("storage.zig").Storage; diff --git a/src/template/storage.zig b/src/template/storage.zig new file mode 100644 index 0000000..1932ed5 --- /dev/null +++ b/src/template/storage.zig @@ -0,0 +1,85 @@ +const std = @import("std"); +const App = @import("app"); +const Handle = App.template.Handle; + +pub fn Storage( + comptime CPU: type, + comptime GPU: type, +) type { + return struct { + const Self = @This(); + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub const Record = struct { + cpu: CPU, + gpu: ?GPU = null, + }; + + // + // FIELDS + // + + data: std.ArrayList(Record), + lookup: std.AutoHashMap(Handle(CPU), []const u8), + alloc: std.mem.Allocator, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn init(alloc: std.mem.Allocator) Self { + return .{ + .data = .empty, + .lookup = .init(alloc), + .alloc = alloc, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn deinit(self: *Self) void { + self.data.deinit(self.alloc); + self.lookup.deinit(); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn put( + self: *Self, + name: []const u8, + value: CPU, + ) !Handle(CPU) { + // --- HANDLE --- + const handle: Handle(CPU) = .{ + .idx = @intCast(self.data.items.len), + }; + + // --- ADD DATA --- + try self.data.append(self.alloc, .{ .cpu = value }); + errdefer _ = self.data.swapRemove(self.data.items.len - 1); + + // --- ADD LOOKUP --- + try self.lookup.put( + handle, + name, + ); + + return handle; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn get( + self: *Self, + handle: Handle(CPU), + ) ?*Record { + if (!self.lookup.contains(handle)) return null; + return &self.data.items[@intCast(handle.idx)]; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn items(self: *Self) []Record { + return self.data.items; + } + }; +} diff --git a/src/vendors/sdl3/sdl.h b/src/vendors/sdl3/sdl.h new file mode 100644 index 0000000..1370e44 --- /dev/null +++ b/src/vendors/sdl3/sdl.h @@ -0,0 +1,3 @@ +#include +#include +#include