223 lines
9.0 KiB
C#
223 lines
9.0 KiB
C#
#if UNITY_EDITOR
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using UnityEditor;
|
|
using UnityEditor.Animations;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
|
|
public static class VerifyExplorerAndCampLocomotion
|
|
{
|
|
const string PrefabPath = "Assets/Bundles/Characters/Human/Explorer/Explorer.prefab";
|
|
const string ControllerPath = "Assets/Bundles/Characters/Human/Explorer/Explorer.controller";
|
|
const string ScenePath = "Assets/Bundles/Scenes/Town/HomeCamp/HomeCamp.unity";
|
|
|
|
public static string Run()
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("=== Automated Verification: Explorer Protagonist & Camp Locomotion ===");
|
|
bool allPassed = true;
|
|
|
|
// Phase 1: Explorer Asset & Rigging Validation
|
|
sb.AppendLine("\n[Phase 1: Explorer Asset & Controller Validation]");
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
|
|
if (prefab == null)
|
|
{
|
|
sb.AppendLine("[FAIL] Explorer.prefab not found at " + PrefabPath);
|
|
return sb.ToString();
|
|
}
|
|
|
|
var animator = prefab.GetComponent<Animator>();
|
|
if (animator == null || animator.avatar == null || !animator.avatar.isValid)
|
|
{
|
|
sb.AppendLine("[FAIL] Animator or valid Humanoid Avatar missing on Explorer.prefab");
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"[PASS] Explorer.prefab has valid Humanoid Avatar: {animator.avatar.name}");
|
|
}
|
|
|
|
var controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(ControllerPath);
|
|
if (controller == null)
|
|
{
|
|
sb.AppendLine("[FAIL] Explorer.controller not found at " + ControllerPath);
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
var sm = controller.layers[0].stateMachine;
|
|
var states = sm.states.Select(s => s.state.name).ToArray();
|
|
bool hasIdle = states.Contains("Idle");
|
|
bool hasWalk = states.Contains("Walk");
|
|
bool hasParam = controller.parameters.Any(p => p.name == "isMoving");
|
|
if (hasIdle && hasWalk && hasParam)
|
|
{
|
|
sb.AppendLine($"[PASS] Explorer.controller configured with states: [{string.Join(", ", states)}], default={sm.defaultState.name}, parameter 'isMoving' present");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"[FAIL] Explorer.controller states or parameter missing: hasIdle={hasIdle}, hasWalk={hasWalk}, hasParam={hasParam}");
|
|
allPassed = false;
|
|
}
|
|
}
|
|
|
|
var smr = prefab.GetComponentInChildren<SkinnedMeshRenderer>();
|
|
if (smr == null || smr.sharedMaterial == null)
|
|
{
|
|
sb.AppendLine("[FAIL] SkinnedMeshRenderer or material missing on Explorer.prefab");
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
var mat = smr.sharedMaterial;
|
|
sb.AppendLine($"[PASS] SkinnedMeshRenderer bound to material: {mat.name} (Shader: {mat.shader.name})");
|
|
}
|
|
|
|
// Phase 2: Scene Placement & Locomotion Logic Validation
|
|
sb.AppendLine("\n[Phase 2: Scene Placement & Locomotion Step Simulation]");
|
|
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Additive);
|
|
try
|
|
{
|
|
var root = scene.GetRootGameObjects().FirstOrDefault(g => g.name == "HomeCamp");
|
|
if (root == null)
|
|
{
|
|
sb.AppendLine("[FAIL] HomeCamp root not found in " + ScenePath);
|
|
return sb.ToString();
|
|
}
|
|
|
|
var explorerTf = root.transform.Find("Explorer");
|
|
if (explorerTf == null)
|
|
{
|
|
sb.AppendLine("[FAIL] Explorer not found under HomeCamp scene root");
|
|
allPassed = false;
|
|
return sb.ToString();
|
|
}
|
|
|
|
sb.AppendLine($"[PASS] Explorer placed in HomeCamp scene at: {explorerTf.position}, rotation: {explorerTf.eulerAngles}");
|
|
|
|
// Find Controller Type via reflection
|
|
var ctrlType = AppDomain.CurrentDomain.GetAssemblies()
|
|
.Select(a => a.GetType("HotUpdate.Gameplay.CampExplorerController"))
|
|
.FirstOrDefault(t => t != null);
|
|
|
|
if (ctrlType == null)
|
|
{
|
|
sb.AppendLine("[FAIL] Type HotUpdate.Gameplay.CampExplorerController not found in AppDomain");
|
|
allPassed = false;
|
|
return sb.ToString();
|
|
}
|
|
|
|
var ctrl = explorerTf.GetComponent(ctrlType);
|
|
if (ctrl == null)
|
|
{
|
|
sb.AppendLine("[FAIL] CampExplorerController missing on Explorer in scene");
|
|
allPassed = false;
|
|
return sb.ToString();
|
|
}
|
|
|
|
var isMovingProp = ctrlType.GetProperty("IsMoving");
|
|
var targetPosProp = ctrlType.GetProperty("TargetPosition");
|
|
var moveToMethod = ctrlType.GetMethod("MoveTo", new[] { typeof(Vector3) });
|
|
var stopMethod = ctrlType.GetMethod("Stop");
|
|
var stopDistField = ctrlType.GetField("stopDistance");
|
|
var moveSpeedField = ctrlType.GetField("moveSpeed");
|
|
|
|
bool isMoving = (bool)isMovingProp.GetValue(ctrl);
|
|
if (isMoving)
|
|
{
|
|
sb.AppendLine("[FAIL] Explorer initially moving when should be idle");
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("[PASS] Initial state: Explorer is idle (IsMoving=False)");
|
|
}
|
|
|
|
// Issue Move Command
|
|
Vector3 startPos = explorerTf.position;
|
|
Vector3 targetPos = startPos + new Vector3(2.5f, 0, 1.5f);
|
|
moveToMethod.Invoke(ctrl, new object[] { targetPos });
|
|
|
|
isMoving = (bool)isMovingProp.GetValue(ctrl);
|
|
Vector3 curTarget = (Vector3)targetPosProp.GetValue(ctrl);
|
|
if (!isMoving)
|
|
{
|
|
sb.AppendLine("[FAIL] MoveTo did not set IsMoving to true");
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"[PASS] MoveTo({targetPos}) triggered: IsMoving=True, Target={curTarget}");
|
|
}
|
|
|
|
// Simulate walk
|
|
float stopDist = (float)stopDistField.GetValue(ctrl);
|
|
float moveSpeed = (float)moveSpeedField.GetValue(ctrl);
|
|
float simDt = 0.05f;
|
|
int frames = 0;
|
|
while ((bool)isMovingProp.GetValue(ctrl) && frames < 100)
|
|
{
|
|
Vector3 toTarget = curTarget - explorerTf.position;
|
|
toTarget.y = 0;
|
|
float dist = toTarget.magnitude;
|
|
if (dist <= stopDist)
|
|
{
|
|
stopMethod.Invoke(ctrl, null);
|
|
break;
|
|
}
|
|
explorerTf.rotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
|
|
explorerTf.position += toTarget.normalized * Mathf.Min(moveSpeed * simDt, dist);
|
|
frames++;
|
|
}
|
|
|
|
isMoving = (bool)isMovingProp.GetValue(ctrl);
|
|
float finalDist = Vector3.Distance(new Vector3(explorerTf.position.x, 0, explorerTf.position.z), new Vector3(curTarget.x, 0, curTarget.z));
|
|
if (!isMoving && finalDist <= stopDist + 0.05f)
|
|
{
|
|
sb.AppendLine($"[PASS] Explorer successfully walked to target in {frames} steps! Final position: {explorerTf.position}, remaining dist: {finalDist:F3}m, IsMoving={isMoving} (returned to Idle)");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"[FAIL] Explorer failed to reach target: finalDist={finalDist}, IsMoving={isMoving}");
|
|
allPassed = false;
|
|
}
|
|
|
|
// Phase 3: Camera Raycast to Ground
|
|
sb.AppendLine("\n[Phase 3: Camera Raycast to Ground Verification]");
|
|
var camGo = root.transform.Find("HomeCampCamera")?.gameObject;
|
|
var camera = camGo?.GetComponent<Camera>();
|
|
if (camera == null)
|
|
{
|
|
sb.AppendLine("[FAIL] HomeCampCamera missing in scene");
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
// Raycast from bottom-center of screen (plaza ground)
|
|
Ray ray = camera.ViewportPointToRay(new Vector3(0.5f, 0.35f, 0));
|
|
int layerMask = 1 << 29;
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 150f, layerMask))
|
|
{
|
|
sb.AppendLine($"[PASS] Camera raycast hit ground at {hit.point} on collider '{hit.collider.name}' (layer {hit.collider.gameObject.layer})");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("[FAIL] Camera raycast missed ground surface");
|
|
allPassed = false;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
EditorSceneManager.CloseScene(scene, true);
|
|
}
|
|
|
|
sb.AppendLine($"\nOverall Result: {(allPassed ? "ALL TESTS PASSED" : "TESTS FAILED")}");
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
#endif |