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