#if UNITY_EDITOR using System; using System.IO; using System.Linq; using System.Reflection; using System.Text; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; public static class VerifyCampInteractionsAndPets { const string ScenePath = "Assets/Bundles/Scenes/Town/HomeCamp/HomeCamp.unity"; public static string Run() { var sb = new StringBuilder(); sb.AppendLine("=== Automated Verification: Camp Interactions, Building Nav & Pet Wandering ==="); bool allPassed = true; 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(); } // Phase 1: Entities Existence, Scale & Component Checks sb.AppendLine("\n[Phase 1: Scene Entities, 4x Scale & Component Checks]"); var explorerTf = root.transform.Find("Explorer"); if (explorerTf == null) { sb.AppendLine("[FAIL] Explorer missing in scene"); allPassed = false; } else { var ctrl = explorerTf.GetComponent(); bool scale4x = Mathf.Approximately(explorerTf.localScale.x, 4.0f); if (scale4x) { sb.AppendLine($"[PASS] Explorer scaled 4x ({explorerTf.localScale}), pos={explorerTf.position}, Controller={ctrl != null}, Layer={explorerTf.gameObject.layer}"); } else { sb.AppendLine($"[FAIL] Explorer scale is {explorerTf.localScale}, expected (4, 4, 4)"); allPassed = false; } } var petNames = new[] { "Pet_FireCat", "Pet_RockBall", "Pet_BudBud" }; foreach (var petName in petNames) { var petTf = root.transform.Find(petName); if (petTf == null) { sb.AppendLine($"[FAIL] Companion {petName} missing in scene"); allPassed = false; continue; } var wanderer = petTf.GetComponent(); var anim = petTf.GetComponentInChildren(); bool hasWanderer = wanderer != null; bool hasAnim = anim != null && anim.runtimeAnimatorController != null; bool correctLayer = petTf.gameObject.layer == 29; bool isEnlarged = petTf.localScale.x >= 2.8f; if (hasWanderer && hasAnim && correctLayer && isEnlarged) { sb.AppendLine($"[PASS] {petName} scaled 4x ({petTf.localScale}), Controller={anim.runtimeAnimatorController.name}, Layer={petTf.gameObject.layer}, WanderRadius={wanderer.wanderRadius}m"); } else { sb.AppendLine($"[FAIL] {petName} check failed: hasWanderer={hasWanderer}, hasAnim={hasAnim}, correctLayer={correctLayer}, isEnlarged={isEnlarged}"); allPassed = false; } } // Phase 2: Companion Forward-Walking Facing Verification sb.AppendLine("\n[Phase 2: Companion Forward-Walking Facing Verification]"); var fireCatTf = root.transform.Find("Pet_FireCat"); if (fireCatTf != null) { var wanderer = fireCatTf.GetComponent(); var anim = fireCatTf.GetComponentInChildren(); var motionHash = Animator.StringToHash("Motion"); // Initial state if (!wanderer.IsMoving) { sb.AppendLine("[PASS] Pet_FireCat initially idle (IsMoving=False)"); } else { sb.AppendLine("[FAIL] Pet_FireCat initially moving"); allPassed = false; } // Trigger Wander MoveTo Vector3 moveDelta = new Vector3(1.2f, 0, 0.8f); Vector3 petTarget = fireCatTf.position + moveDelta; wanderer.MoveTo(petTarget); // Rotate towards movement target using wanderer facing convention Vector3 lookDir = wanderer.modelFacesMinusZ ? -moveDelta.normalized : moveDelta.normalized; fireCatTf.rotation = Quaternion.LookRotation(lookDir, Vector3.up); // Companion front face is local -Z (Vector3.back in model space) Vector3 visualFront = fireCatTf.TransformDirection(Vector3.back); float facingError = Vector3.Angle(visualFront, moveDelta.normalized); if (wanderer.IsMoving && anim.GetInteger(motionHash) == 1 && facingError < 1.0f) { sb.AppendLine($"[PASS] Pet Walk Facing verified: Model front face (-Z) aligns with move direction (+{moveDelta.normalized}), facingError={facingError:F2}° (NOT walking backwards)"); } else { sb.AppendLine($"[FAIL] Pet Walk Facing incorrect: facingError={facingError:F2}°, IsMoving={wanderer.IsMoving}"); allPassed = false; } // Stop Pet wanderer.Stop(); if (!wanderer.IsMoving && anim.GetInteger(motionHash) == 0) { sb.AppendLine($"[PASS] Pet Stop triggered: IsMoving=False, Motion parameter = 0 (Idle)"); } else { sb.AppendLine($"[FAIL] Pet Stop did not return to Motion=0: IsMoving={wanderer.IsMoving}, Motion={anim.GetInteger(motionHash)}"); allPassed = false; } // Social greeting simulation if (explorerTf != null) { explorerTf.position = fireCatTf.position + new Vector3(1.0f, 0, 0.8f); Vector3 toExplorer = explorerTf.position - fireCatTf.position; toExplorer.y = 0; if (toExplorer.magnitude < wanderer.greetingDistance) { Vector3 greetDir = wanderer.modelFacesMinusZ ? -toExplorer.normalized : toExplorer.normalized; fireCatTf.rotation = Quaternion.LookRotation(greetDir, Vector3.up); Vector3 greetFront = fireCatTf.TransformDirection(Vector3.back); float greetError = Vector3.Angle(greetFront, toExplorer.normalized); if (greetError < 1.0f) { sb.AppendLine($"[PASS] Social Greeting: Pet front face directly faces Explorer (dist={toExplorer.magnitude:F2}m, greetError={greetError:F2}°)"); } else { sb.AppendLine($"[FAIL] Social Greeting face error: {greetError:F2}°"); allPassed = false; } } } } // Phase 3: Building Interaction & Arrival Callback Simulation sb.AppendLine("\n[Phase 3: Building Navigation & Arrival Callback Simulation]"); if (explorerTf != null) { var explorerCtrl = explorerTf.GetComponent(); bool arrivalCallbackFired = false; Vector3 mainHouseEntrance = new Vector3(0f, 0f, 3.5f); Vector3 mainHouseFacing = Vector3.forward; explorerTf.position = new Vector3(0f, 0f, -3.5f); explorerCtrl.MoveTo(mainHouseEntrance, () => { arrivalCallbackFired = true; }, mainHouseFacing); if (explorerCtrl.IsMoving) { sb.AppendLine($"[PASS] MoveTo building entrance initiated: IsMoving=True, Target={explorerCtrl.TargetPosition}"); } else { sb.AppendLine("[FAIL] MoveTo building entrance failed to start"); allPassed = false; } // Simulate movement until arrival int steps = 0; while (explorerCtrl.IsMoving && steps < 150) { Vector3 toTgt = explorerCtrl.TargetPosition - explorerTf.position; toTgt.y = 0; float dist = toTgt.magnitude; if (dist <= explorerCtrl.stopDistance) { explorerCtrl.Stop(); break; } explorerTf.position += toTgt.normalized * Mathf.Min(explorerCtrl.moveSpeed * 0.05f, dist); steps++; } float finalDist = Vector2.Distance(new Vector2(explorerTf.position.x, explorerTf.position.z), new Vector2(mainHouseEntrance.x, mainHouseEntrance.z)); float facingAngleDiff = Vector3.Angle(explorerTf.forward, mainHouseFacing); if (!explorerCtrl.IsMoving && finalDist <= explorerCtrl.stopDistance + 0.05f && arrivalCallbackFired && facingAngleDiff < 5.0f) { sb.AppendLine($"[PASS] Explorer reached MainHouse entrance in {steps} steps! ArrivalCallbackFired=True, FacingAngleDiff={facingAngleDiff:F1}°, FinalPos={explorerTf.position}"); } else { sb.AppendLine($"[FAIL] Building navigation arrival failed: finalDist={finalDist:F3}, callback={arrivalCallbackFired}, facingDiff={facingAngleDiff:F1}°"); allPassed = false; } } // Phase 4: Click Indicator Validation sb.AppendLine("\n[Phase 4: Click Indicator Validation]"); if (explorerTf != null) { var explorerCtrl = explorerTf.GetComponent(); explorerCtrl.MoveTo(new Vector3(1.5f, 0, -2.0f)); var indicator = root.transform.Find("ClickIndicator"); if (indicator != null && indicator.gameObject.activeSelf) { sb.AppendLine($"[PASS] ClickIndicator instantiated and active at: {indicator.position}"); } else { sb.AppendLine("[FAIL] ClickIndicator not active during movement"); allPassed = false; } explorerCtrl.Stop(); if (indicator != null && !indicator.gameObject.activeSelf) { sb.AppendLine("[PASS] ClickIndicator deactivated upon arrival/stop"); } else { sb.AppendLine("[FAIL] ClickIndicator failed to deactivate upon stop"); allPassed = false; } } } finally { EditorSceneManager.CloseScene(scene, true); } sb.AppendLine($"\nOverall Result: {(allPassed ? "ALL TESTS PASSED" : "TESTS FAILED")}"); return sb.ToString(); } } #endif