Basic multi node type
This commit is contained in:
parent
0c04307db8
commit
0ab4a47ebc
@ -1,34 +0,0 @@
|
|||||||
-- 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
|
|
||||||
46
scripts/enemy_rect.lua
Normal file
46
scripts/enemy_rect.lua
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
-- scripts/enemy_rect.lua
|
||||||
|
-- RectNode: bounces around, changes color on bounce.
|
||||||
|
-- Try editing vx/vy or the color while the engine is running!
|
||||||
|
|
||||||
|
local vx, vy = 180, 130
|
||||||
|
|
||||||
|
---@param self RectNode
|
||||||
|
function on_init(self)
|
||||||
|
self.x = 600
|
||||||
|
self.y = 300
|
||||||
|
-- self.color already set by engine (passed in addRectNode),
|
||||||
|
-- but we can override it here.
|
||||||
|
self.color = { r = 220, g = 60, b = 60, a = 255 }
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param self RectNode
|
||||||
|
---@param dt number
|
||||||
|
function on_update(self, dt)
|
||||||
|
self.x = self.x + vx * dt
|
||||||
|
self.y = self.y + vy * dt
|
||||||
|
|
||||||
|
if self.x < 0 or self.x + self.width > 1030 then
|
||||||
|
vx = -vx
|
||||||
|
self.x = math.max(0, math.min(1030 - self.width, self.x))
|
||||||
|
-- Flash yellow on bounce
|
||||||
|
self.color = { r = 240, g = 220, b = 50, a = 255 }
|
||||||
|
end
|
||||||
|
if self.y < 0 or self.y + self.height > 720 then
|
||||||
|
vy = -vy
|
||||||
|
self.y = math.max(0, math.min(720 - self.height, self.y))
|
||||||
|
self.color = { r = 240, g = 220, b = 50, a = 255 }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fade back to red
|
||||||
|
self.color.r = math.min(220, self.color.r + 180 * dt)
|
||||||
|
self.color.g = math.max(60, self.color.g - 300 * dt)
|
||||||
|
self.color.b = math.max(60, self.color.b - 200 * dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param self RectNode
|
||||||
|
function on_render(self)
|
||||||
|
-- Label over auto-drawn rect
|
||||||
|
draw_text("Enemy", self.x + 4, self.y + 18, 13, 255, 210, 210)
|
||||||
|
local spd = math.floor(math.sqrt(vx * vx + vy * vy))
|
||||||
|
draw_text("v=" .. spd, self.x + 4, self.y + 34, 10, 200, 200, 200)
|
||||||
|
end
|
||||||
114
scripts/engine.lua
Normal file
114
scripts/engine.lua
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
---@meta engine
|
||||||
|
---
|
||||||
|
--- LSP type stubs for the Zig engine API.
|
||||||
|
--- This file is NEVER executed — it only exists so lua-language-server
|
||||||
|
--- can provide autocomplete, type checking, and inline docs.
|
||||||
|
---
|
||||||
|
--- Usage in your scripts:
|
||||||
|
--- ---@param self RectNode (or NodeBase for base nodes)
|
||||||
|
|
||||||
|
-- ─── Color type ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
---@class Color
|
||||||
|
---@field r number Red 0-255
|
||||||
|
---@field g number Green 0-255
|
||||||
|
---@field b number Blue 0-255
|
||||||
|
---@field a number Alpha 0-255
|
||||||
|
|
||||||
|
-- ─── Node self types ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
---@class NodeBase
|
||||||
|
---@field x number World X position (read/write)
|
||||||
|
---@field y number World Y position (read/write)
|
||||||
|
|
||||||
|
--- Extends NodeBase with rectangle geometry.
|
||||||
|
--- The engine draws the rect each frame; modify fields to change it.
|
||||||
|
---@class RectNode : NodeBase
|
||||||
|
---@field width number Rectangle width
|
||||||
|
---@field height number Rectangle height
|
||||||
|
---@field color Color Fill color (r/g/b/a subtable)
|
||||||
|
|
||||||
|
-- ─── Drawing ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param w number
|
||||||
|
---@param h number
|
||||||
|
---@param r number Red 0-255
|
||||||
|
---@param g number Green 0-255
|
||||||
|
---@param b number Blue 0-255
|
||||||
|
---@param a? number Alpha 0-255 (default 255)
|
||||||
|
function draw_rect(x, y, w, h, r, g, b, a) end
|
||||||
|
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param radius number
|
||||||
|
---@param r number
|
||||||
|
---@param g number
|
||||||
|
---@param b number
|
||||||
|
---@param a? number
|
||||||
|
function draw_circle(x, y, radius, r, g, b, a) end
|
||||||
|
|
||||||
|
---@param text string
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param size number Font size in pixels
|
||||||
|
---@param r number
|
||||||
|
---@param g number
|
||||||
|
---@param b number
|
||||||
|
---@param a? number
|
||||||
|
function draw_text(text, x, y, size, r, g, b, a) end
|
||||||
|
|
||||||
|
---@param x1 number
|
||||||
|
---@param y1 number
|
||||||
|
---@param x2 number
|
||||||
|
---@param y2 number
|
||||||
|
---@param r number
|
||||||
|
---@param g number
|
||||||
|
---@param b number
|
||||||
|
---@param a? number
|
||||||
|
function draw_line(x1, y1, x2, y2, r, g, b, a) end
|
||||||
|
|
||||||
|
-- ─── Input ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
--- Returns true while key is held.
|
||||||
|
---@param key integer Use Key.X constants
|
||||||
|
---@return boolean
|
||||||
|
function key_down(key) end
|
||||||
|
|
||||||
|
--- Returns true only on the first frame the key is pressed.
|
||||||
|
---@param key integer
|
||||||
|
---@return boolean
|
||||||
|
function key_pressed(key) end
|
||||||
|
|
||||||
|
--- Returns true while mouse button is held. 0=left 1=right 2=middle
|
||||||
|
---@param button integer
|
||||||
|
---@return boolean
|
||||||
|
function mouse_down(button) end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function mouse_x() end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function mouse_y() end
|
||||||
|
|
||||||
|
-- ─── Key constants ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
---@class KeyTable
|
||||||
|
---@field LEFT integer
|
||||||
|
---@field RIGHT integer
|
||||||
|
---@field UP integer
|
||||||
|
---@field DOWN integer
|
||||||
|
---@field SPACE integer
|
||||||
|
---@field W integer
|
||||||
|
---@field A integer
|
||||||
|
---@field S integer
|
||||||
|
---@field D integer
|
||||||
|
|
||||||
|
---@type KeyTable
|
||||||
|
Key = {}
|
||||||
|
|
||||||
|
-- ─── Utility ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
---@param message string
|
||||||
|
function log(message) end
|
||||||
39
scripts/healthbar.lua
Normal file
39
scripts/healthbar.lua
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
-- scripts/healthbar.lua
|
||||||
|
-- RectNode: the engine draws the green bar automatically.
|
||||||
|
-- This script drives the HP logic and recolors/resizes the bar.
|
||||||
|
|
||||||
|
local max_hp = 100
|
||||||
|
local hp = 100
|
||||||
|
|
||||||
|
---@param self RectNode
|
||||||
|
function on_init(self)
|
||||||
|
self.x = 20
|
||||||
|
self.y = 20
|
||||||
|
self.width = 200
|
||||||
|
self.height = 20
|
||||||
|
self.color = { r = 60, g = 200, b = 80, a = 255 }
|
||||||
|
log("HealthBar ready")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param self RectNode
|
||||||
|
---@param dt number
|
||||||
|
function on_update(self, dt)
|
||||||
|
-- Slow drain (replace with real damage events)
|
||||||
|
hp = math.max(0, hp - 4 * dt)
|
||||||
|
|
||||||
|
-- Shrink bar proportionally
|
||||||
|
self.width = (hp / max_hp) * 200
|
||||||
|
|
||||||
|
-- Green → red as HP drops
|
||||||
|
local t = hp / max_hp
|
||||||
|
self.color.r = math.floor((1 - t) * 220)
|
||||||
|
self.color.g = math.floor(t * 200)
|
||||||
|
self.color.b = 40
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param self RectNode
|
||||||
|
function on_render(self)
|
||||||
|
-- Label drawn on top of the auto-rendered bar
|
||||||
|
local label = "HP: " .. (math.floor(hp)) .. (" / ") .. max_hp
|
||||||
|
draw_text(label, self.x + 4, self.y + 4, 12, 240, 240, 240)
|
||||||
|
end
|
||||||
@ -1,42 +1,33 @@
|
|||||||
-- scripts/player.lua
|
-- scripts/player.lua
|
||||||
-- Globals provided each frame by the engine:
|
-- Base node: the script handles all drawing.
|
||||||
-- 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 speed = 200
|
||||||
local width = 40
|
local w, h = 40, 50
|
||||||
local height = 50
|
|
||||||
local cr, cg, cb = 80, 160, 255 -- ← try changing this color and saving!
|
|
||||||
|
|
||||||
function on_init()
|
---@param self NodeBase
|
||||||
log("Player init at " .. self_x .. ", " .. self_y)
|
function on_init(self)
|
||||||
-- self_x/self_y already hold the last known position,
|
log("Player spawned at " .. (self.x) .. ", " .. (self.y))
|
||||||
-- so hot-reload doesn't teleport the player back to origin.
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_update(dt)
|
---@param self NodeBase
|
||||||
if key_down(Key.RIGHT) or key_down(Key.D) then self_x = self_x + speed * dt end
|
---@param dt number
|
||||||
if key_down(Key.LEFT) or key_down(Key.A) then self_x = self_x - speed * dt end
|
function on_update(self, dt)
|
||||||
if key_down(Key.DOWN) or key_down(Key.S) then self_y = self_y + speed * dt end
|
if key_down(Key.RIGHT) or key_down(Key.D) then self.x = self.x + speed * dt end
|
||||||
if key_down(Key.UP) or key_down(Key.W) then self_y = self_y - 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(1030, self.x))
|
||||||
self_x = math.max(0, math.min(1055, self_x))
|
self.y = math.max(0, math.min(665, self.y))
|
||||||
self_y = math.max(0, math.min(665, self_y))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_render()
|
---@param self NodeBase
|
||||||
-- Body
|
function on_render(self)
|
||||||
draw_rect(self_x, self_y, width, height, cr, cg, cb)
|
draw_rect(self.x, self.y, w, h, 80, 160, 255)
|
||||||
|
-- eyes
|
||||||
-- Eyes (white + pupil)
|
draw_circle(self.x + 11, self.y + 14, 6, 255, 255, 255)
|
||||||
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 + 29, self_y + 14, 6, 255, 255, 255)
|
draw_circle(self.x + 13, self.y + 14, 3, 0, 0, 0)
|
||||||
draw_circle(self_x + 13, self_y + 14, 3, 0, 0, 0)
|
draw_circle(self.x + 31, self.y + 14, 3, 0, 0, 0)
|
||||||
draw_circle(self_x + 31, self_y + 14, 3, 0, 0, 0)
|
draw_text("Player", self.x, self.y - 16, 11, 180, 220, 255)
|
||||||
|
|
||||||
-- Label
|
|
||||||
draw_text("Player", self_x, self_y - 16, 11, 180, 220, 255)
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -19,10 +19,28 @@ pub fn deinit(self: *Engine) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn addNode(self: *Engine, name: []const u8, script: []const u8) !void {
|
pub fn addNode(self: *Engine, name: []const u8, script: []const u8) !void {
|
||||||
const node = try Node.init(self.allocator, name, script);
|
const node = try Node.init(self.allocator, name, .base, script);
|
||||||
try self.nodes.append(self.allocator, node);
|
try self.nodes.append(self.allocator, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A rectangle node. Engine auto-draws the rect from Zig properties.
|
||||||
|
/// Script receives a RectNode self and can change width/height/color.
|
||||||
|
pub fn addRectNode(
|
||||||
|
self: *Engine,
|
||||||
|
name: []const u8,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
color: rl.Color,
|
||||||
|
script: []const u8,
|
||||||
|
) !void {
|
||||||
|
const kind: Node.NodeKind = .{ .rectangle = .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.color = color,
|
||||||
|
} };
|
||||||
|
try self.nodes.append(self.allocator, try Node.init(self.allocator, name, kind, script));
|
||||||
|
}
|
||||||
|
|
||||||
/// Poll every node's file watcher; reload changed scripts.
|
/// Poll every node's file watcher; reload changed scripts.
|
||||||
pub fn checkReloads(self: *Engine) !void {
|
pub fn checkReloads(self: *Engine) !void {
|
||||||
for (self.nodes.items) |*node| {
|
for (self.nodes.items) |*node| {
|
||||||
|
|||||||
167
src/Node.zig
167
src/Node.zig
@ -1,9 +1,22 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const rl = @import("raylib");
|
||||||
const zlua = @import("zlua");
|
const zlua = @import("zlua");
|
||||||
const Lua = zlua.Lua;
|
const Lua = zlua.Lua;
|
||||||
const lua_api = @import("lua_api.zig");
|
const lua_api = @import("lua_api.zig");
|
||||||
const FileWatcher = @import("FileWatcher.zig");
|
const FileWatcher = @import("FileWatcher.zig");
|
||||||
|
|
||||||
|
pub const RectData = struct {
|
||||||
|
width: f32 = 50,
|
||||||
|
height: f32 = 50,
|
||||||
|
color: rl.Color = rl.Color.white,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NodeKind = union(enum) {
|
||||||
|
base: void,
|
||||||
|
rectangle: RectData,
|
||||||
|
// extend here: sprite, audio_emitter, tilemap, ...
|
||||||
|
};
|
||||||
|
|
||||||
/// A scene node with an attached Lua script.
|
/// A scene node with an attached Lua script.
|
||||||
/// Each node owns a dedicated Lua state → scripts are fully isolated.
|
/// Each node owns a dedicated Lua state → scripts are fully isolated.
|
||||||
const Node = @This();
|
const Node = @This();
|
||||||
@ -13,27 +26,33 @@ name: []const u8,
|
|||||||
script_path: []const u8,
|
script_path: []const u8,
|
||||||
lua: *Lua,
|
lua: *Lua,
|
||||||
watcher: FileWatcher,
|
watcher: FileWatcher,
|
||||||
|
/// Lua registry key for the persistent self table.
|
||||||
|
/// Survives hot-reloads so script-side state is preserved.
|
||||||
|
self_ref: i32 = 0,
|
||||||
|
|
||||||
// Node transform — synced to/from Lua as self_x, self_y globals
|
x: f32 = 400,
|
||||||
x: f64 = 400,
|
y: f32 = 300,
|
||||||
y: f64 = 300,
|
kind: NodeKind = .base,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
kind: NodeKind,
|
||||||
script_path: []const u8,
|
script_path: []const u8,
|
||||||
) !Node {
|
) !Node {
|
||||||
const lua = try Lua.init(allocator);
|
const lua = try Lua.init(allocator);
|
||||||
lua.openLibs();
|
lua.openLibs();
|
||||||
lua_api.registerAll(lua); // expose engine API to this state
|
lua_api.registerAll(lua);
|
||||||
|
|
||||||
var node = Node{
|
var node = Node{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.name = name,
|
.name = name,
|
||||||
|
.kind = kind,
|
||||||
.script_path = script_path,
|
.script_path = script_path,
|
||||||
.lua = lua,
|
.lua = lua,
|
||||||
.watcher = FileWatcher.init(script_path),
|
.watcher = FileWatcher.init(script_path),
|
||||||
};
|
};
|
||||||
|
try node.createSelfTable();
|
||||||
try node.loadScript();
|
try node.loadScript();
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@ -42,26 +61,25 @@ pub fn deinit(self: *Node) void {
|
|||||||
self.lua.deinit();
|
self.lua.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load (or hot-reload) the Lua script from disk.
|
/// Load or hot-reload the Lua script from disk.
|
||||||
|
/// The self table (and any data stored in it) survives the reload.
|
||||||
pub fn loadScript(self: *Node) !void {
|
pub fn loadScript(self: *Node) !void {
|
||||||
std.log.info("(re)loading {s}", .{self.script_path});
|
std.log.info("(re)loading {s}", .{self.script_path});
|
||||||
|
|
||||||
const path_z = try self.allocator.dupeZ(u8, self.script_path);
|
const path_z = try self.allocator.dupeZ(u8, self.script_path);
|
||||||
defer self.allocator.free(path_z);
|
defer self.allocator.free(path_z);
|
||||||
|
|
||||||
self.lua.doFile(path_z) catch |err| {
|
self.lua.doFile(path_z) catch |err| {
|
||||||
const msg = self.lua.toString(-1) catch "?";
|
const msg = self.lua.toString(-1) catch "?";
|
||||||
std.log.err("[{s}] load error: {s} ({})", .{ self.script_path, msg, err });
|
std.log.err("[{s}] {s} ({})", .{ self.script_path, msg, err });
|
||||||
self.lua.pop(1);
|
self.lua.pop(1);
|
||||||
return; // keep running with old code rather than crashing
|
return; // keep old functions running — don't crash on syntax error
|
||||||
};
|
};
|
||||||
|
|
||||||
self.syncToLua(); // give on_init() the current position
|
self.syncToLua();
|
||||||
self.callVoid("on_init");
|
self.callWithSelf("on_init");
|
||||||
self.syncFromLua();
|
self.syncFromLua();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called every frame. Passes dt as an argument.
|
|
||||||
pub fn update(self: *Node, dt: f32) void {
|
pub fn update(self: *Node, dt: f32) void {
|
||||||
self.syncToLua();
|
self.syncToLua();
|
||||||
const t = self.lua.getGlobal("on_update") catch return;
|
const t = self.lua.getGlobal("on_update") catch return;
|
||||||
@ -69,46 +87,99 @@ pub fn update(self: *Node, dt: f32) void {
|
|||||||
self.lua.pop(1);
|
self.lua.pop(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_ = self.lua.rawGetIndex(zlua.registry_index, self.self_ref);
|
||||||
self.lua.pushNumber(@floatCast(dt));
|
self.lua.pushNumber(@floatCast(dt));
|
||||||
self.lua.protectedCall(.{ .args = 1 }) catch |err| self.logErr("on_update", err);
|
self.lua.protectedCall(.{ .args = 2 }) catch |err| self.logErr("on_update", err);
|
||||||
self.syncFromLua();
|
self.syncFromLua();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called every frame after update.
|
|
||||||
pub fn render(self: *Node) void {
|
pub fn render(self: *Node) void {
|
||||||
|
// 1. Automatic draw from Zig-side properties
|
||||||
|
switch (self.kind) {
|
||||||
|
.rectangle => |r| {
|
||||||
|
rl.drawRectangle(
|
||||||
|
@intFromFloat(self.x),
|
||||||
|
@intFromFloat(self.y),
|
||||||
|
@intFromFloat(r.width),
|
||||||
|
@intFromFloat(r.height),
|
||||||
|
r.color,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
.base => {},
|
||||||
|
}
|
||||||
|
// 2. Script overlay (labels, effects, custom shapes, etc.)
|
||||||
self.syncToLua();
|
self.syncToLua();
|
||||||
self.callVoid("on_render");
|
self.callWithSelf("on_render");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── internals ──────────────────────────────────────────────────────────
|
// ── Self table ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Write node state into Lua globals so scripts can read/modify them.
|
fn createSelfTable(self: *Node) !void {
|
||||||
|
self.lua.newTable();
|
||||||
|
// luaL_ref pops the table and returns an integer key in the registry.
|
||||||
|
// Note: ziglua may return !i32 or !i64 depending on version.
|
||||||
|
self.self_ref = try self.lua.ref(zlua.registry_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write Zig state → Lua self table before any callback.
|
||||||
fn syncToLua(self: *Node) void {
|
fn syncToLua(self: *Node) void {
|
||||||
self.lua.pushNumber(self.x);
|
_ = self.lua.rawGetIndex(zlua.registry_index, self.self_ref);
|
||||||
self.lua.setGlobal("self_x");
|
// self table is now at stack -1
|
||||||
self.lua.pushNumber(self.y);
|
|
||||||
self.lua.setGlobal("self_y");
|
setNum(self.lua, "x", self.x);
|
||||||
|
setNum(self.lua, "y", self.y);
|
||||||
|
|
||||||
|
switch (self.kind) {
|
||||||
|
.rectangle => |r| {
|
||||||
|
setNum(self.lua, "width", r.width);
|
||||||
|
setNum(self.lua, "height", r.height);
|
||||||
|
pushColorTable(self.lua, r.color); // pushes {r,g,b,a} at -1
|
||||||
|
self.lua.setField(-2, "color"); // self_tbl.color = color_tbl; pops color_tbl
|
||||||
|
},
|
||||||
|
.base => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read back any position changes the script made.
|
self.lua.pop(1); // pop self table
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read back Lua self table → Zig state after any callback.
|
||||||
fn syncFromLua(self: *Node) void {
|
fn syncFromLua(self: *Node) void {
|
||||||
_ = self.lua.getGlobal("self_x") catch {};
|
_ = self.lua.rawGetIndex(zlua.registry_index, self.self_ref);
|
||||||
self.x = self.lua.toNumber(-1) catch self.x;
|
// self table at -1
|
||||||
self.lua.pop(1);
|
|
||||||
|
|
||||||
_ = self.lua.getGlobal("self_y") catch {};
|
self.x = getNum(self.lua, "x", self.x);
|
||||||
self.y = self.lua.toNumber(-1) catch self.y;
|
self.y = getNum(self.lua, "y", self.y);
|
||||||
self.lua.pop(1);
|
|
||||||
|
switch (self.kind) {
|
||||||
|
.rectangle => |*r| {
|
||||||
|
r.width = getNum(self.lua, "width", r.width);
|
||||||
|
r.height = getNum(self.lua, "height", r.height);
|
||||||
|
_ = self.lua.getField(-1, "color"); // stack: [..., self_tbl, color_tbl]
|
||||||
|
if (self.lua.typeOf(-1) == .table) {
|
||||||
|
r.color.r = getU8(self.lua, "r", r.color.r);
|
||||||
|
r.color.g = getU8(self.lua, "g", r.color.g);
|
||||||
|
r.color.b = getU8(self.lua, "b", r.color.b);
|
||||||
|
r.color.a = getU8(self.lua, "a", r.color.a);
|
||||||
|
}
|
||||||
|
self.lua.pop(1); // pop color_tbl (or nil)
|
||||||
|
},
|
||||||
|
.base => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a zero-arg Lua function by name (no-op if it doesn't exist).
|
self.lua.pop(1); // pop self table
|
||||||
fn callVoid(self: *Node, name: [:0]const u8) void {
|
}
|
||||||
|
|
||||||
|
// ── Callback helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Call lua_fn_name(self) — no-op if the function is not defined.
|
||||||
|
fn callWithSelf(self: *Node, name: [:0]const u8) void {
|
||||||
const t = self.lua.getGlobal(name) catch return;
|
const t = self.lua.getGlobal(name) catch return;
|
||||||
if (t != .function) {
|
if (t != .function) {
|
||||||
self.lua.pop(1);
|
self.lua.pop(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.lua.protectedCall(.{}) catch |err| self.logErr(name, err);
|
_ = self.lua.rawGetIndex(zlua.registry_index, self.self_ref);
|
||||||
|
self.lua.protectedCall(.{ .args = 1 }) catch |err| self.logErr(name, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logErr(self: *Node, ctx: []const u8, err: anyerror) void {
|
fn logErr(self: *Node, ctx: []const u8, err: anyerror) void {
|
||||||
@ -116,3 +187,39 @@ fn logErr(self: *Node, ctx: []const u8, err: anyerror) void {
|
|||||||
std.log.err("[{s}] {s}: {s} ({})", .{ self.script_path, ctx, msg, err });
|
std.log.err("[{s}] {s}: {s} ({})", .{ self.script_path, ctx, msg, err });
|
||||||
self.lua.pop(1);
|
self.lua.pop(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a number field on the table currently at stack -1.
|
||||||
|
fn setNum(lua: *Lua, key: [:0]const u8, val: f32) void {
|
||||||
|
lua.pushNumber(@floatCast(val));
|
||||||
|
lua.setField(-2, key); // pops num, sets table[-2][key] = num
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a number field from the table currently at stack -1.
|
||||||
|
fn getNum(lua: *Lua, key: [:0]const u8, default: f32) f32 {
|
||||||
|
_ = lua.getField(-1, key);
|
||||||
|
const v = lua.toNumber(-1) catch @as(f64, @floatCast(default));
|
||||||
|
lua.pop(1);
|
||||||
|
return @floatCast(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a u8 color component (0-255) from the table currently at stack -1.
|
||||||
|
fn getU8(lua: *Lua, key: [:0]const u8, default: u8) u8 {
|
||||||
|
_ = lua.getField(-1, key);
|
||||||
|
const v = lua.toNumber(-1) catch @as(f64, @floatFromInt(default));
|
||||||
|
lua.pop(1);
|
||||||
|
return @intFromFloat(std.math.clamp(v, 0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new {r, g, b, a} table onto the stack.
|
||||||
|
fn pushColorTable(lua: *Lua, c: rl.Color) void {
|
||||||
|
lua.newTable();
|
||||||
|
lua.pushNumber(@floatFromInt(c.r));
|
||||||
|
lua.setField(-2, "r");
|
||||||
|
lua.pushNumber(@floatFromInt(c.g));
|
||||||
|
lua.setField(-2, "g");
|
||||||
|
lua.pushNumber(@floatFromInt(c.b));
|
||||||
|
lua.setField(-2, "b");
|
||||||
|
lua.pushNumber(@floatFromInt(c.a));
|
||||||
|
lua.setField(-2, "a");
|
||||||
|
// color table is now at stack top — caller sets it as a field
|
||||||
|
}
|
||||||
|
|||||||
15
src/main.zig
15
src/main.zig
@ -16,7 +16,20 @@ pub fn main() !void {
|
|||||||
|
|
||||||
// Attach Lua scripts to nodes — add as many as you like
|
// Attach Lua scripts to nodes — add as many as you like
|
||||||
try engine.addNode("Player", "scripts/player.lua");
|
try engine.addNode("Player", "scripts/player.lua");
|
||||||
try engine.addNode("Box", "scripts/box.lua");
|
try engine.addRectNode(
|
||||||
|
"HealthBar",
|
||||||
|
200,
|
||||||
|
20,
|
||||||
|
.{ .r = 60, .g = 200, .b = 80, .a = 255 },
|
||||||
|
"scripts/healthbar.lua",
|
||||||
|
);
|
||||||
|
try engine.addRectNode(
|
||||||
|
"Enemy",
|
||||||
|
55,
|
||||||
|
55,
|
||||||
|
.{ .r = 220, .g = 60, .b = 60, .a = 255 },
|
||||||
|
"scripts/enemy_rect.lua",
|
||||||
|
);
|
||||||
|
|
||||||
while (!rl.windowShouldClose()) {
|
while (!rl.windowShouldClose()) {
|
||||||
const dt = rl.getFrameTime();
|
const dt = rl.getFrameTime();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user