Base commit
This commit is contained in:
commit
0c04307db8
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.zig-cache
|
||||||
|
zig-out
|
||||||
52
build.zig
Normal file
52
build.zig
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const lua_dep = b.dependency("zlua", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const raylib_dep = b.dependency("raylib_zig", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "engine",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.imports = &.{},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
exe.root_module.linkLibrary(raylib_dep.artifact("raylib"));
|
||||||
|
exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
||||||
|
exe.root_module.addImport("zlua", lua_dep.module("zlua"));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
21
build.zig.zon
Normal file
21
build.zig.zon
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.{
|
||||||
|
.name = .engine,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0xe8a81a8d12d08408, // Changing this has security and trust implications.
|
||||||
|
.minimum_zig_version = "0.15.2",
|
||||||
|
.dependencies = .{
|
||||||
|
.zlua = .{
|
||||||
|
.url = "git+https://github.com/natecraddock/ziglua#9f5f3db7ed893000e44badc073e0f3632b731021",
|
||||||
|
.hash = "zlua-0.1.0-hGRpC-xTBQDwe5Mu1zKV5SB2VOY9AmSco05_vurY5jGh",
|
||||||
|
},
|
||||||
|
.raylib_zig = .{
|
||||||
|
.url = "git+https://github.com/raylib-zig/raylib-zig#1e257d1738b4ee25fe76ea1b1bd8b5ea2bf639c4",
|
||||||
|
.hash = "raylib_zig-5.6.0-dev-KE8RECxEBQDwM9vp3RY9uvOduewuez61dojPyNO_L9Ph",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
||||||
34
scripts/box.lua
Normal file
34
scripts/box.lua
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-- scripts/box.lua
|
||||||
|
-- Autonomous bouncing box.
|
||||||
|
-- Edit vx, vy, or the color below, save the file, and watch it update live!
|
||||||
|
|
||||||
|
local vx, vy = 2050, 110 -- ← try cranking these up
|
||||||
|
local size = 60
|
||||||
|
local r, g, b = 255, 180, 50 -- ← golden amber box
|
||||||
|
|
||||||
|
function on_init()
|
||||||
|
log("Box ready – bouncing at vx=" .. vx .. " vy=" .. vy)
|
||||||
|
end
|
||||||
|
|
||||||
|
function on_update(dt)
|
||||||
|
self_x = self_x + vx * dt
|
||||||
|
self_y = self_y + vy * dt
|
||||||
|
|
||||||
|
-- Bounce off the four walls (respecting the editor panel)
|
||||||
|
if self_x < 0 or self_x + size > 1055 then
|
||||||
|
vx = -vx
|
||||||
|
self_x = math.max(0, math.min(1055 - size, self_x))
|
||||||
|
end
|
||||||
|
if self_y < 0 or self_y + size > 720 then
|
||||||
|
vy = -vy
|
||||||
|
self_y = math.max(0, math.min(720 - size, self_y))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function on_render()
|
||||||
|
draw_rect(self_x, self_y, size, size, r, g, b)
|
||||||
|
|
||||||
|
-- Velocity debug overlay
|
||||||
|
local speed_label = "v=" .. math.floor(math.sqrt(vx * vx + vy * vy))
|
||||||
|
draw_text(speed_label, self_x + 4, self_y + size / 2 - 6, 12, 0, 0, 0)
|
||||||
|
end
|
||||||
42
scripts/player.lua
Normal file
42
scripts/player.lua
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
-- scripts/player.lua
|
||||||
|
-- Globals provided each frame by the engine:
|
||||||
|
-- self_x, self_y ← read AND write to move the node
|
||||||
|
-- API: draw_rect draw_circle draw_text draw_line
|
||||||
|
-- key_down key_pressed mouse_down mouse_x mouse_y
|
||||||
|
-- log(msg)
|
||||||
|
|
||||||
|
local speed = 200
|
||||||
|
local width = 40
|
||||||
|
local height = 50
|
||||||
|
local cr, cg, cb = 80, 160, 255 -- ← try changing this color and saving!
|
||||||
|
|
||||||
|
function on_init()
|
||||||
|
log("Player init at " .. self_x .. ", " .. self_y)
|
||||||
|
-- self_x/self_y already hold the last known position,
|
||||||
|
-- so hot-reload doesn't teleport the player back to origin.
|
||||||
|
end
|
||||||
|
|
||||||
|
function on_update(dt)
|
||||||
|
if key_down(Key.RIGHT) or key_down(Key.D) then self_x = self_x + speed * dt end
|
||||||
|
if key_down(Key.LEFT) or key_down(Key.A) then self_x = self_x - speed * dt end
|
||||||
|
if key_down(Key.DOWN) or key_down(Key.S) then self_y = self_y + speed * dt end
|
||||||
|
if key_down(Key.UP) or key_down(Key.W) then self_y = self_y - speed * dt end
|
||||||
|
|
||||||
|
-- Clamp to game area (leave the editor panel free)
|
||||||
|
self_x = math.max(0, math.min(1055, self_x))
|
||||||
|
self_y = math.max(0, math.min(665, self_y))
|
||||||
|
end
|
||||||
|
|
||||||
|
function on_render()
|
||||||
|
-- Body
|
||||||
|
draw_rect(self_x, self_y, width, height, cr, cg, cb)
|
||||||
|
|
||||||
|
-- Eyes (white + pupil)
|
||||||
|
draw_circle(self_x + 11, self_y + 14, 6, 255, 255, 255)
|
||||||
|
draw_circle(self_x + 29, self_y + 14, 6, 255, 255, 255)
|
||||||
|
draw_circle(self_x + 13, self_y + 14, 3, 0, 0, 0)
|
||||||
|
draw_circle(self_x + 31, self_y + 14, 3, 0, 0, 0)
|
||||||
|
|
||||||
|
-- Label
|
||||||
|
draw_text("Player", self_x, self_y - 16, 11, 180, 220, 255)
|
||||||
|
end
|
||||||
61
src/Engine.zig
Normal file
61
src/Engine.zig
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const rl = @import("raylib");
|
||||||
|
const Node = @import("Node.zig");
|
||||||
|
|
||||||
|
const Engine = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
nodes: std.ArrayList(Node) = .{},
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !Engine {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Engine) void {
|
||||||
|
for (self.nodes.items) |*n| n.deinit();
|
||||||
|
self.nodes.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addNode(self: *Engine, name: []const u8, script: []const u8) !void {
|
||||||
|
const node = try Node.init(self.allocator, name, script);
|
||||||
|
try self.nodes.append(self.allocator, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll every node's file watcher; reload changed scripts.
|
||||||
|
pub fn checkReloads(self: *Engine) !void {
|
||||||
|
for (self.nodes.items) |*node| {
|
||||||
|
if (node.watcher.poll()) try node.loadScript();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(self: *Engine, dt: f32) void {
|
||||||
|
for (self.nodes.items) |*n| n.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(self: *Engine) void {
|
||||||
|
for (self.nodes.items) |*n| n.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Editor overlay: simple node-list panel on the right edge.
|
||||||
|
pub fn drawEditorUI(self: *Engine) void {
|
||||||
|
const px: i32 = 1280 - 220;
|
||||||
|
rl.drawRectangle(px, 0, 220, 720, .{ .r = 20, .g = 20, .b = 20, .a = 210 });
|
||||||
|
rl.drawText("NODES", px + 10, 10, 12, rl.Color.gray);
|
||||||
|
|
||||||
|
for (self.nodes.items, 0..) |*node, i| {
|
||||||
|
const y: i32 = 32 + @as(i32, @intCast(i)) * 44;
|
||||||
|
rl.drawRectangle(px + 4, y, 212, 38, .{ .r = 45, .g = 45, .b = 45, .a = 255 });
|
||||||
|
|
||||||
|
const name_z = std.fmt.allocPrintSentinel(self.allocator, "{s}", .{node.name}, 0) catch continue;
|
||||||
|
defer self.allocator.free(name_z);
|
||||||
|
rl.drawText(name_z, px + 10, y + 4, 13, rl.Color.white);
|
||||||
|
|
||||||
|
const path_z = std.fmt.allocPrintSentinel(self.allocator, "{s}", .{node.script_path}, 0) catch continue;
|
||||||
|
defer self.allocator.free(path_z);
|
||||||
|
rl.drawText(path_z, px + 10, y + 22, 10, rl.Color.light_gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.drawText("Edit .lua to hot reload", px + 6, 700, 9, rl.Color.dark_gray);
|
||||||
|
}
|
||||||
29
src/FileWatcher.zig
Normal file
29
src/FileWatcher.zig
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Polls a single file's mtime to detect saves.
|
||||||
|
/// Cross-platform — works on Linux, macOS, Windows.
|
||||||
|
const FileWatcher = @This();
|
||||||
|
|
||||||
|
path: []const u8,
|
||||||
|
last_mtime: i128 = 0,
|
||||||
|
|
||||||
|
pub fn init(path: []const u8) FileWatcher {
|
||||||
|
var w = FileWatcher{ .path = path };
|
||||||
|
w.last_mtime = w.getMtime() catch 0;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true (and updates internal mtime) if the file changed.
|
||||||
|
pub fn poll(self: *FileWatcher) bool {
|
||||||
|
const mtime = self.getMtime() catch return false;
|
||||||
|
if (mtime != self.last_mtime) {
|
||||||
|
self.last_mtime = mtime;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getMtime(self: *const FileWatcher) !i128 {
|
||||||
|
const stat = try std.fs.cwd().statFile(self.path);
|
||||||
|
return stat.mtime;
|
||||||
|
}
|
||||||
118
src/Node.zig
Normal file
118
src/Node.zig
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const zlua = @import("zlua");
|
||||||
|
const Lua = zlua.Lua;
|
||||||
|
const lua_api = @import("lua_api.zig");
|
||||||
|
const FileWatcher = @import("FileWatcher.zig");
|
||||||
|
|
||||||
|
/// A scene node with an attached Lua script.
|
||||||
|
/// Each node owns a dedicated Lua state → scripts are fully isolated.
|
||||||
|
const Node = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
name: []const u8,
|
||||||
|
script_path: []const u8,
|
||||||
|
lua: *Lua,
|
||||||
|
watcher: FileWatcher,
|
||||||
|
|
||||||
|
// Node transform — synced to/from Lua as self_x, self_y globals
|
||||||
|
x: f64 = 400,
|
||||||
|
y: f64 = 300,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
name: []const u8,
|
||||||
|
script_path: []const u8,
|
||||||
|
) !Node {
|
||||||
|
const lua = try Lua.init(allocator);
|
||||||
|
lua.openLibs();
|
||||||
|
lua_api.registerAll(lua); // expose engine API to this state
|
||||||
|
|
||||||
|
var node = Node{
|
||||||
|
.allocator = allocator,
|
||||||
|
.name = name,
|
||||||
|
.script_path = script_path,
|
||||||
|
.lua = lua,
|
||||||
|
.watcher = FileWatcher.init(script_path),
|
||||||
|
};
|
||||||
|
try node.loadScript();
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Node) void {
|
||||||
|
self.lua.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load (or hot-reload) the Lua script from disk.
|
||||||
|
pub fn loadScript(self: *Node) !void {
|
||||||
|
std.log.info("(re)loading {s}", .{self.script_path});
|
||||||
|
|
||||||
|
const path_z = try self.allocator.dupeZ(u8, self.script_path);
|
||||||
|
defer self.allocator.free(path_z);
|
||||||
|
|
||||||
|
self.lua.doFile(path_z) catch |err| {
|
||||||
|
const msg = self.lua.toString(-1) catch "?";
|
||||||
|
std.log.err("[{s}] load error: {s} ({})", .{ self.script_path, msg, err });
|
||||||
|
self.lua.pop(1);
|
||||||
|
return; // keep running with old code rather than crashing
|
||||||
|
};
|
||||||
|
|
||||||
|
self.syncToLua(); // give on_init() the current position
|
||||||
|
self.callVoid("on_init");
|
||||||
|
self.syncFromLua();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called every frame. Passes dt as an argument.
|
||||||
|
pub fn update(self: *Node, dt: f32) void {
|
||||||
|
self.syncToLua();
|
||||||
|
const t = self.lua.getGlobal("on_update") catch return;
|
||||||
|
if (t != .function) {
|
||||||
|
self.lua.pop(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.lua.pushNumber(@floatCast(dt));
|
||||||
|
self.lua.protectedCall(.{ .args = 1 }) catch |err| self.logErr("on_update", err);
|
||||||
|
self.syncFromLua();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called every frame after update.
|
||||||
|
pub fn render(self: *Node) void {
|
||||||
|
self.syncToLua();
|
||||||
|
self.callVoid("on_render");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── internals ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Write node state into Lua globals so scripts can read/modify them.
|
||||||
|
fn syncToLua(self: *Node) void {
|
||||||
|
self.lua.pushNumber(self.x);
|
||||||
|
self.lua.setGlobal("self_x");
|
||||||
|
self.lua.pushNumber(self.y);
|
||||||
|
self.lua.setGlobal("self_y");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read back any position changes the script made.
|
||||||
|
fn syncFromLua(self: *Node) void {
|
||||||
|
_ = self.lua.getGlobal("self_x") catch {};
|
||||||
|
self.x = self.lua.toNumber(-1) catch self.x;
|
||||||
|
self.lua.pop(1);
|
||||||
|
|
||||||
|
_ = self.lua.getGlobal("self_y") catch {};
|
||||||
|
self.y = self.lua.toNumber(-1) catch self.y;
|
||||||
|
self.lua.pop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a zero-arg Lua function by name (no-op if it doesn't exist).
|
||||||
|
fn callVoid(self: *Node, name: [:0]const u8) void {
|
||||||
|
const t = self.lua.getGlobal(name) catch return;
|
||||||
|
if (t != .function) {
|
||||||
|
self.lua.pop(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.lua.protectedCall(.{}) catch |err| self.logErr(name, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logErr(self: *Node, ctx: []const u8, err: anyerror) void {
|
||||||
|
const msg = self.lua.toString(-1) catch "?";
|
||||||
|
std.log.err("[{s}] {s}: {s} ({})", .{ self.script_path, ctx, msg, err });
|
||||||
|
self.lua.pop(1);
|
||||||
|
}
|
||||||
132
src/lua_api.zig
Normal file
132
src/lua_api.zig
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const rl = @import("raylib");
|
||||||
|
const zlua = @import("zlua");
|
||||||
|
const Lua = zlua.Lua;
|
||||||
|
|
||||||
|
/// Register all engine API functions + constants into a Lua state.
|
||||||
|
pub fn registerAll(lua: *Lua) void {
|
||||||
|
// Drawing
|
||||||
|
reg(lua, "draw_rect", drawRect);
|
||||||
|
reg(lua, "draw_circle", drawCircle);
|
||||||
|
reg(lua, "draw_text", drawText);
|
||||||
|
reg(lua, "draw_line", drawLine);
|
||||||
|
// Input
|
||||||
|
reg(lua, "key_down", keyDown);
|
||||||
|
reg(lua, "key_pressed", keyPressed);
|
||||||
|
reg(lua, "mouse_down", mouseDown);
|
||||||
|
reg(lua, "mouse_x", mouseX);
|
||||||
|
reg(lua, "mouse_y", mouseY);
|
||||||
|
// Util
|
||||||
|
reg(lua, "log", luaLog);
|
||||||
|
|
||||||
|
// Key constants table: Key.LEFT, Key.W, Key.SPACE, ...
|
||||||
|
lua.newTable();
|
||||||
|
keyConst(lua, "LEFT", .left);
|
||||||
|
keyConst(lua, "RIGHT", .right);
|
||||||
|
keyConst(lua, "UP", .up);
|
||||||
|
keyConst(lua, "DOWN", .down);
|
||||||
|
keyConst(lua, "SPACE", .space);
|
||||||
|
keyConst(lua, "W", .w);
|
||||||
|
keyConst(lua, "A", .a);
|
||||||
|
keyConst(lua, "S", .s);
|
||||||
|
keyConst(lua, "D", .d);
|
||||||
|
lua.setGlobal("Key");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reg(lua: *Lua, name: [:0]const u8, comptime f: fn (*Lua) i32) void {
|
||||||
|
lua.pushFunction(zlua.wrap(f));
|
||||||
|
lua.setGlobal(name);
|
||||||
|
}
|
||||||
|
fn keyConst(lua: *Lua, name: [:0]const u8, key: rl.KeyboardKey) void {
|
||||||
|
_ = lua.pushString(name);
|
||||||
|
lua.pushInteger(@intFromEnum(key));
|
||||||
|
lua.setTable(-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drawing ────────────────────────────────────────────────────────────────
|
||||||
|
// Helpers to read color args from Lua stack at a given base index
|
||||||
|
fn argColor(lua: *Lua, base: i32) rl.Color {
|
||||||
|
return .{
|
||||||
|
.r = @intFromFloat(lua.toNumber(base) catch 255),
|
||||||
|
.g = @intFromFloat(lua.toNumber(base + 1) catch 255),
|
||||||
|
.b = @intFromFloat(lua.toNumber(base + 2) catch 255),
|
||||||
|
.a = @intFromFloat(lua.toNumber(base + 3) catch 255),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// draw_rect(x, y, w, h, r, g, b [,a])
|
||||||
|
fn drawRect(lua: *Lua) i32 {
|
||||||
|
rl.drawRectangle(
|
||||||
|
@intFromFloat(lua.toNumber(1) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(2) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(3) catch 10),
|
||||||
|
@intFromFloat(lua.toNumber(4) catch 10),
|
||||||
|
argColor(lua, 5),
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// draw_circle(x, y, radius, r, g, b [,a])
|
||||||
|
fn drawCircle(lua: *Lua) i32 {
|
||||||
|
rl.drawCircleV(.{
|
||||||
|
.x = @floatCast(lua.toNumber(1) catch 0),
|
||||||
|
.y = @floatCast(lua.toNumber(2) catch 0),
|
||||||
|
}, @floatCast(lua.toNumber(3) catch 10), argColor(lua, 4));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// draw_text(text, x, y, size, r, g, b [,a])
|
||||||
|
fn drawText(lua: *Lua) i32 {
|
||||||
|
const text = lua.toString(1) catch return 0;
|
||||||
|
rl.drawText(
|
||||||
|
text,
|
||||||
|
@intFromFloat(lua.toNumber(2) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(3) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(4) catch 20),
|
||||||
|
argColor(lua, 5),
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// draw_line(x1, y1, x2, y2, r, g, b [,a])
|
||||||
|
fn drawLine(lua: *Lua) i32 {
|
||||||
|
rl.drawLine(
|
||||||
|
@intFromFloat(lua.toNumber(1) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(2) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(3) catch 0),
|
||||||
|
@intFromFloat(lua.toNumber(4) catch 0),
|
||||||
|
argColor(lua, 5),
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Input ─────────────────────────────────────────────────────────────────
|
||||||
|
// key_down(Key.X) → bool
|
||||||
|
fn keyDown(lua: *Lua) i32 {
|
||||||
|
const k = lua.toInteger(1) catch return 0;
|
||||||
|
lua.pushBoolean(rl.isKeyDown(@enumFromInt(k)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// key_pressed(Key.X) → bool (true only on the frame it was pressed)
|
||||||
|
fn keyPressed(lua: *Lua) i32 {
|
||||||
|
const k = lua.toInteger(1) catch return 0;
|
||||||
|
lua.pushBoolean(rl.isKeyPressed(@enumFromInt(k)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// mouse_down(0|1|2) → bool 0=left 1=right 2=middle
|
||||||
|
fn mouseDown(lua: *Lua) i32 {
|
||||||
|
const b = lua.toInteger(1) catch 0;
|
||||||
|
lua.pushBoolean(rl.isMouseButtonDown(@enumFromInt(b)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fn mouseX(lua: *Lua) i32 {
|
||||||
|
lua.pushNumber(@floatFromInt(rl.getMouseX()));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fn mouseY(lua: *Lua) i32 {
|
||||||
|
lua.pushNumber(@floatFromInt(rl.getMouseY()));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Util ──────────────────────────────────────────────────────────────────
|
||||||
|
fn luaLog(lua: *Lua) i32 {
|
||||||
|
const msg = lua.toString(1) catch "nil";
|
||||||
|
std.log.info("[lua] {s}", .{msg});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
36
src/main.zig
Normal file
36
src/main.zig
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const rl = @import("raylib");
|
||||||
|
const Engine = @import("Engine.zig");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
rl.initWindow(1280, 720, "Zig + Raylib + Lua Engine");
|
||||||
|
rl.setTargetFPS(60);
|
||||||
|
defer rl.closeWindow();
|
||||||
|
|
||||||
|
var engine = try Engine.init(allocator);
|
||||||
|
defer engine.deinit();
|
||||||
|
|
||||||
|
// Attach Lua scripts to nodes — add as many as you like
|
||||||
|
try engine.addNode("Player", "scripts/player.lua");
|
||||||
|
try engine.addNode("Box", "scripts/box.lua");
|
||||||
|
|
||||||
|
while (!rl.windowShouldClose()) {
|
||||||
|
const dt = rl.getFrameTime();
|
||||||
|
|
||||||
|
// Poll file mtimes — reloads any script that changed on disk
|
||||||
|
try engine.checkReloads();
|
||||||
|
|
||||||
|
engine.update(dt); // calls on_update(dt) in each script
|
||||||
|
|
||||||
|
rl.beginDrawing();
|
||||||
|
defer rl.endDrawing();
|
||||||
|
rl.clearBackground(rl.Color.black);
|
||||||
|
|
||||||
|
engine.render(); // calls on_render() in each script
|
||||||
|
engine.drawEditorUI(); // node-list panel (right side)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user