Add Unity-style GetComponents scripting API
This commit is contained in:
@@ -260,6 +260,6 @@ C# 脚本模块已经完成了第一阶段的核心闭环,不再是“从 0
|
||||
- 已通过 `MonoScriptRuntimeTest.*` 与 `ProjectScriptAssemblyTest.*` 相关整组验证。
|
||||
|
||||
- 下一步建议继续做第二批 Unity API 对齐:
|
||||
- `GetComponents<T>()`
|
||||
- `Object.Instantiate`
|
||||
- `tag / CompareTag / layer`
|
||||
- 已完成第二步第一小项:`GetComponents<T>()`
|
||||
- 下一步候选:`Object.Instantiate`
|
||||
- 下一步候选:`tag / CompareTag / layer`
|
||||
|
||||
@@ -262,6 +262,15 @@ bool HasNativeComponent(Components::GameObject* gameObject, ManagedComponentKind
|
||||
return FindNativeComponent(gameObject, componentKind) != nullptr;
|
||||
}
|
||||
|
||||
bool IsMatchingScriptComponent(const ScriptComponent* component, const ManagedComponentTypeInfo& typeInfo) {
|
||||
return component
|
||||
&& component->HasScriptClass()
|
||||
&& typeInfo.kind == ManagedComponentKind::Script
|
||||
&& component->GetAssemblyName() == typeInfo.assemblyName
|
||||
&& component->GetNamespaceName() == typeInfo.namespaceName
|
||||
&& component->GetClassName() == typeInfo.className;
|
||||
}
|
||||
|
||||
Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObject, ManagedComponentKind componentKind) {
|
||||
if (!gameObject) {
|
||||
return nullptr;
|
||||
@@ -302,13 +311,7 @@ ScriptComponent* FindMatchingScriptComponent(
|
||||
}
|
||||
|
||||
for (ScriptComponent* component : gameObject->GetComponents<ScriptComponent>()) {
|
||||
if (!component || !component->HasScriptClass()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component->GetAssemblyName() == typeInfo.assemblyName
|
||||
&& component->GetNamespaceName() == typeInfo.namespaceName
|
||||
&& component->GetClassName() == typeInfo.className) {
|
||||
if (IsMatchingScriptComponent(component, typeInfo)) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
@@ -423,6 +426,28 @@ Components::Space ResolveManagedSpace(int32_t value) {
|
||||
: Components::Space::Self;
|
||||
}
|
||||
|
||||
MonoArray* CreateManagedComponentArray(MonoClass* componentClass, const std::vector<MonoObject*>& components) {
|
||||
if (!componentClass) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MonoDomain* domain = mono_domain_get();
|
||||
if (!domain) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MonoArray* array = mono_array_new(domain, componentClass, static_cast<uintptr_t>(components.size()));
|
||||
if (!array) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (uintptr_t index = 0; index < components.size(); ++index) {
|
||||
mono_array_setref(array, index, components[index]);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
void InternalCall_Debug_Log(MonoString* message) {
|
||||
XCEngine::Debug::Logger::Get().Info(
|
||||
XCEngine::Debug::LogCategory::Scripting,
|
||||
@@ -608,6 +633,74 @@ MonoObject* InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoRe
|
||||
return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID);
|
||||
}
|
||||
|
||||
MonoArray* InternalCall_GameObject_GetComponents(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
|
||||
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
|
||||
if (!gameObject) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime();
|
||||
if (!runtime) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType);
|
||||
if (!typeInfo.monoClass) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<MonoObject*> managedComponents;
|
||||
auto appendNativeComponents = [&](const auto& nativeComponents) {
|
||||
for (const auto* component : nativeComponents) {
|
||||
if (!component || !component->GetGameObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (MonoObject* managedObject =
|
||||
runtime->CreateManagedComponentWrapper(typeInfo.monoClass, component->GetGameObject()->GetUUID())) {
|
||||
managedComponents.push_back(managedObject);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (typeInfo.kind) {
|
||||
case ManagedComponentKind::Script:
|
||||
for (ScriptComponent* component : gameObject->GetComponents<ScriptComponent>()) {
|
||||
if (!IsMatchingScriptComponent(component, typeInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!runtime->HasManagedInstance(component)) {
|
||||
ScriptEngine::Get().OnScriptComponentEnabled(component);
|
||||
}
|
||||
|
||||
if (MonoObject* managedObject = runtime->GetManagedInstanceObject(component)) {
|
||||
managedComponents.push_back(managedObject);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ManagedComponentKind::Transform:
|
||||
appendNativeComponents(gameObject->GetComponents<Components::TransformComponent>());
|
||||
break;
|
||||
case ManagedComponentKind::Camera:
|
||||
appendNativeComponents(gameObject->GetComponents<Components::CameraComponent>());
|
||||
break;
|
||||
case ManagedComponentKind::Light:
|
||||
appendNativeComponents(gameObject->GetComponents<Components::LightComponent>());
|
||||
break;
|
||||
case ManagedComponentKind::MeshFilter:
|
||||
appendNativeComponents(gameObject->GetComponents<Components::MeshFilterComponent>());
|
||||
break;
|
||||
case ManagedComponentKind::MeshRenderer:
|
||||
appendNativeComponents(gameObject->GetComponents<Components::MeshRendererComponent>());
|
||||
break;
|
||||
case ManagedComponentKind::Unknown:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CreateManagedComponentArray(typeInfo.monoClass, managedComponents);
|
||||
}
|
||||
|
||||
MonoObject* InternalCall_GameObject_GetComponentInChildren(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
|
||||
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
|
||||
if (!gameObject) {
|
||||
@@ -1404,6 +1497,7 @@ void RegisterInternalCalls() {
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetActive", reinterpret_cast<const void*>(&InternalCall_GameObject_SetActive));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_HasComponent", reinterpret_cast<const void*>(&InternalCall_GameObject_HasComponent));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponent", reinterpret_cast<const void*>(&InternalCall_GameObject_GetComponent));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponents", reinterpret_cast<const void*>(&InternalCall_GameObject_GetComponents));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponentInChildren", reinterpret_cast<const void*>(&InternalCall_GameObject_GetComponentInChildren));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponentInParent", reinterpret_cast<const void*>(&InternalCall_GameObject_GetComponentInParent));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::GameObject_AddComponent", reinterpret_cast<const void*>(&InternalCall_GameObject_AddComponent));
|
||||
|
||||
@@ -101,6 +101,7 @@ set(XCENGINE_GAME_SCRIPT_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/BuiltinComponentProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/AddComponentProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/FieldMetadataProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/GetComponentsProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/ScriptComponentApiProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/RuntimeGameObjectProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/HierarchyProbe.cs
|
||||
|
||||
49
managed/GameScripts/GetComponentsProbe.cs
Normal file
49
managed/GameScripts/GetComponentsProbe.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using XCEngine;
|
||||
|
||||
namespace Gameplay
|
||||
{
|
||||
public sealed class GetComponentsProbe : MonoBehaviour
|
||||
{
|
||||
public int ObservedTransformCount;
|
||||
public int ObservedCameraCount;
|
||||
public int ObservedCameraCountViaGameObject;
|
||||
public int ObservedMarkerCount;
|
||||
public int ObservedMeshRendererCount;
|
||||
public bool ObservedAllMarkersNonNull;
|
||||
public bool ObservedFirstMarkerMatchesGetComponent;
|
||||
public bool ObservedMarkerInstancesAreDistinct;
|
||||
public bool ObservedTransformBoundToHost;
|
||||
public string ObservedFirstMarkerHostName = string.Empty;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Transform[] transforms = GetComponents<Transform>();
|
||||
Camera[] cameras = GetComponents<Camera>();
|
||||
Camera[] camerasViaGameObject = gameObject.GetComponents<Camera>();
|
||||
ObjectApiMarkerProbe[] markers = GetComponents<ObjectApiMarkerProbe>();
|
||||
MeshRenderer[] meshRenderers = GetComponents<MeshRenderer>();
|
||||
ObjectApiMarkerProbe firstMarker = GetComponent<ObjectApiMarkerProbe>();
|
||||
|
||||
ObservedTransformCount = transforms.Length;
|
||||
ObservedCameraCount = cameras.Length;
|
||||
ObservedCameraCountViaGameObject = camerasViaGameObject.Length;
|
||||
ObservedMarkerCount = markers.Length;
|
||||
ObservedMeshRendererCount = meshRenderers.Length;
|
||||
ObservedAllMarkersNonNull = Array.TrueForAll(markers, marker => marker != null);
|
||||
ObservedFirstMarkerMatchesGetComponent = firstMarker != null
|
||||
&& markers.Length > 0
|
||||
&& ReferenceEquals(markers[0], firstMarker);
|
||||
ObservedMarkerInstancesAreDistinct = markers.Length >= 2
|
||||
&& !ReferenceEquals(markers[0], markers[1]);
|
||||
ObservedTransformBoundToHost = transforms.Length == 1
|
||||
&& transforms[0] != null
|
||||
&& transforms[0].GameObjectUUID == GameObjectUUID;
|
||||
|
||||
if (markers.Length > 0 && markers[0] != null)
|
||||
{
|
||||
ObservedFirstMarkerHostName = markers[0].gameObject.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,11 @@ namespace XCEngine
|
||||
return GameObject.GetComponent<T>();
|
||||
}
|
||||
|
||||
public T[] GetComponents<T>() where T : Component
|
||||
{
|
||||
return GameObject.GetComponents<T>();
|
||||
}
|
||||
|
||||
public T GetComponentInChildren<T>() where T : Component
|
||||
{
|
||||
return GameObject.GetComponentInChildren<T>();
|
||||
|
||||
@@ -67,6 +67,28 @@ namespace XCEngine
|
||||
return InternalCalls.GameObject_GetComponent(UUID, typeof(T)) as T;
|
||||
}
|
||||
|
||||
public T[] GetComponents<T>() where T : Component
|
||||
{
|
||||
Component[] components = InternalCalls.GameObject_GetComponents(UUID, typeof(T));
|
||||
if (components == null || components.Length == 0)
|
||||
{
|
||||
return System.Array.Empty<T>();
|
||||
}
|
||||
|
||||
if (components is T[] typedComponents)
|
||||
{
|
||||
return typedComponents;
|
||||
}
|
||||
|
||||
T[] result = new T[components.Length];
|
||||
for (int index = 0; index < components.Length; ++index)
|
||||
{
|
||||
result[index] = components[index] as T;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public T GetComponentInChildren<T>() where T : Component
|
||||
{
|
||||
return InternalCalls.GameObject_GetComponentInChildren(UUID, typeof(T)) as T;
|
||||
|
||||
@@ -86,6 +86,9 @@ namespace XCEngine
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern Component GameObject_GetComponent(ulong gameObjectUUID, Type componentType);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern Component[] GameObject_GetComponents(ulong gameObjectUUID, Type componentType);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern Component GameObject_GetComponentInChildren(ulong gameObjectUUID, Type componentType);
|
||||
|
||||
|
||||
@@ -1920,6 +1920,62 @@ TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparent
|
||||
EXPECT_EQ(reparentTarget->GetChildCount(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, UnityObjectApiGetComponentsReturnsDirectComponentsAndReusesManagedInstances) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* child = runtimeScene->CreateGameObject("Child", host);
|
||||
|
||||
host->AddComponent<CameraComponent>();
|
||||
child->AddComponent<CameraComponent>();
|
||||
child->AddComponent<MeshRendererComponent>();
|
||||
|
||||
AddScript(host, "Gameplay", "ObjectApiMarkerProbe");
|
||||
AddScript(host, "Gameplay", "ObjectApiMarkerProbe");
|
||||
ScriptComponent* hostProbe = AddScript(host, "Gameplay", "GetComponentsProbe");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
int32_t observedTransformCount = -1;
|
||||
int32_t observedCameraCount = -1;
|
||||
int32_t observedCameraCountViaGameObject = -1;
|
||||
int32_t observedMarkerCount = -1;
|
||||
int32_t observedMeshRendererCount = -1;
|
||||
bool observedAllMarkersNonNull = false;
|
||||
bool observedFirstMarkerMatchesGetComponent = false;
|
||||
bool observedMarkerInstancesAreDistinct = false;
|
||||
bool observedTransformBoundToHost = false;
|
||||
std::string observedFirstMarkerHostName;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedTransformCount", observedTransformCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedCameraCount", observedCameraCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedCameraCountViaGameObject", observedCameraCountViaGameObject));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedMarkerCount", observedMarkerCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedMeshRendererCount", observedMeshRendererCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedAllMarkersNonNull", observedAllMarkersNonNull));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedFirstMarkerMatchesGetComponent", observedFirstMarkerMatchesGetComponent));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedMarkerInstancesAreDistinct", observedMarkerInstancesAreDistinct));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedTransformBoundToHost", observedTransformBoundToHost));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(hostProbe, "ObservedFirstMarkerHostName", observedFirstMarkerHostName));
|
||||
|
||||
EXPECT_EQ(observedTransformCount, 1);
|
||||
EXPECT_EQ(observedCameraCount, 1);
|
||||
EXPECT_EQ(observedCameraCountViaGameObject, 1);
|
||||
EXPECT_EQ(observedMarkerCount, 2);
|
||||
EXPECT_EQ(observedMeshRendererCount, 0);
|
||||
EXPECT_TRUE(observedAllMarkersNonNull);
|
||||
EXPECT_TRUE(observedFirstMarkerMatchesGetComponent);
|
||||
EXPECT_TRUE(observedMarkerInstancesAreDistinct);
|
||||
EXPECT_TRUE(observedTransformBoundToHost);
|
||||
EXPECT_EQ(observedFirstMarkerHostName, "Host");
|
||||
|
||||
EXPECT_EQ(host->GetComponents<CameraComponent>().size(), 1u);
|
||||
EXPECT_EQ(host->GetComponents<ScriptComponent>().size(), 3u);
|
||||
EXPECT_EQ(child->GetComponents<CameraComponent>().size(), 1u);
|
||||
EXPECT_EQ(child->GetComponents<MeshRendererComponent>().size(), 1u);
|
||||
EXPECT_EQ(runtime->GetManagedInstanceCount(), 3u);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, UnityObjectApiSupportsHierarchyLookupAndDestroy) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* root = runtimeScene->CreateGameObject("Root");
|
||||
|
||||
Reference in New Issue
Block a user