简单添加ScriptComponent序列化功能

This commit is contained in:
2025-07-18 14:19:10 +08:00
parent e0bfdd9c4e
commit 1bdb1d240e
10 changed files with 326 additions and 58 deletions

View File

@ -8,7 +8,7 @@ namespace Sandbox {
// private TransformComponent m_Transform; // private TransformComponent m_Transform;
// private RigidBody2DComponent m_Rigidbody; // private RigidBody2DComponent m_Rigidbody;
public float MoveSpeed;
/* /*
void OnCreate() void OnCreate()
{ {
@ -23,7 +23,7 @@ namespace Sandbox {
{ {
// Console.WriteLine($"Player.OnUpdate: {ts}"); // Console.WriteLine($"Player.OnUpdate: {ts}");
float speed = 1.0f; float speed = MoveSpeed;
Vector3 velocity = Vector3.Zero; Vector3 velocity = Vector3.Zero;
if(Input.IsKeyDown(KeyCode.UP)) if(Input.IsKeyDown(KeyCode.UP))

View File

@ -8,7 +8,8 @@ namespace Sandbox {
private TransformComponent m_Transform; private TransformComponent m_Transform;
private RigidBody2DComponent m_Rigidbody; private RigidBody2DComponent m_Rigidbody;
public float Speed = 1.0f; public float Force;
public float Time;
void OnCreate() void OnCreate()
{ {
@ -20,9 +21,9 @@ namespace Sandbox {
void OnUpdate(float ts) void OnUpdate(float ts)
{ {
// Console.WriteLine($"Player.OnUpdate: {ts}"); Time += ts;
float speed = Speed; float speed = Force;
Vector3 velocity = Vector3.Zero; Vector3 velocity = Vector3.Zero;
if(Input.IsKeyDown(KeyCode.W)) if(Input.IsKeyDown(KeyCode.W))
@ -36,14 +37,6 @@ namespace Sandbox {
velocity.X = 1.0f; velocity.X = 1.0f;
m_Rigidbody.ApplyLinearImpulseToCenter(velocity.XY * speed, true); m_Rigidbody.ApplyLinearImpulseToCenter(velocity.XY * speed, true);
/*
velocity *= speed;
Vector3 translation = m_Transform.Translation;
translation += velocity * ts;
m_Transform.Translation = translation;
*/
} }
} }

View File

@ -48,6 +48,7 @@ namespace Hazel
if (ImGui::MenuItem("Create Empty Entity")) if (ImGui::MenuItem("Create Empty Entity"))
{ {
m_SelectionContext = m_Context->CreateEntity("Empty Entity"); m_SelectionContext = m_Context->CreateEntity("Empty Entity");
m_LastSelectionContext = m_SelectionContext;
} }
else if (ImGui::MenuItem("Create Camera")) else if (ImGui::MenuItem("Create Camera"))
{ {
@ -61,9 +62,9 @@ namespace Hazel
} }
ImGui::Begin("Properties"); ImGui::Begin("Properties");
if (m_SelectionContext) if (m_LastSelectionContext)
{ {
DrawComponents(m_SelectionContext); DrawComponents(m_LastSelectionContext);
} }
ImGui::End(); ImGui::End();
} }
@ -80,6 +81,7 @@ namespace Hazel
if (ImGui::IsItemClicked()) if (ImGui::IsItemClicked())
{ {
m_SelectionContext = entity; m_SelectionContext = entity;
m_LastSelectionContext = m_SelectionContext;
} }
if (ImGui::BeginPopupContextItem()) if (ImGui::BeginPopupContextItem())
@ -100,6 +102,7 @@ namespace Hazel
if (m_SelectionContext == entity) if (m_SelectionContext == entity)
{ {
m_SelectionContext = {}; m_SelectionContext = {};
m_LastSelectionContext = m_SelectionContext;
} }
} }
} }
@ -358,40 +361,93 @@ namespace Hazel
} }
}); });
DrawComponent<ScriptComponent>("Script", entity, [entity](auto& component) mutable DrawComponent<ScriptComponent>("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] = {}; static char buffer[64] = {};
strcpy(buffer, component.ClassName.c_str()); strcpy(buffer, component.ClassName.c_str());
if (!scriptClassExists) if (!scriptClassExists)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.3f, 1.0f)); 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; component.ClassName = buffer;
scriptClassExists = ScriptEngine::ClassExists(component.ClassName);
}
// Fields // Fields
Ref<ScriptInstance> scriptInstance = ScriptEngine::GetEntityScriptInstance(entity.GetUUID()); Ref<ScriptInstance> scriptInstance = ScriptEngine::GetEntityScriptInstance(entity.GetUUID());
if (scriptInstance) const bool sceneRunning = scene->IsRunning();
if (sceneRunning)
{ {
const auto& fields = scriptInstance->GetScriptClass()->GetFields(); if (scriptInstance)
for (const auto&[name, field] : fields)
{ {
if (field.Type == ScriptFieldType::Float) const auto& fields = scriptInstance->GetScriptClass()->GetFields();
for (const auto& [name, field] : fields)
{ {
float data = scriptInstance->GetFieldValue<float>(name); if (field.Type == ScriptFieldType::Float)
if (ImGui::DragFloat(name.c_str(), &data))
{ {
scriptInstance->SetFieldValue(name, data); float data = scriptInstance->GetFieldValue<float>(name);
if (ImGui::DragFloat(name.c_str(), &data))
{
scriptInstance->SetFieldValue(name, data);
}
} }
} }
} }
} }
else
{
if (scriptClassExists)
{
Ref<ScriptClass> 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<float>();
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(); ImGui::PopStyleColor();
}); });
@ -478,5 +534,6 @@ namespace Hazel
void SceneHierachyPanel::SetSelectedEntity(const Entity entity) void SceneHierachyPanel::SetSelectedEntity(const Entity entity)
{ {
m_SelectionContext = entity; m_SelectionContext = entity;
m_LastSelectionContext = m_SelectionContext;
} }
} }

