mirror of
https://github.com/antopilo/Nuake.git
synced 2026-09-15 20:08:54 +03:00
Added prefab instancing through C# and support for mid-game rigidbody and skeletal animation
This commit is contained in:
@@ -13,6 +13,8 @@ namespace Nuake {
|
||||
std::string ScriptPath;
|
||||
std::string Class;
|
||||
|
||||
bool Initialized = false;
|
||||
|
||||
json Serialize()
|
||||
{
|
||||
BEGIN_SERIALIZE();
|
||||
|
||||
@@ -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<TransformComponent, RigidBodyComponent>();
|
||||
for (auto e : view)
|
||||
{
|
||||
@@ -143,32 +145,44 @@ namespace Nuake
|
||||
for (auto entity : boxView)
|
||||
{
|
||||
auto& boxComponent = boxView.get<BoxColliderComponent>(entity);
|
||||
boxComponent.Box = CreateRef<Physics::Box>(boxComponent.Size);
|
||||
if (!boxComponent.Box)
|
||||
{
|
||||
boxComponent.Box = CreateRef<Physics::Box>(boxComponent.Size);
|
||||
}
|
||||
}
|
||||
|
||||
const auto capsuleView = m_Scene->m_Registry.view<CapsuleColliderComponent>();
|
||||
for (auto entity : capsuleView)
|
||||
{
|
||||
auto& capsuleComponent = capsuleView.get<CapsuleColliderComponent>(entity);
|
||||
float radius = capsuleComponent.Radius;
|
||||
float height = capsuleComponent.Height;
|
||||
capsuleComponent.Capsule = CreateRef<Physics::Capsule>(radius, height);
|
||||
if (!capsuleComponent.Capsule)
|
||||
{
|
||||
float radius = capsuleComponent.Radius;
|
||||
float height = capsuleComponent.Height;
|
||||
capsuleComponent.Capsule = CreateRef<Physics::Capsule>(radius, height);
|
||||
}
|
||||
}
|
||||
|
||||
const auto cylinderView = m_Scene->m_Registry.view<CylinderColliderComponent>();
|
||||
for (auto entity : cylinderView)
|
||||
{
|
||||
auto& cylinderComponent = cylinderView.get<CylinderColliderComponent>(entity);
|
||||
float radius = cylinderComponent.Radius;
|
||||
float height = cylinderComponent.Height;
|
||||
cylinderComponent.Cylinder = CreateRef<Physics::Cylinder>(radius, height);
|
||||
if (!cylinderComponent.Cylinder)
|
||||
{
|
||||
float radius = cylinderComponent.Radius;
|
||||
float height = cylinderComponent.Height;
|
||||
cylinderComponent.Cylinder = CreateRef<Physics::Cylinder>(radius, height);
|
||||
}
|
||||
}
|
||||
|
||||
const auto sphereView = m_Scene->m_Registry.view<SphereColliderComponent>();
|
||||
for (auto entity : sphereView)
|
||||
{
|
||||
auto& sphereComponent = sphereView.get<SphereColliderComponent>(entity);
|
||||
sphereComponent.Sphere = CreateRef<Physics::Sphere>(sphereComponent.Radius);
|
||||
if (!sphereComponent.Sphere)
|
||||
{
|
||||
sphereComponent.Sphere = CreateRef<Physics::Sphere>(sphereComponent.Radius);
|
||||
}
|
||||
}
|
||||
|
||||
const auto meshColliderView = m_Scene->m_Registry.view<MeshColliderComponent>();
|
||||
@@ -181,19 +195,22 @@ namespace Nuake
|
||||
}
|
||||
|
||||
auto& meshColliderComponent = meshColliderView.get<MeshColliderComponent>(e);
|
||||
const auto& modelComponent = entity.GetComponent<ModelComponent>();
|
||||
|
||||
if (modelComponent.ModelResource)
|
||||
if (!meshColliderComponent.Shape)
|
||||
{
|
||||
uint32_t subMeshId = meshColliderComponent.SubMesh;
|
||||
const std::vector<Ref<Mesh>>& submeshes = modelComponent.ModelResource->GetMeshes();
|
||||
if (subMeshId >= submeshes.size())
|
||||
{
|
||||
Logger::Log("Cannot create mesh collider, invalid submesh ID", "physics", WARNING);
|
||||
}
|
||||
const auto& modelComponent = entity.GetComponent<ModelComponent>();
|
||||
|
||||
Ref<Mesh> mesh = submeshes[subMeshId];
|
||||
meshColliderComponent.Shape = CreateRef<Physics::MeshShape>(mesh);
|
||||
if (modelComponent.ModelResource)
|
||||
{
|
||||
uint32_t subMeshId = meshColliderComponent.SubMesh;
|
||||
const std::vector<Ref<Mesh>>& submeshes = modelComponent.ModelResource->GetMeshes();
|
||||
if (subMeshId >= submeshes.size())
|
||||
{
|
||||
Logger::Log("Cannot create mesh collider, invalid submesh ID", "physics", WARNING);
|
||||
}
|
||||
|
||||
Ref<Mesh> mesh = submeshes[subMeshId];
|
||||
meshColliderComponent.Shape = CreateRef<Physics::MeshShape>(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,6 +315,11 @@ namespace Nuake
|
||||
Entity entity = Entity({ e, m_Scene });
|
||||
auto [transformComponent, characterControllerComponent] = characterControllerView.get<TransformComponent, CharacterControllerComponent>(e);
|
||||
|
||||
if (characterControllerComponent.GetCharacterController())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<Physics::PhysicShape> shape;
|
||||
if (entity.HasComponent<CapsuleColliderComponent>())
|
||||
{
|
||||
@@ -328,6 +350,7 @@ namespace Nuake
|
||||
characterController->Position = transformComponent.GetGlobalPosition();
|
||||
characterController->Rotation = transformComponent.GetGlobalRotation();
|
||||
characterControllerComponent.SetCharacterController(characterController);
|
||||
|
||||
PhysicsManager::Get().RegisterCharacterController(characterController);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NetScriptComponent>();
|
||||
|
||||
// Instance all the new entities
|
||||
std::vector<Entity> entityJustInstanced;
|
||||
for (auto& e : netEntities)
|
||||
{
|
||||
auto& netScriptComponent = netEntities.get<NetScriptComponent>(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<NetScriptComponent>();
|
||||
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<NetScriptComponent>(e);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -181,47 +181,9 @@ namespace Nuake
|
||||
|
||||
auto& component = entity.GetComponent<NetScriptComponent>();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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*<NativeString, int> GetEntityIcall;
|
||||
internal static unsafe delegate*<NativeString, NativeInstance<Entity>> GetEntityScriptFromNameIcall;
|
||||
internal static unsafe delegate*<int, NativeInstance<Entity>> GetEntityScriptFromHandleIcall;
|
||||
internal static unsafe delegate*<NativeString, int> InstancePrefabIcall;
|
||||
|
||||
public static T? GetEntity<T>(string entityName) where T : class
|
||||
{
|
||||
@@ -22,6 +24,7 @@ namespace Nuake.Net
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T? GetEntity<T>(int entityHandle) where T : class
|
||||
{
|
||||
NativeInstance<Entity> 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<Entity> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user