added an object spawner

This commit is contained in:
Peter Li 2025-05-05 22:57:20 -07:00
parent 20e0c1b3ee
commit 442705c786
9 changed files with 338 additions and 17 deletions

View File

@ -36,6 +36,8 @@ pub const Console = struct {
var i: usize = 0; var i: usize = 0;
const command = core.stringStrip(c); const command = core.stringStrip(c);
core.console_log("console_eval > {s}", .{c});
while (i < command.len) : (i += 1) { while (i < command.len) : (i += 1) {
if (command[i] == ' ') { if (command[i] == ' ') {
const func = command[0..i]; const func = command[0..i];

View File

@ -2,6 +2,11 @@
const std = @import("std"); const std = @import("std");
pub const gameObjectList = @import("utils/gameObjectList.zig");
pub const GameObjectList = gameObjectList.GameObjectList;
pub const GameObjectInterface = gameObjectList.GameObjectInterface;
pub const GameObjectData = gameObjectList.GameObjectData;
pub const inputs = @import("inputs/inputStack.zig"); pub const inputs = @import("inputs/inputStack.zig");
pub const getInputStack = inputs.getInputStack; pub const getInputStack = inputs.getInputStack;
pub const ActionEvent = inputs.ActionEvent; pub const ActionEvent = inputs.ActionEvent;
@ -156,9 +161,9 @@ pub fn shutdown_module(_: std.mem.Allocator) void {
MemoryTracker.MTPrintStatsDelta(); MemoryTracker.MTPrintStatsDelta();
logging.shutdownLogging(); logging.shutdownLogging();
ecs.shutdown();
debug_draw.shutdownDrawInterface(); debug_draw.shutdownDrawInterface();
ecs.shutdown();
algorithm.destroyNameRegistry(); algorithm.destroyNameRegistry();
gEngine.deinit(); gEngine.deinit();
gPackerFS.destroy(); gPackerFS.destroy();

View File

@ -115,6 +115,16 @@ pub fn createEntity() !Entity {
return .{ .handle = try gEcsRegistry.baseSet.createObject(.{}) }; return .{ .handle = try gEcsRegistry.baseSet.createObject(.{}) };
} }
pub fn destroyEntity(e: Entity) void {
if (gEcsRegistry.baseSet.get(e.handle)) |entityEntry| {
for (entityEntry.containers.items) |ref| {
ref.vtable.destroyObject(ref.ptr, e.handle);
}
entityEntry.containers.deinit(gEcsRegistry.allocator);
gEcsRegistry.baseSet.destroyObject(e.handle);
}
}
pub fn CreateEntity_Lua(state: lua.LuaState) i32 { pub fn CreateEntity_Lua(state: lua.LuaState) i32 {
const ud = state.newZigUserdata(Entity) catch return 0; const ud = state.newZigUserdata(Entity) catch return 0;
ud.* = createEntity() catch { ud.* = createEntity() catch {
@ -255,6 +265,11 @@ pub const EcsRegistry = struct {
for (self.systems.items) |ref| { for (self.systems.items) |ref| {
ref.vtable.destroy(ref.ptr); ref.vtable.destroy(ref.ptr);
} }
for (self.baseSet.dense.items) |*entry| {
entry.value.containers.deinit(self.allocator);
}
self.systems.deinit(self.allocator); self.systems.deinit(self.allocator);
self.tickableSystems.deinit(self.allocator); self.tickableSystems.deinit(self.allocator);
self.baseSet.deinit(); self.baseSet.deinit();
@ -277,6 +292,7 @@ pub fn defineComponent(comptime Component: type, allocator: std.mem.Allocator) !
} }
pub fn undefineComponent(comptime Component: type) void { pub fn undefineComponent(comptime Component: type) void {
core.engine_logs("undefining component " ++ @typeName(Component));
Component.BaseContainer.destroy(); Component.BaseContainer.destroy();
} }
@ -292,6 +308,10 @@ pub const Entity = struct {
}, },
}; };
pub fn destroy(self: *@This()) void {
destroyEntity(self.*);
}
pub fn fromHandle(handle: core.ObjectHandle) @This() { pub fn fromHandle(handle: core.ObjectHandle) @This() {
return .{ .handle = handle }; return .{ .handle = handle };
} }
@ -299,6 +319,10 @@ pub const Entity = struct {
pub fn addComponent(self: @This(), comptime Component: type) ?*Component { pub fn addComponent(self: @This(), comptime Component: type) ?*Component {
const rv = Component.BaseContainer.createWithHandleECS(self.handle); const rv = Component.BaseContainer.createWithHandleECS(self.handle);
const list = &gEcsRegistry.baseSet.get(self.handle).?.containers;
const allocator = gEcsRegistry.allocator;
list.append(allocator, getTypeContainer(Component)) catch return null;
// if (@hasDecl(Component, "init")) { // if (@hasDecl(Component, "init")) {
// rv.init(self.handle); // rv.init(self.handle);
// } // }
@ -365,6 +389,7 @@ pub fn getTypeContainer(comptime T: type) EcsContainerRef {
pub const EcsEntry = struct { pub const EcsEntry = struct {
containersCount: u32 = 0, containersCount: u32 = 0,
containers: std.ArrayListUnmanaged(EcsContainerRef) = .{},
}; };
pub const BaseSet = p2.SparseSet(EcsEntry); pub const BaseSet = p2.SparseSet(EcsEntry);

View File

@ -0,0 +1,131 @@
// helper, add this to your game manager class and invoke it's tick,
//
// will automatically manage spawning and despawning entities and manage de-initialization for you
// i actually don't reccomend using this for the most part for more serious games
// but it's something that will help a lot when implementing gamejams
//
// what i would rather reccomend instead is creating components instead with SparseMap base container for most things
//
// however if you want to define prefabs, then something like this might be useful.
//
// Like i said, more geared towards gamejams and toys than serious work.
pub const GameObjectInterfaceVTable = struct {
destroy: *const fn (*anyopaque) void,
tick: ?*const fn (*anyopaque, f64) void,
getEntity: ?*const fn (*anyopaque) core.Entity,
};
pub const GameObjectData = struct {
index: u32,
list: *GameObjectList,
pub fn release(self: *@This(), outer: anytype) void {
core.engine_log("releases {d}", .{self.list.objects.items.len});
if (self.list.objects.items.len > 1) {
self.list.objects.items[self.list.objects.items.len - 1].data.index = self.index;
}
_ = self.list.objects.swapRemove(@intCast(self.index));
self.list.allocator.destroy(outer);
}
};
pub const GameObjectInterface = struct {
vtable: *const GameObjectInterfaceVTable,
ptr: *anyopaque,
data: *GameObjectData,
};
pub const GameObjectList = struct {
allocator: std.mem.Allocator,
objects: std.ArrayListUnmanaged(GameObjectInterface) = .{},
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{ .allocator = allocator };
return self;
}
pub const SpawnFunc = *const fn (*anyopaque) ?GameObjectInterface;
pub fn spawnObjectFunction(comptime T: type) SpawnFunc {
const Wrapped = struct {
pub fn w_spawn(list_p: *anyopaque) ?GameObjectInterface {
const ctx: *GameObjectList = @ptrCast(@alignCast(list_p));
return ctx.spawnObject(T) catch null;
}
};
return Wrapped.w_spawn;
}
pub fn spawnObject(self: *@This(), comptime T: type) !GameObjectInterface {
const Wrapped = struct {
pub fn w_tick(ptr: *anyopaque, dt: f64) void {
const object: *T = @ptrCast(@alignCast(ptr));
object.tick(dt);
}
pub fn w_getEntity(ptr: *anyopaque) core.Entity {
const object: *T = @ptrCast(@alignCast(ptr));
return object.getEntity();
}
pub fn w_destroy(ptr: *anyopaque) void {
const object: *T = @ptrCast(@alignCast(ptr));
object.destroy();
}
pub const VTable = GameObjectInterfaceVTable{
.tick = if (@hasDecl(T, "tick")) w_tick else null,
.getEntity = if (@hasDecl(T, "getEntity")) w_getEntity else null,
.destroy = w_destroy,
};
};
const new = try T.create(self.allocator);
new.data = .{
.index = @intCast(self.objects.items.len),
.list = self,
};
const interface = GameObjectInterface{
.vtable = &Wrapped.VTable,
.ptr = new,
.data = &new.data,
};
try self.objects.append(self.allocator, interface);
return interface;
}
pub fn newObject(self: *@This(), comptime T: type) !*T {
const interface = try self.spawnObject(T);
return @ptrCast(@alignCast(interface.ptr));
}
pub fn tick(self: *@This(), dt: f64) void {
for (self.objects.items) |interface| {
if (interface.vtable.tick) |tick_fn| {
tick_fn(interface.ptr, dt);
}
}
}
pub fn destroy(self: *@This()) void {
for (self.objects.items) |interface| {
interface.vtable.destroy(interface.ptr);
}
self.objects.deinit(self.allocator);
self.allocator.destroy(self);
}
};
const std = @import("std");
const core = @import("../core.zig");

View File

@ -23,6 +23,7 @@ test "simple systems setup for core" {
try test_loadingConfigs(); try test_loadingConfigs();
try test_consoleCommands(); try test_consoleCommands();
try test_gameObjects(std.testing.allocator);
try memory.dumpTimeline("test-core-timeline.txt"); try memory.dumpTimeline("test-core-timeline.txt");
} }
@ -47,6 +48,8 @@ fn test_loadingConfigs() !void {
if (core.getConfigVar([]const u8, "platform.windowName")) |height| { if (core.getConfigVar([]const u8, "platform.windowName")) |height| {
core.engine_log("config string: {s}", .{height}); core.engine_log("config string: {s}", .{height});
} }
try core.assert(core.configVar(f64, "platform.extent.height", 0) == core.getConfigVar(f64, "platform.extent.height").?);
} }
fn test_consoleCommands() !void { fn test_consoleCommands() !void {
@ -81,3 +84,54 @@ fn test_consoleCommands() !void {
core.console.evaluate("echo lmfao"); core.console.evaluate("echo lmfao");
core.console.evaluate("foo lmfao"); core.console.evaluate("foo lmfao");
} }
fn test_gameObjects(allocator: std.mem.Allocator) !void {
engine_logs("test: gameobjects... ");
const objList = try core.GameObjectList.create(allocator);
defer objList.destroy();
const GameObject = struct {
data: core.GameObjectData = undefined,
entity: core.Entity,
pub fn create(alloc: std.mem.Allocator) !*@This() {
const self = try alloc.create(@This());
self.entity = try core.createEntity();
_ = self.entity.addComponent(core.Scene);
return self;
}
pub fn getEntity(self: *@This()) core.Entity {
return self.entity;
}
pub fn tick(self: *@This(), dt: f64) void {
_ = dt;
engine_log("gameObject ticking.... {d} {d}", .{ self.data.index, self.entity.handle.index });
}
pub fn destroy(self: *@This()) void {
self.entity.destroy();
self.data.release(self);
}
};
const gameObject = try objList.newObject(GameObject);
const gameObject2 = try objList.newObject(GameObject);
objList.tick(0.016);
gameObject.destroy();
objList.tick(0.016);
gameObject2.destroy();
objList.tick(0.016);
const spawnFunc = core.GameObjectList.spawnObjectFunction(GameObject);
const interface = spawnFunc(objList).?;
core.engine_log("handle = {x}", .{interface.vtable.getEntity.?(interface.ptr).handle.index});
objList.tick(0.016);
}

View File

@ -1,3 +1,5 @@
pub const FpCamera = @import("FpCamera.zig"); pub const FpCamera = @import("FpCamera.zig");
pub const inputDebugger = @import("inputDebugger.zig"); pub const inputDebugger = @import("inputDebugger.zig");
pub const ObjectSpawner = @import("objectSpawner.zig");

View File

@ -0,0 +1,60 @@
allocator: std.mem.Allocator,
objectList: *core.GameObjectList,
windowOpen: bool = true,
spawnFuncs: std.ArrayListUnmanaged(SpawnEntry) = .{},
spawnPosition: core.Vectorf = .{},
pub const SpawnEntry = struct {
typeName: []const u8,
spawnFunc: core.GameObjectList.SpawnFunc,
};
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.objectList = try core.GameObjectList.create(allocator),
};
return self;
}
pub fn tick(self: *@This(), dt: f64) void {
self.objectList.tick(dt);
if (self.windowOpen) {
if (ig.begin("object Spawner", null, .{})) {
for (self.spawnFuncs.items) |entry| {
if (ig.smallButton(entry.typeName.ptr)) {
if (entry.spawnFunc(self.objectList)) |new| {
const entity = new.vtable.getEntity.?(new.ptr);
self.prepareEntity(entity);
}
}
}
ig.end();
}
}
}
pub fn addSpawnFunction(self: *@This(), comptime name: []const u8, comptime T: type) !void {
try self.spawnFuncs.append(self.allocator, .{ .typeName = name, .spawnFunc = core.GameObjectList.spawnObjectFunction(T) });
}
pub fn prepareEntity(self: *@This(), entity: core.Entity) void {
if (entity.fetch(core.Scene)) |scene| {
scene.setPosition(self.spawnPosition);
}
}
pub fn destroy(self: *@This()) void {
self.spawnFuncs.deinit(self.allocator);
self.objectList.destroy();
self.allocator.destroy(self);
}
const std = @import("std");
const backlog = @import("Backlog");
const core = backlog.core;
const rend = backlog.rend;
const ig = backlog.imgui.api;

View File

@ -199,7 +199,7 @@ pub fn SparseMultiSetAdvanced(comptime T: type, comptime SparseSize: u32) type {
return self.createObjectInternal(initValue, handle.index, handle.generation); return self.createObjectInternal(initValue, handle.index, handle.generation);
} }
pub fn destroyObject(self: *@This(), handle: SetHandle) bool { pub fn destroyObject(self: *@This(), handle: SetHandle) false {
// to destroy an object // to destroy an object
// get handle and get the dense position, swap and remove. // get handle and get the dense position, swap and remove.
// Then insert the tombstone value into the sparse handle // Then insert the tombstone value into the sparse handle
@ -225,8 +225,6 @@ pub fn SparseMultiSetAdvanced(comptime T: type, comptime SparseSize: u32) type {
if (self.containerListener) |l| { if (self.containerListener) |l| {
l.onHandleRemoved(l.ptr, self.containerID, handle); l.onHandleRemoved(l.ptr, self.containerID, handle);
} }
return true;
} }
var prng = std.Random.DefaultPrng.init(0x1234); var prng = std.Random.DefaultPrng.init(0x1234);
@ -469,7 +467,7 @@ pub fn SparseSetAdvanced(comptime T: type, comptime SparseSize: u32) type {
// Will fail if the handle already exists. // Will fail if the handle already exists.
pub fn createWithHandle(self: *@This(), handle: SetHandle, initValue: T) !ConstructResult { pub fn createWithHandle(self: *@This(), handle: SetHandle, initValue: T) !ConstructResult {
if (handle.index >= DefaultSparseSize) { if (handle.index >= DefaultSparseSize) {
std.debug.print("this should never happen handle index is huge: {d}\n", .{handle.index}); std.debug.print("this should never happen handle index is huge: {x} {d}\n", .{ handle.index, handle.index });
@panic("impossible handle"); @panic("impossible handle");
} }
var currentDenseHandle = self.sparse[handle.index]; var currentDenseHandle = self.sparse[handle.index];
@ -709,6 +707,7 @@ pub const EcsContainerInterface = interface.MakeInterface("EcsContainerInterface
handleExists: *const fn (*const anyopaque, SetHandle) bool, handleExists: *const fn (*const anyopaque, SetHandle) bool,
get: *const fn (*const anyopaque, SetHandle) ?*anyopaque, get: *const fn (*const anyopaque, SetHandle) ?*anyopaque,
createWithHandle: *const fn (*anyopaque, SetHandle) *anyopaque, createWithHandle: *const fn (*anyopaque, SetHandle) *anyopaque,
destroyObject: *const fn (*anyopaque, SetHandle) void,
getContainerID: *const fn (*const anyopaque) u32, getContainerID: *const fn (*const anyopaque) u32,
onRegister: *const fn (*anyopaque, u32, ContainerListener) void, onRegister: *const fn (*anyopaque, u32, ContainerListener) void,
evictFromRegistry: *const fn (*anyopaque) void, evictFromRegistry: *const fn (*anyopaque) void,
@ -736,6 +735,12 @@ pub const EcsContainerInterface = interface.MakeInterface("EcsContainerInterface
return @ptrCast(ptr.get(handle)); return @ptrCast(ptr.get(handle));
} }
pub fn destroyObject(p: *anyopaque, handle: SetHandle) void {
// std.debug.print("creat with handle {p}\n", .{p});
var ptr = @as(*TargetType, @ptrCast(@alignCast(p)));
ptr.destroyObject(handle);
}
pub fn createWithHandle(p: *anyopaque, handle: SetHandle) *anyopaque { pub fn createWithHandle(p: *anyopaque, handle: SetHandle) *anyopaque {
// std.debug.print("creat with handle {p}\n", .{p}); // std.debug.print("creat with handle {p}\n", .{p});
var ptr = @as(*TargetType, @ptrCast(@alignCast(p))); var ptr = @as(*TargetType, @ptrCast(@alignCast(p)));
@ -769,6 +774,7 @@ pub const EcsContainerInterface = interface.MakeInterface("EcsContainerInterface
.handleExists = Wrap.handleExists, .handleExists = Wrap.handleExists,
.get = Wrap.get, .get = Wrap.get,
.createWithHandle = Wrap.createWithHandle, .createWithHandle = Wrap.createWithHandle,
.destroyObject = Wrap.destroyObject,
.getContainerID = Wrap.getContainerID, .getContainerID = Wrap.getContainerID,
.onRegister = Wrap.onRegister, .onRegister = Wrap.onRegister,
.evictFromRegistry = Wrap.evictFromRegistry, .evictFromRegistry = Wrap.evictFromRegistry,

View File

@ -20,8 +20,9 @@ doomplayer: *DoomPlayer = undefined,
videoplayerObject: core.Entity = undefined, videoplayerObject: core.Entity = undefined,
doomplayerObject: core.Entity = undefined, doomplayerObject: core.Entity = undefined,
inputEnabled: bool = true, objectSpawner: *extras.ObjectSpawner = undefined,
inputEnabled: bool = true,
videoFullbright: bool = false, videoFullbright: bool = false,
moveLight: bool = true, moveLight: bool = true,
@ -47,16 +48,16 @@ const assetReferences = [_]assets.AssetImportReference{
"m_skybox", "m_skybox",
.{ .path = "meshes/skybox.obj" }, .{ .path = "meshes/skybox.obj" },
), ),
// assets.MakeImportRefOptions( assets.MakeImportRefOptions(
// "Mesh", "Mesh",
// "m_fox", "m_fox",
// .{ .path = "gltf-samples/Fox/glTF/Fox.gltf" }, .{ .path = "gltf-samples/Fox/glTF/Fox.gltf" },
// ), ),
// assets.MakeImportRefOptions( assets.MakeImportRefOptions(
// "Texture", "Texture",
// "t_fox", "t_fox",
// .{ .path = "gltf-samples/Fox/glTF/Texture.png" }, .{ .path = "gltf-samples/Fox/glTF/Texture.png" },
// ), ),
assets.MakeImportRefOptions( assets.MakeImportRefOptions(
"Texture", "Texture",
"t_empire", "t_empire",
@ -79,6 +80,35 @@ const assetReferences = [_]assets.AssetImportReference{
), ),
}; };
pub const FoxObject = struct {
data: core.GameObjectData = undefined,
entity: core.Entity,
pub fn create(alloc: std.mem.Allocator) !*@This() {
const self = try alloc.create(@This());
const fox = try core.createEntity();
const scene = fox.addComponent(core.Scene).?;
scene.setScale(0.1, 0.1, 0.1);
const mesh = fox.addComponent(rend.MeshComponent).?;
mesh.setMesh("m_fox");
mesh.setTexture("t_fox");
self.entity = fox;
return self;
}
pub fn getEntity(self: *@This()) core.Entity {
return self.entity;
}
pub fn destroy(self: *@This()) void {
self.entity.destroy();
self.data.release(self);
}
};
pub fn prepare(self: *@This()) !void { pub fn prepare(self: *@This()) !void {
core.engine_log(">>>>>>> game prepare", .{}); core.engine_log(">>>>>>> game prepare", .{});
var z = core.tracy.ZoneN(@src(), "PREPARING GAME"); var z = core.tracy.ZoneN(@src(), "PREPARING GAME");
@ -87,6 +117,9 @@ pub fn prepare(self: *@This()) !void {
try script.loadTypes("scripts"); try script.loadTypes("scripts");
try script.runScriptFile("scripts/prepare.lua"); try script.runScriptFile("scripts/prepare.lua");
self.objectSpawner = try extras.ObjectSpawner.create(self.allocator);
try self.objectSpawner.addSpawnFunction("fox", FoxObject);
try assets.loadList(assetReferences); try assets.loadList(assetReferences);
self.videoplayer = try VideoPlayer.create(self.allocator); self.videoplayer = try VideoPlayer.create(self.allocator);
try self.videoplayer.startPlayback("LAPWING2.ogv"); try self.videoplayer.startPlayback("LAPWING2.ogv");
@ -293,6 +326,7 @@ pub fn tick(self: *@This(), dt: f64) void {
const fdt: f32 = @floatCast(dt); const fdt: f32 = @floatCast(dt);
extras.inputDebugger.tick(); extras.inputDebugger.tick();
self.objectSpawner.tick(dt);
self.videoplayer.tick(dt); self.videoplayer.tick(dt);
self.doomplayer.tick(dt); self.doomplayer.tick(dt);
self.fpcamera.tick(dt); self.fpcamera.tick(dt);
@ -312,6 +346,7 @@ pub fn tick(self: *@This(), dt: f64) void {
const forward = self.fpcamera.getForward(); const forward = self.fpcamera.getForward();
const debugCenter = self.fpcamera.getPosition().add(forward.fmul(self.centerDist)); const debugCenter = self.fpcamera.getPosition().add(forward.fmul(self.centerDist));
self.objectSpawner.spawnPosition = debugCenter;
if (self.moveLight) { if (self.moveLight) {
core.debugSphere(debugCenter, 0.3, .{}); core.debugSphere(debugCenter, 0.3, .{});
@ -339,6 +374,7 @@ pub fn deinit(self: *@This()) void {
self.doomplayer.destroy(); self.doomplayer.destroy();
self.fpcamera.destroy(); self.fpcamera.destroy();
self.objectSpawner.destroy();
self.videoplayer.destroy(); self.videoplayer.destroy();
self.allocator.destroy(self); self.allocator.destroy(self);
} }