View File

@ -33,6 +33,7 @@ namespace Hazel
Ref<Scene> m_Context; Ref<Scene> m_Context;
Entity m_SelectionContext; Entity m_SelectionContext;
Entity m_LastSelectionContext;
}; };
} }

View File

@ -242,6 +242,7 @@ namespace Hazel
void Scene::OnRuntimeStart() void Scene::OnRuntimeStart()
{ {
m_IsRunning = true;
OnPhysics2DStart(); OnPhysics2DStart();
// scripts // scripts
@ -274,6 +275,7 @@ namespace Hazel
void Scene::OnRuntimeStop() void Scene::OnRuntimeStop()
{ {
m_IsRunning = false;
OnPhysics2DStop(); OnPhysics2DStop();
// script // script
@ -293,7 +295,7 @@ namespace Hazel
} }
// //
m_Registry.view<NativeScriptComponent>().each([=](auto entity, auto& nsc) m_Registry.view<NativeScriptComponent>().each([=, this](auto entity, auto& nsc)
{ {
// TODO: move to Scene::OnScenePlay // TODO: move to Scene::OnScenePlay
if (!nsc.Instance) if (!nsc.Instance)

View File

@ -47,6 +47,8 @@ namespace Hazel
Entity GetPrimaryCameraEntity(); Entity GetPrimaryCameraEntity();
Entity GetEntityByUUID(UUID uuid); Entity GetEntityByUUID(UUID uuid);
bool IsRunning() const {return m_IsRunning; }
template<typename... Components> template<typename... Components>
auto GetAllEntitiesWith() auto GetAllEntitiesWith()
{ {
@ -69,6 +71,8 @@ namespace Hazel
b2WorldId* m_PhysicsWorld = nullptr; b2WorldId* m_PhysicsWorld = nullptr;
bool m_IsRunning = false;
std::unordered_map<UUID, entt::entity> m_EnttMap; std::unordered_map<UUID, entt::entity> m_EnttMap;
friend class Entity; friend class Entity;

View File

@ -8,10 +8,29 @@
#include "Components.h" #include "Components.h"
#include "Entity.h" #include "Entity.h"
#include "Hazel/Scripting/ScriptEngine.h"
#include "yaml-cpp/yaml.h" #include "yaml-cpp/yaml.h"
namespace YAML namespace YAML
{ {
template<>
struct convert<Hazel::UUID>
{
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<uint64_t>();
return true;
}
};
template<> template<>
struct convert<glm::vec2> struct convert<glm::vec2>
{ {
@ -208,6 +227,64 @@ namespace Hazel
out << YAML::BeginMap; // ScriptComponent out << YAML::BeginMap; // ScriptComponent
out << YAML::Key << "ClassName" << YAML::Value << scriptComponent.ClassName; out << YAML::Key << "ClassName" << YAML::Value << scriptComponent.ClassName;
// fields
Ref<ScriptClass> 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<Type>();\
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 out << YAML::EndMap; // ScriptComponent
} }
@ -401,6 +478,58 @@ namespace Hazel
{ {
auto& sc = deserializedEntity.AddComponent<ScriptComponent>(); auto& sc = deserializedEntity.AddComponent<ScriptComponent>();
sc.ClassName = scriptComponent["ClassName"].as<std::string>(); sc.ClassName = scriptComponent["ClassName"].as<std::string>();
auto scriptFields = scriptComponent["ScriptFields"];
if (scriptFields)
{
Ref<ScriptClass> 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>();
std::string typeName = scriptField["Type"].as<std::string>();
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<Type>(); \
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"]; auto spriteRendererComponent = entity["SpriteRendererComponent"];

View File

@ -122,34 +122,6 @@ namespace Hazel
} }
return s_ScriptFieldTypeMap.at(typeName); 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 "<Invalid>";
}
} }
struct ScriptEngineData struct ScriptEngineData
@ -168,6 +140,8 @@ namespace Hazel
std::unordered_map<std::string, Ref<ScriptClass>> EntityClasses; std::unordered_map<std::string, Ref<ScriptClass>> EntityClasses;
std::unordered_map<UUID, Ref<ScriptInstance>> EntityInstances; std::unordered_map<UUID, Ref<ScriptInstance>> EntityInstances;
std::unordered_map<UUID, ScriptFieldMap> EntityScriptFields;
// runtime // runtime
Scene* SceneContext = nullptr; Scene* SceneContext = nullptr;
}; };
@ -306,6 +280,18 @@ namespace Hazel
return s_Data->EntityClasses; return s_Data->EntityClasses;
} }
Ref<ScriptClass> 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) void ScriptEngine::OnRuntimeStart(Scene* scene)
{ {
s_Data->SceneContext = scene; s_Data->SceneContext = scene;
@ -328,8 +314,18 @@ namespace Hazel
const auto& sc = entity.GetComponent<ScriptComponent>(); const auto& sc = entity.GetComponent<ScriptComponent>();
if (ClassExists(sc.ClassName)) if (ClassExists(sc.ClassName))
{ {
UUID entityID = entity.GetUUID();
Ref<ScriptInstance> instance = CreateRef<ScriptInstance>(s_Data->EntityClasses[sc.ClassName], entity); Ref<ScriptInstance> instance = CreateRef<ScriptInstance>(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(); instance->InvokeOnCreate();
} }

View File

@ -41,6 +41,36 @@ namespace Hazel
MonoClassField* ClassField; MonoClassField* ClassField;
}; };
struct ScriptFieldInstance
{
ScriptField Field;
template <typename T>
T GetValue()
{
return *(T*)m_Buffer;
}
template <typename T>
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<std::string, ScriptFieldInstance>;
class ScriptClass class ScriptClass
{ {
public: public:
@ -103,6 +133,9 @@ namespace Hazel
MonoMethod* m_OnUpdateMethod = nullptr; MonoMethod* m_OnUpdateMethod = nullptr;
inline static char s_FieldValueBuffer[8]; inline static char s_FieldValueBuffer[8];
friend class ScriptEngine;
friend struct ScriptFieldInstance;
}; };
class ScriptEngine class ScriptEngine
@ -126,6 +159,8 @@ namespace Hazel
static Ref<ScriptInstance> GetEntityScriptInstance(UUID entityID); static Ref<ScriptInstance> GetEntityScriptInstance(UUID entityID);
static std::unordered_map<std::string, Ref<ScriptClass>> GetEntityClasses(); static std::unordered_map<std::string, Ref<ScriptClass>> GetEntityClasses();
static Ref<ScriptClass> GetEntityClass(const std::string& name);
static ScriptFieldMap& GetScriptFieldMap(Entity entity);
static MonoImage* GetCoreAssemblyImage(); static MonoImage* GetCoreAssemblyImage();
@ -139,6 +174,57 @@ namespace Hazel
friend class ScriptClass; friend class ScriptClass;
friend class ScriptGlue; 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;
}
}
} }

View File

@ -18,7 +18,7 @@ namespace Hazel
static std::unordered_map<MonoType*, std::function<bool(Entity)>> s_EntityHasComponentFuncs; static std::unordered_map<MonoType*, std::function<bool(Entity)>> 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<const void*>(Name))
static void NativeLog(MonoString* text, int param) static void NativeLog(MonoString* text, int param)
{ {