From 39025d8b0659ea3d5331c9dc8e9ba2a1d5a36a50 Mon Sep 17 00:00:00 2001 From: Antoine Pilote Date: Tue, 23 Apr 2024 23:22:19 -0400 Subject: [PATCH] Added prefab instancing through C# and support for mid-game rigidbody and skeletal animation --- .../src/Scene/Components/NetScriptComponent.h | 2 + Nuake/src/Scene/Systems/PhysicsSystem.cpp | 65 +++++++++----- Nuake/src/Scene/Systems/ScriptingSystem.cpp | 36 ++++++++ .../src/Scripting/NetModules/SceneNetAPI.cpp | 14 ++- Nuake/src/Scripting/ScriptingEngineNet.cpp | 86 ++++++++++--------- Nuake/src/Scripting/ScriptingEngineNet.h | 2 + NuakeNet/src/Scene.cs | 30 +++++++ 7 files changed, 173 insertions(+), 62 deletions(-) diff --git a/Nuake/src/Scene/Components/NetScriptComponent.h b/Nuake/src/Scene/Components/NetScriptComponent.h index d99a94d9..0e740ff7 100644 --- a/Nuake/src/Scene/Components/NetScriptComponent.h +++ b/Nuake/src/Scene/Components/NetScriptComponent.h @@ -13,6 +13,8 @@ namespace Nuake { std::string ScriptPath; std::string Class; + bool Initialized = false; + json Serialize() { BEGIN_SERIALIZE(); diff --git a/Nuake/src/Scene/Systems/PhysicsSystem.cpp b/Nuake/src/Scene/Systems/PhysicsSystem.cpp index 9a0a5b37..e310ca62 100644 --- a/Nuake/src/Scene/Systems/PhysicsSystem.cpp +++ b/Nuake/src/Scene/Systems/PhysicsSystem.cpp @@ -43,6 +43,10 @@ namespace Nuake return; } + InitializeShapes(); + InitializeRigidbodies(); + InitializeCharacterControllers(); + ApplyForces(); PhysicsManager::Get().Step(ts); @@ -74,8 +78,6 @@ namespace Nuake if (!Engine::IsPlayMode()) return; - InitializeRigidbodies(); - auto view = m_Scene->m_Registry.view(); for (auto e : view) { @@ -143,32 +145,44 @@ namespace Nuake for (auto entity : boxView) { auto& boxComponent = boxView.get(entity); - boxComponent.Box = CreateRef(boxComponent.Size); + if (!boxComponent.Box) + { + boxComponent.Box = CreateRef(boxComponent.Size); + } } const auto capsuleView = m_Scene->m_Registry.view(); for (auto entity : capsuleView) { auto& capsuleComponent = capsuleView.get(entity); - float radius = capsuleComponent.Radius; - float height = capsuleComponent.Height; - capsuleComponent.Capsule = CreateRef(radius, height); + if (!capsuleComponent.Capsule) + { + float radius = capsuleComponent.Radius; + float height = capsuleComponent.Height; + capsuleComponent.Capsule = CreateRef(radius, height); + } } const auto cylinderView = m_Scene->m_Registry.view(); for (auto entity : cylinderView) { auto& cylinderComponent = cylinderView.get(entity); - float radius = cylinderComponent.Radius; - float height = cylinderComponent.Height; - cylinderComponent.Cylinder = CreateRef(radius, height); + if (!cylinderComponent.Cylinder) + { + float radius = cylinderComponent.Radius; + float height = cylinderComponent.Height; + cylinderComponent.Cylinder = CreateRef(radius, height); + } } const auto sphereView = m_Scene->m_Registry.view(); for (auto entity : sphereView) { auto& sphereComponent = sphereView.get(entity); - sphereComponent.Sphere = CreateRef(sphereComponent.Radius); + if (!sphereComponent.Sphere) + { + sphereComponent.Sphere = CreateRef(sphereComponent.Radius); + } } const auto meshColliderView = m_Scene->m_Registry.view(); @@ -181,19 +195,22 @@ namespace Nuake } auto& meshColliderComponent = meshColliderView.get(e); - const auto& modelComponent = entity.GetComponent(); - - if (modelComponent.ModelResource) + if (!meshColliderComponent.Shape) { - uint32_t subMeshId = meshColliderComponent.SubMesh; - const std::vector>& submeshes = modelComponent.ModelResource->GetMeshes(); - if (subMeshId >= submeshes.size()) - { - Logger::Log("Cannot create mesh collider, invalid submesh ID", "physics", WARNING); - } + const auto& modelComponent = entity.GetComponent(); - Ref mesh = submeshes[subMeshId]; - meshColliderComponent.Shape = CreateRef(mesh); + if (modelComponent.ModelResource) + { + uint32_t subMeshId = meshColliderComponent.SubMesh; + const std::vector>& submeshes = modelComponent.ModelResource->GetMeshes(); + if (subMeshId >= submeshes.size()) + { + Logger::Log("Cannot create mesh collider, invalid submesh ID", "physics", WARNING); + } + + Ref mesh = submeshes[subMeshId]; + meshColliderComponent.Shape = CreateRef(mesh); + } } } } @@ -298,6 +315,11 @@ namespace Nuake Entity entity = Entity({ e, m_Scene }); auto [transformComponent, characterControllerComponent] = characterControllerView.get(e); + if (characterControllerComponent.GetCharacterController()) + { + continue; + } + Ref shape; if (entity.HasComponent()) { @@ -328,6 +350,7 @@ namespace Nuake characterController->Position = transformComponent.GetGlobalPosition(); characterController->Rotation = transformComponent.GetGlobalRotation(); characterControllerComponent.SetCharacterController(characterController); + PhysicsManager::Get().RegisterCharacterController(characterController); } } diff --git a/Nuake/src/Scene/Systems/ScriptingSystem.cpp b/Nuake/src/Scene/Systems/ScriptingSystem.cpp index 6afef97c..08f15533 100644 --- a/Nuake/src/Scene/Systems/ScriptingSystem.cpp +++ b/Nuake/src/Scene/Systems/ScriptingSystem.cpp @@ -56,6 +56,8 @@ namespace Nuake // Creates an instance of the entity script in C# scriptingEngineNet.RegisterEntityScript(entity); + + netScriptComponent.Initialized = true; } for (auto& e : netEntities) @@ -89,6 +91,40 @@ namespace Nuake auto& scriptingEngineNet = ScriptingEngineNet::Get(); auto netEntities = m_Scene->m_Registry.view(); + + // Instance all the new entities + std::vector entityJustInstanced; + for (auto& e : netEntities) + { + auto& netScriptComponent = netEntities.get(e); + if (!netScriptComponent.Initialized) + { + if (netScriptComponent.ScriptPath.empty()) + continue; + + auto entity = Entity{ e, m_Scene }; + + // Creates an instance of the entity script in C# + scriptingEngineNet.RegisterEntityScript(entity); + + netScriptComponent.Initialized = true; + entityJustInstanced.push_back(entity); + } + } + + // Call init on the newly created instance + for (auto& e : entityJustInstanced) + { + auto& netScriptComponent = e.GetComponent(); + if (e.IsValid() && scriptingEngineNet.HasEntityScriptInstance(e)) + { + // We can now call on init on it. + auto scriptInstance = scriptingEngineNet.GetEntityScript(e); + scriptInstance.InvokeMethod("OnInit"); + } + } + + // Then call update on all the other scripts for (auto& e : netEntities) { NetScriptComponent& netScriptComponent = netEntities.get(e); diff --git a/Nuake/src/Scripting/NetModules/SceneNetAPI.cpp b/Nuake/src/Scripting/NetModules/SceneNetAPI.cpp index 8fcd15ac..c1630328 100644 --- a/Nuake/src/Scripting/NetModules/SceneNetAPI.cpp +++ b/Nuake/src/Scripting/NetModules/SceneNetAPI.cpp @@ -75,6 +75,17 @@ namespace Nuake { } } + int InstancePrefab(Coral::String path) + { + if (!FileSystem::FileExists(path)) + { + return -1; + } + + const auto& prefab = Prefab::New(path); + return prefab->Root.GetHandle(); + } + static enum ComponentTypes { Unknown = -1, @@ -291,11 +302,12 @@ namespace Nuake { RegisterMethod("Entity.EntityHasComponentIcall", &EntityHasComponent); RegisterMethod("Entity.EntityHasManagedInstanceIcall", &EntityHasManagedInstance); RegisterMethod("Entity.EntityGetEntityIcall", &EntityGetEntity); + // Scene RegisterMethod("Scene.GetEntityIcall", &GetEntity); RegisterMethod("Scene.GetEntityScriptFromNameIcall", &GetEntityScriptFromName); RegisterMethod("Scene.GetEntityScriptFromHandleIcall", &GetEntityScriptFromHandle); - + RegisterMethod("Scene.InstancePrefabIcall", &InstancePrefab); // Components RegisterMethod("TransformComponent.SetPositionIcall", &TransformSetPosition); RegisterMethod("TransformComponent.GetPositionIcall", &TransformGetPosition); diff --git a/Nuake/src/Scripting/ScriptingEngineNet.cpp b/Nuake/src/Scripting/ScriptingEngineNet.cpp index 3dadd05b..6a2ddc46 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.cpp +++ b/Nuake/src/Scripting/ScriptingEngineNet.cpp @@ -181,47 +181,9 @@ namespace Nuake auto& component = entity.GetComponent(); const auto& filePath = component.ScriptPath; - if (filePath.empty()) - { - Logger::Log("Skipped .net entity script since it was empty.", ".net", VERBOSE); - return; - } + + const std::string& className = FindClassNameInScript(filePath); - if (!FileSystem::FileExists(filePath)) - { - Logger::Log("Skipped .net entity script: The file path doesn't exist.", ".net", WARNING); - return; - } - - // We can now scan the file and look for this pattern: class XXXXX : Entity - // Warning, this doesnt do any bound check so if there is a semicolon at the end - // of the file. IT MIGHT CRASH HERE. POTENTIALLY - I have not tested. - // ----------------------------------------------------- - std::string fileContent = FileSystem::ReadFile(filePath); - fileContent = fileContent.substr(3, fileContent.size()); - fileContent = String::RemoveWhiteSpace(fileContent); - - // Find class token - size_t classTokenPos = fileContent.find("class"); - if (classTokenPos == std::string::npos) - { - Logger::Log("Skipped .net entity script: file doesnt contain entity class.", ".net", WARNING); - return; - } - - size_t classNameStartIndex = classTokenPos + 5; // 4 letter: class + 1 for next char - - // Find semi-colon token - size_t semiColonPos = fileContent.find(":"); - if (semiColonPos == std::string::npos || semiColonPos < classTokenPos) - { - Logger::Log("Skipped .net entity script: Not class inheriting Entity was found.", ".net", WARNING); - return; - } - - size_t classNameLength = semiColonPos - classNameStartIndex; - - const std::string className = fileContent.substr(classNameStartIndex, classNameLength); if(m_GameEntityTypes.find(className) == m_GameEntityTypes.end()) { // The class name parsed in the file was not found in the game's DLL. @@ -343,4 +305,48 @@ namespace NuakeShowcase )"; } + std::string ScriptingEngineNet::FindClassNameInScript(const std::string & filePath) + { + if (filePath.empty()) + { + Logger::Log("Skipped .net entity script since it was empty.", ".net", VERBOSE); + return ""; + } + + if (!FileSystem::FileExists(filePath)) + { + Logger::Log("Skipped .net entity script: The file path doesn't exist.", ".net", WARNING); + return ""; + } + + // We can now scan the file and look for this pattern: class XXXXX : Entity + // Warning, this doesnt do any bound check so if there is a semicolon at the end + // of the file. IT MIGHT CRASH HERE. POTENTIALLY - I have not tested. + // ----------------------------------------------------- + std::string fileContent = FileSystem::ReadFile(filePath); + fileContent = fileContent.substr(3, fileContent.size()); + fileContent = String::RemoveWhiteSpace(fileContent); + + // Find class token + size_t classTokenPos = fileContent.find("class"); + if (classTokenPos == std::string::npos) + { + Logger::Log("Skipped .net entity script: file doesnt contain entity class.", ".net", WARNING); + return ""; + } + + size_t classNameStartIndex = classTokenPos + 5; // 4 letter: class + 1 for next char + + // Find semi-colon token + size_t semiColonPos = fileContent.find(":"); + if (semiColonPos == std::string::npos || semiColonPos < classTokenPos) + { + Logger::Log("Skipped .net entity script: Not class inheriting Entity was found.", ".net", WARNING); + return ""; + } + + size_t classNameLength = semiColonPos - classNameStartIndex; + return fileContent.substr(classNameStartIndex, classNameLength); + } + } \ No newline at end of file diff --git a/Nuake/src/Scripting/ScriptingEngineNet.h b/Nuake/src/Scripting/ScriptingEngineNet.h index 7201e14f..b2984717 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.h +++ b/Nuake/src/Scripting/ScriptingEngineNet.h @@ -61,5 +61,7 @@ namespace Nuake { void CopyNuakeNETAssemblies(const std::string& path); void GenerateSolution(const std::string& path, const std::string& projectName); void CreateEntityScript(const std::string& path, const std::string& entityName); + + std::string FindClassNameInScript(const std::string& filePath); }; } \ No newline at end of file diff --git a/NuakeNet/src/Scene.cs b/NuakeNet/src/Scene.cs index ef0e077e..f7f981b5 100644 --- a/NuakeNet/src/Scene.cs +++ b/NuakeNet/src/Scene.cs @@ -1,5 +1,6 @@ using Coral.Managed.Interop; using System; +using System.Reflection.Metadata; namespace Nuake.Net { @@ -8,6 +9,7 @@ namespace Nuake.Net internal static unsafe delegate* GetEntityIcall; internal static unsafe delegate*> GetEntityScriptFromNameIcall; internal static unsafe delegate*> GetEntityScriptFromHandleIcall; + internal static unsafe delegate* InstancePrefabIcall; public static T? GetEntity(string entityName) where T : class { @@ -22,6 +24,7 @@ namespace Nuake.Net return null; } + public static T? GetEntity(int entityHandle) where T : class { NativeInstance handle; @@ -53,5 +56,32 @@ namespace Nuake.Net return entity; } + + public static Entity? InstancePrefab(string path, Entity? parent = null) + { + int handle; + unsafe { handle = InstancePrefabIcall(path); } + + if (handle == -1) + { + return null; + } + + //NativeInstance nativeInstance; + //unsafe { nativeInstance = GetEntityScriptFromHandleIcall(handle); } + // + //Entity? entity = nativeInstance.Get(); + //if (entity != null && entity is T) + //{ + // return entity as T; + //} + + Entity entity = new Entity + { + ECSHandle = handle + }; + + return entity; + } } } \ No newline at end of file