From 1bdb1d240e576f9e36159f1d050bd5ea90dc6917 Mon Sep 17 00:00:00 2001 From: Atdunbg <979541498@qq.com> Date: Fri, 18 Jul 2025 14:19:10 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8D=95=E6=B7=BB=E5=8A=A0ScriptCompo?= =?UTF-8?q?nent=E5=BA=8F=E5=88=97=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/SandboxProject/Source/Camera.cs | 4 +- Editor/SandboxProject/Source/Player.cs | 15 +- .../src/Editor/Panels/SceneHierachyPanel.cpp | 85 ++++++++++-- Editor/src/Editor/Panels/SceneHierachyPanel.h | 1 + Hazel/src/Hazel/Scene/Scene.cpp | 4 +- Hazel/src/Hazel/Scene/Scene.h | 4 + Hazel/src/Hazel/Scene/SceneSerializer.cpp | 129 ++++++++++++++++++ Hazel/src/Hazel/Scripting/ScriptEngine.cpp | 54 ++++---- Hazel/src/Hazel/Scripting/ScriptEngine.h | 86 ++++++++++++ Hazel/src/Hazel/Scripting/ScriptGlue.cpp | 2 +- 10 files changed, 326 insertions(+), 58 deletions(-) diff --git a/Editor/SandboxProject/Source/Camera.cs b/Editor/SandboxProject/Source/Camera.cs index b70923b..6294f57 100644 --- a/Editor/SandboxProject/Source/Camera.cs +++ b/Editor/SandboxProject/Source/Camera.cs @@ -8,7 +8,7 @@ namespace Sandbox { // private TransformComponent m_Transform; // private RigidBody2DComponent m_Rigidbody; - + public float MoveSpeed; /* void OnCreate() { @@ -23,7 +23,7 @@ namespace Sandbox { { // Console.WriteLine($"Player.OnUpdate: {ts}"); - float speed = 1.0f; + float speed = MoveSpeed; Vector3 velocity = Vector3.Zero; if(Input.IsKeyDown(KeyCode.UP)) diff --git a/Editor/SandboxProject/Source/Player.cs b/Editor/SandboxProject/Source/Player.cs index 7795352..e81121e 100644 --- a/Editor/SandboxProject/Source/Player.cs +++ b/Editor/SandboxProject/Source/Player.cs @@ -8,7 +8,8 @@ namespace Sandbox { private TransformComponent m_Transform; private RigidBody2DComponent m_Rigidbody; - public float Speed = 1.0f; + public float Force; + public float Time; void OnCreate() { @@ -20,9 +21,9 @@ namespace Sandbox { void OnUpdate(float ts) { -// Console.WriteLine($"Player.OnUpdate: {ts}"); + Time += ts; - float speed = Speed; + float speed = Force; Vector3 velocity = Vector3.Zero; if(Input.IsKeyDown(KeyCode.W)) @@ -36,14 +37,6 @@ namespace Sandbox { velocity.X = 1.0f; m_Rigidbody.ApplyLinearImpulseToCenter(velocity.XY * speed, true); - -/* - velocity *= speed; - - Vector3 translation = m_Transform.Translation; - translation += velocity * ts; - m_Transform.Translation = translation; - */ } } diff --git a/Editor/src/Editor/Panels/SceneHierachyPanel.cpp b/Editor/src/Editor/Panels/SceneHierachyPanel.cpp index 6dfc0cc..15e9840 100644 --- a/Editor/src/Editor/Panels/SceneHierachyPanel.cpp +++ b/Editor/src/Editor/Panels/SceneHierachyPanel.cpp @@ -48,6 +48,7 @@ namespace Hazel if (ImGui::MenuItem("Create Empty Entity")) { m_SelectionContext = m_Context->CreateEntity("Empty Entity"); + m_LastSelectionContext = m_SelectionContext; } else if (ImGui::MenuItem("Create Camera")) { @@ -61,9 +62,9 @@ namespace Hazel } ImGui::Begin("Properties"); - if (m_SelectionContext) + if (m_LastSelectionContext) { - DrawComponents(m_SelectionContext); + DrawComponents(m_LastSelectionContext); } ImGui::End(); } @@ -80,6 +81,7 @@ namespace Hazel if (ImGui::IsItemClicked()) { m_SelectionContext = entity; + m_LastSelectionContext = m_SelectionContext; } if (ImGui::BeginPopupContextItem()) @@ -100,6 +102,7 @@ namespace Hazel if (m_SelectionContext == entity) { m_SelectionContext = {}; + m_LastSelectionContext = m_SelectionContext; } } } @@ -358,40 +361,93 @@ namespace Hazel } }); - DrawComponent("Script", entity, [entity](auto& component) mutable + DrawComponent("Script", entity, [entity, &scene = m_Context](auto& component) mutable { - const bool scriptClassExists = ScriptEngine::ClassExists(component.ClassName); + bool scriptClassExists = ScriptEngine::ClassExists(component.ClassName); + bool setStyleFlag = false; static char buffer[64] = {}; strcpy(buffer, component.ClassName.c_str()); if (!scriptClassExists) + { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.3f, 1.0f)); + setStyleFlag = true; + } - if (ImGui::InputText("Script", buffer, sizeof(buffer))) + if (ImGui::InputText("Class", buffer, sizeof(buffer))) + { component.ClassName = buffer; + scriptClassExists = ScriptEngine::ClassExists(component.ClassName); + } + // Fields - Ref scriptInstance = ScriptEngine::GetEntityScriptInstance(entity.GetUUID()); - if (scriptInstance) + const bool sceneRunning = scene->IsRunning(); + if (sceneRunning) { - const auto& fields = scriptInstance->GetScriptClass()->GetFields(); - for (const auto&[name, field] : fields) + if (scriptInstance) { - if (field.Type == ScriptFieldType::Float) + const auto& fields = scriptInstance->GetScriptClass()->GetFields(); + for (const auto& [name, field] : fields) { - float data = scriptInstance->GetFieldValue(name); - if (ImGui::DragFloat(name.c_str(), &data)) + if (field.Type == ScriptFieldType::Float) { - scriptInstance->SetFieldValue(name, data); + float data = scriptInstance->GetFieldValue(name); + if (ImGui::DragFloat(name.c_str(), &data)) + { + scriptInstance->SetFieldValue(name, data); + } } } } } + else + { + if (scriptClassExists) + { + Ref entityClass = ScriptEngine::GetEntityClass(component.ClassName); + const auto& fields = entityClass->GetFields(); + auto& entityFields = ScriptEngine::GetScriptFieldMap(entity); - if (!scriptClassExists) + + for (const auto& [name, field] : fields) + { + // field has been set in Editor + if (entityFields.contains(name)) + { + ScriptFieldInstance& scriptField = entityFields.at(name); + + // display control to set + if (field.Type == ScriptFieldType::Float) + { + float data = scriptField.GetValue(); + if (ImGui::DragFloat(name.c_str(), &data)) + scriptField.SetValue(data); + } + } + else + { + // display control to set + if (field.Type == ScriptFieldType::Float) + { + float data = 0.0f; + if (ImGui::DragFloat(name.c_str(), &data)) + { + ScriptFieldInstance& fieldInstance = entityFields[name]; + fieldInstance.Field = field; + fieldInstance.SetValue(data); + } + } + } + } + } + + } + + if (setStyleFlag) ImGui::PopStyleColor(); }); @@ -478,5 +534,6 @@ namespace Hazel void SceneHierachyPanel::SetSelectedEntity(const Entity entity) { m_SelectionContext = entity; + m_LastSelectionContext = m_SelectionContext; } } diff --git a/Editor/src/Editor/Panels/SceneHierachyPanel.h b/Editor/src/Editor/Panels/SceneHierachyPanel.h index 046f1be..6a03b14 100644 --- a/Editor/src/Editor/Panels/SceneHierachyPanel.h +++ b/Editor/src/Editor/Panels/SceneHierachyPanel.h @@ -33,6 +33,7 @@ namespace Hazel Ref m_Context; Entity m_SelectionContext; + Entity m_LastSelectionContext; }; } diff --git a/Hazel/src/Hazel/Scene/Scene.cpp b/Hazel/src/Hazel/Scene/Scene.cpp index 1e6dc8a..8bb5863 100644 --- a/Hazel/src/Hazel/Scene/Scene.cpp +++ b/Hazel/src/Hazel/Scene/Scene.cpp @@ -242,6 +242,7 @@ namespace Hazel void Scene::OnRuntimeStart() { + m_IsRunning = true; OnPhysics2DStart(); // scripts @@ -274,6 +275,7 @@ namespace Hazel void Scene::OnRuntimeStop() { + m_IsRunning = false; OnPhysics2DStop(); // script @@ -293,7 +295,7 @@ namespace Hazel } // - m_Registry.view().each([=](auto entity, auto& nsc) + m_Registry.view().each([=, this](auto entity, auto& nsc) { // TODO: move to Scene::OnScenePlay if (!nsc.Instance) diff --git a/Hazel/src/Hazel/Scene/Scene.h b/Hazel/src/Hazel/Scene/Scene.h index d172cbd..0eca445 100644 --- a/Hazel/src/Hazel/Scene/Scene.h +++ b/Hazel/src/Hazel/Scene/Scene.h @@ -47,6 +47,8 @@ namespace Hazel Entity GetPrimaryCameraEntity(); Entity GetEntityByUUID(UUID uuid); + bool IsRunning() const {return m_IsRunning; } + template auto GetAllEntitiesWith() { @@ -69,6 +71,8 @@ namespace Hazel b2WorldId* m_PhysicsWorld = nullptr; + bool m_IsRunning = false; + std::unordered_map m_EnttMap; friend class Entity; diff --git a/Hazel/src/Hazel/Scene/SceneSerializer.cpp b/Hazel/src/Hazel/Scene/SceneSerializer.cpp index 79762cf..634b8e4 100644 --- a/Hazel/src/Hazel/Scene/SceneSerializer.cpp +++ b/Hazel/src/Hazel/Scene/SceneSerializer.cpp @@ -8,10 +8,29 @@ #include "Components.h" #include "Entity.h" +#include "Hazel/Scripting/ScriptEngine.h" #include "yaml-cpp/yaml.h" namespace YAML { + template<> + struct convert + { + static Node encode(const Hazel::UUID& out) + { + Node node; + node.push_back((uint64_t)out); + return node; + } + + static bool decode(const Node& node, Hazel::UUID& uuid) + { + uuid = node.as(); + return true; + } + }; + + template<> struct convert { @@ -208,6 +227,64 @@ namespace Hazel out << YAML::BeginMap; // ScriptComponent out << YAML::Key << "ClassName" << YAML::Value << scriptComponent.ClassName; + + // fields + Ref entityClass = ScriptEngine::GetEntityClass(scriptComponent.ClassName); + const auto& fields = entityClass->GetFields(); + if (fields.size() > 0) + { + auto& entityFields = ScriptEngine::GetScriptFieldMap(entity); + out << YAML::Key << "ScriptFields" << YAML::Value; + + out << YAML::BeginSeq; + for (const auto& [name, field] : fields) + { + if (!entityFields.contains(name)) + continue; + // - Name: FieldName + // Type: Int + // Data: 5 + + + out << YAML::BeginMap; // Script Field + out << YAML::Key << "Name" << YAML::Value << name; + out << YAML::Key << "Type" << YAML::Value << Utils::ScriptFieldTypeToString(field.Type); + out << YAML::Key << "Data" << YAML::Value; + + ScriptFieldInstance& scriptField = entityFields.at(name); +#define WRITE_FIELD_TYPE(FieldType, Type) case ScriptFieldType::FieldType:\ + out << scriptField.GetValue();\ + break + + switch (field.Type) + { + WRITE_FIELD_TYPE(Float, float); + WRITE_FIELD_TYPE(Double, double); + WRITE_FIELD_TYPE(Boolean, bool); + WRITE_FIELD_TYPE(Char, char); + WRITE_FIELD_TYPE(Byte, int8_t); + WRITE_FIELD_TYPE(Short, int16_t); + WRITE_FIELD_TYPE(Int, int32_t); + WRITE_FIELD_TYPE(Long, int64_t); + WRITE_FIELD_TYPE(UByte, uint8_t); + WRITE_FIELD_TYPE(UShort, uint16_t); + WRITE_FIELD_TYPE(UInt, uint32_t); + WRITE_FIELD_TYPE(ULong, uint64_t); + + WRITE_FIELD_TYPE(Vector2, glm::vec2); + WRITE_FIELD_TYPE(Vector3, glm::vec3); + WRITE_FIELD_TYPE(Vector4, glm::vec4); + + WRITE_FIELD_TYPE(Entity, UUID); + } + + out << YAML::EndMap; +#undef WRITE_FIELD_TYPE + } + out << YAML::EndSeq; + + } + out << YAML::EndMap; // ScriptComponent } @@ -401,6 +478,58 @@ namespace Hazel { auto& sc = deserializedEntity.AddComponent(); sc.ClassName = scriptComponent["ClassName"].as(); + + auto scriptFields = scriptComponent["ScriptFields"]; + if (scriptFields) + { + Ref entityClass = ScriptEngine::GetEntityClass(sc.ClassName); + const auto& fields = entityClass->GetFields(); + auto& entityFields = ScriptEngine::GetScriptFieldMap(deserializedEntity); + + for (auto scriptField: scriptFields) + { + std::string name = scriptField["Name"].as(); + std::string typeName = scriptField["Type"].as(); + ScriptFieldType type = Utils::ScriptFieldTypeFromString(typeName); + ScriptFieldInstance& fieldInstance = entityFields[name]; + + fieldInstance.Field = fields.at(name); + + +#define READ_FIELD_TYPE(FieldType, Type) \ + case ScriptFieldType::FieldType: \ + { \ + Type valueData = scriptField["Data"].as(); \ + fieldInstance.SetValue(valueData); \ + break; \ + } + + + switch (type) + { + READ_FIELD_TYPE(Float, float); + READ_FIELD_TYPE(Double, double); + READ_FIELD_TYPE(Boolean, bool); + READ_FIELD_TYPE(Char, char); + READ_FIELD_TYPE(Byte, int8_t); + READ_FIELD_TYPE(Short, int16_t); + READ_FIELD_TYPE(Int, int32_t); + READ_FIELD_TYPE(Long, int64_t); + READ_FIELD_TYPE(UByte, uint8_t); + READ_FIELD_TYPE(UShort, uint16_t); + READ_FIELD_TYPE(UInt, uint32_t); + READ_FIELD_TYPE(ULong, uint64_t); + + READ_FIELD_TYPE(Vector2, glm::vec2); + READ_FIELD_TYPE(Vector3, glm::vec3); + READ_FIELD_TYPE(Vector4, glm::vec4); + + READ_FIELD_TYPE(Entity, UUID); + } + +#undef READ_FIELD_TYPE + } + } } auto spriteRendererComponent = entity["SpriteRendererComponent"]; diff --git a/Hazel/src/Hazel/Scripting/ScriptEngine.cpp b/Hazel/src/Hazel/Scripting/ScriptEngine.cpp index eb8ae87..74c8148 100644 --- a/Hazel/src/Hazel/Scripting/ScriptEngine.cpp +++ b/Hazel/src/Hazel/Scripting/ScriptEngine.cpp @@ -122,34 +122,6 @@ namespace Hazel } return s_ScriptFieldTypeMap.at(typeName); } - - const char* ScriptFieldTypeToString(ScriptFieldType type) - { - switch (type) - { - case ScriptFieldType::Float: return "Float"; - case ScriptFieldType::Double: return "Double"; - case ScriptFieldType::Boolean: return "Boolean"; - case ScriptFieldType::Byte: return "Byte"; - case ScriptFieldType::Char: return "Char"; - case ScriptFieldType::Short: return "Short"; - case ScriptFieldType::Int: return "Int"; - case ScriptFieldType::Long: return "Long"; - case ScriptFieldType::UByte: return "UByte"; - case ScriptFieldType::UShort: return "UShort"; - case ScriptFieldType::UInt: return "UInt"; - case ScriptFieldType::ULong: return "ULong"; - - case ScriptFieldType::Entity: return "Entity"; - - case ScriptFieldType::Vector2: return "Vector2"; - case ScriptFieldType::Vector3: return "Vector3"; - case ScriptFieldType::Vector4: return "Vector4"; - } - - return ""; - } - } struct ScriptEngineData @@ -168,6 +140,8 @@ namespace Hazel std::unordered_map> EntityClasses; std::unordered_map> EntityInstances; + std::unordered_map EntityScriptFields; + // runtime Scene* SceneContext = nullptr; }; @@ -306,6 +280,18 @@ namespace Hazel return s_Data->EntityClasses; } + Ref ScriptEngine::GetEntityClass(const std::string& name) + { + if (!s_Data->EntityClasses.contains(name)) + return nullptr; + return s_Data->EntityClasses.at(name); + } + + ScriptFieldMap& ScriptEngine::GetScriptFieldMap(Entity entity) + { + return s_Data->EntityScriptFields[entity.GetUUID()]; + } + void ScriptEngine::OnRuntimeStart(Scene* scene) { s_Data->SceneContext = scene; @@ -328,8 +314,18 @@ namespace Hazel const auto& sc = entity.GetComponent(); if (ClassExists(sc.ClassName)) { + UUID entityID = entity.GetUUID(); Ref instance = CreateRef(s_Data->EntityClasses[sc.ClassName], entity); - s_Data->EntityInstances[entity.GetUUID()] = instance; + s_Data->EntityInstances[entityID] = instance; + + // copy Field value + if (s_Data->EntityScriptFields.contains(entityID)) + { + const ScriptFieldMap& fieldMap = s_Data->EntityScriptFields.at(entityID); + + for (const auto& [name, fieldInstance] : fieldMap) + instance->SetFieldValueInternal(name, fieldInstance.m_Buffer); + } instance->InvokeOnCreate(); } diff --git a/Hazel/src/Hazel/Scripting/ScriptEngine.h b/Hazel/src/Hazel/Scripting/ScriptEngine.h index 9af6e4b..ea6953c 100644 --- a/Hazel/src/Hazel/Scripting/ScriptEngine.h +++ b/Hazel/src/Hazel/Scripting/ScriptEngine.h @@ -41,6 +41,36 @@ namespace Hazel MonoClassField* ClassField; }; + struct ScriptFieldInstance + { + ScriptField Field; + + template + T GetValue() + { + return *(T*)m_Buffer; + } + + template + void SetValue(T data) + { + if constexpr (sizeof(T) <= 8) + memcpy(m_Buffer, &data, sizeof(T)); + else + { + HZ_CLIENT_ERROR("Type to large: sizeof(data): {} bytes", sizeof(T)); + } + } + + private: + uint8_t m_Buffer[8] = {}; + + friend class ScriptEngine; + friend class ScriptInstance; + }; + + using ScriptFieldMap = std::unordered_map; + class ScriptClass { public: @@ -103,6 +133,9 @@ namespace Hazel MonoMethod* m_OnUpdateMethod = nullptr; inline static char s_FieldValueBuffer[8]; + + friend class ScriptEngine; + friend struct ScriptFieldInstance; }; class ScriptEngine @@ -126,6 +159,8 @@ namespace Hazel static Ref GetEntityScriptInstance(UUID entityID); static std::unordered_map> GetEntityClasses(); + static Ref GetEntityClass(const std::string& name); + static ScriptFieldMap& GetScriptFieldMap(Entity entity); static MonoImage* GetCoreAssemblyImage(); @@ -139,6 +174,57 @@ namespace Hazel friend class ScriptClass; friend class ScriptGlue; }; + + namespace Utils + { + inline const char* ScriptFieldTypeToString(const ScriptFieldType fieldType) + { + switch (fieldType) + { + case ScriptFieldType::Char: return "Char"; + case ScriptFieldType::Float: return "Float"; + case ScriptFieldType::Double: return "Double"; + case ScriptFieldType::Vector2: return "Vector2"; + case ScriptFieldType::Vector3: return "Vector3"; + case ScriptFieldType::Vector4: return "Vector4"; + case ScriptFieldType::Byte: return "Byte"; + case ScriptFieldType::Short: return "Short"; + case ScriptFieldType::Int: return "Int"; + case ScriptFieldType::Long: return "Long"; + case ScriptFieldType::Boolean: return "Boolean"; + case ScriptFieldType::UByte: return "UByte"; + case ScriptFieldType::UShort: return "UShort"; + case ScriptFieldType::UInt: return "UInt"; + case ScriptFieldType::ULong: return "ULong"; + case ScriptFieldType::Entity: return "Entity"; + } + HZ_CORE_ERROR("Unknown scriptFieldType"); + return "None"; + } + + inline ScriptFieldType ScriptFieldTypeFromString(const std::string& fieldType) + { + if(fieldType == "Char") return ScriptFieldType::Char; + if(fieldType == "Float") return ScriptFieldType::Float; + if(fieldType == "Double") return ScriptFieldType::Double; + if(fieldType == "Vector2") return ScriptFieldType::Vector2; + if(fieldType == "Vector3") return ScriptFieldType::Vector3; + if(fieldType == "Vector4") return ScriptFieldType::Vector4; + if(fieldType == "Byte") return ScriptFieldType::Byte; + if(fieldType == "Short") return ScriptFieldType::Short; + if(fieldType == "Int") return ScriptFieldType::Int; + if(fieldType == "Long") return ScriptFieldType::Long; + if(fieldType == "Boolean") return ScriptFieldType::Boolean; + if(fieldType == "UByte") return ScriptFieldType::UByte; + if(fieldType == "UShort") return ScriptFieldType::UShort; + if(fieldType == "UInt") return ScriptFieldType::UInt; + if(fieldType == "ULong") return ScriptFieldType::ULong; + if(fieldType == "Entity") return ScriptFieldType::Entity; + + HZ_CORE_ERROR("Unknown scriptFieldType"); + return ScriptFieldType::None; + } + } } diff --git a/Hazel/src/Hazel/Scripting/ScriptGlue.cpp b/Hazel/src/Hazel/Scripting/ScriptGlue.cpp index 98693a4..8857464 100644 --- a/Hazel/src/Hazel/Scripting/ScriptGlue.cpp +++ b/Hazel/src/Hazel/Scripting/ScriptGlue.cpp @@ -18,7 +18,7 @@ namespace Hazel static std::unordered_map> s_EntityHasComponentFuncs; -#define HZ_ADD_INTERNAL_CALL(Name) mono_add_internal_call("Hazel.InternalCalls::" #Name, Name) +#define HZ_ADD_INTERNAL_CALL(Name) mono_add_internal_call("Hazel.InternalCalls::" #Name, reinterpret_cast(Name)) static void NativeLog(MonoString* text, int param) {