233 lines
10 KiB
C#
233 lines
10 KiB
C#
#if UNITY_EDITOR
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using HotUpdate.UI;
|
|
|
|
public static class VerifyHomeCampAdaptationAndEntrances
|
|
{
|
|
const string ScenePath = "Assets/Bundles/Scenes/Town/HomeCamp/HomeCamp.unity";
|
|
const string PrefabPath = "Assets/Bundles/UI/Prefab/UI_MainHUD.prefab";
|
|
const float ReferenceAspect = 1080f / 1920f;
|
|
|
|
static readonly (string name, int w, int h)[] TestResolutions = new[]
|
|
{
|
|
("Standard 9:16", 1080, 1920),
|
|
("Long 9:18", 1080, 2160),
|
|
("iPhone 9:19.5", 1080, 2340),
|
|
("Android Long 9:20", 1080, 2400),
|
|
("Ultra Long 9:22", 1080, 2640),
|
|
("Wide 2:3", 1080, 1620),
|
|
("Tablet 3:4", 1080, 1440),
|
|
};
|
|
|
|
public static string Run()
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("=== Automated Verification: HomeCamp Camera Adaptation & Building Entrances ===");
|
|
bool allPassed = true;
|
|
|
|
// 1. Verify UI Prefab & Bindings
|
|
sb.AppendLine("\n[Phase 1: UI Prefab & Event Dispatch Verification]");
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
|
|
if (prefab == null) return "FAIL: UI_MainHUD.prefab not found";
|
|
|
|
var hudGo = UnityEngine.Object.Instantiate(prefab);
|
|
try
|
|
{
|
|
var hudView = hudGo.GetComponent<MainHudView>() ?? hudGo.AddComponent<MainHudView>();
|
|
var group = hudGo.transform.Find("Group_CampBuildings");
|
|
if (group == null)
|
|
{
|
|
sb.AppendLine("FAIL: Group_CampBuildings not found in UI_MainHUD.prefab");
|
|
return sb.ToString();
|
|
}
|
|
|
|
var triggeredKeys = new List<string>();
|
|
hudView.OnHomeActionClicked += key => triggeredKeys.Add(key);
|
|
hudView.Bind();
|
|
|
|
var expectedButtons = new (string btnName, string expectedKey, string expectedTitle)[]
|
|
{
|
|
("Btn_Building_MainHouse", "Camp", "营地大厅"),
|
|
("Btn_Building_Shop", "Shop", "小镇商店"),
|
|
("Btn_Building_Pets", "Pets", "伙伴之家"),
|
|
("Btn_Building_Fountain", "Rewards", "祈愿喷泉"),
|
|
("Btn_Building_Stall", "Daily", "集市货摊"),
|
|
};
|
|
|
|
foreach (var (btnName, expectedKey, expectedTitle) in expectedButtons)
|
|
{
|
|
var btnTf = group.Find(btnName);
|
|
if (btnTf == null)
|
|
{
|
|
sb.AppendLine($"[FAIL] Button {btnName} not found under Group_CampBuildings");
|
|
allPassed = false;
|
|
continue;
|
|
}
|
|
var btn = btnTf.GetComponent<Button>();
|
|
var tmp = btnTf.GetComponentInChildren<TextMeshProUGUI>();
|
|
if (btn == null)
|
|
{
|
|
sb.AppendLine($"[FAIL] Button component missing on {btnName}");
|
|
allPassed = false;
|
|
continue;
|
|
}
|
|
if (tmp == null || tmp.text != expectedTitle)
|
|
{
|
|
sb.AppendLine($"[FAIL] TextMeshProUGUI mismatch on {btnName}: expected '{expectedTitle}', got '{(tmp != null ? tmp.text : "null")}'");
|
|
allPassed = false;
|
|
}
|
|
|
|
// Simulate click
|
|
triggeredKeys.Clear();
|
|
btn.onClick.Invoke();
|
|
if (triggeredKeys.Count == 1 && triggeredKeys[0] == expectedKey)
|
|
{
|
|
sb.AppendLine($"[PASS] {btnName} clicked -> dispatched key '{expectedKey}', title='{tmp?.text}'");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"[FAIL] {btnName} clicked -> dispatched '{string.Join(",", triggeredKeys)}', expected '{expectedKey}'");
|
|
allPassed = false;
|
|
}
|
|
}
|
|
|
|
// 2. Verify Scene Camera & Multi-resolution Adaptation
|
|
sb.AppendLine("\n[Phase 2: Scene Multi-resolution Adaptation Verification]");
|
|
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 camGo = root.transform.Find("HomeCampCamera")?.gameObject;
|
|
var camera = camGo?.GetComponent<Camera>();
|
|
var backdrop = root.transform.Find("DistantLakeWaterfallCastle");
|
|
var ground = root.transform.Find("Ground");
|
|
|
|
if (camera == null || backdrop == null || ground == null)
|
|
{
|
|
sb.AppendLine("FAIL: Missing essential scene elements (Camera/Backdrop/Ground)");
|
|
return sb.ToString();
|
|
}
|
|
|
|
// Buildings in scene
|
|
var buildings = new (string name, Transform tf, Vector3 anchor)[]
|
|
{
|
|
("MainHouse", root.transform.Find("MainHouse"), new Vector3(0, 6.7f, 7.2f)),
|
|
("HouseLeft", root.transform.Find("HouseLeft"), new Vector3(-6.8f, 4.0f, 3.6f)),
|
|
("HouseRight", root.transform.Find("HouseRight"), new Vector3(7.5f, 4.2f, 4.0f)),
|
|
("Fountain", root.transform.Find("Fountain"), new Vector3(0, 2.5f, 0.8f)),
|
|
("StallRight", root.transform.Find("StallRight"), new Vector3(4.5f, 2.6f, 2.8f)),
|
|
};
|
|
|
|
foreach (var b in buildings)
|
|
{
|
|
if (b.tf == null)
|
|
{
|
|
sb.AppendLine($"[FAIL] Scene building transform missing: {b.name}");
|
|
allPassed = false;
|
|
}
|
|
}
|
|
|
|
// Simulate presenter's Resize and Anchoring for each test resolution
|
|
var hudRt = hudGo.GetComponent<RectTransform>();
|
|
foreach (var res in TestResolutions)
|
|
{
|
|
float aspect = (float)res.w / res.h;
|
|
float adaptedFov = 2f * Mathf.Atan(Mathf.Tan(25f * Mathf.Deg2Rad) * Mathf.Max(1f, ReferenceAspect / aspect)) * Mathf.Rad2Deg;
|
|
camera.fieldOfView = adaptedFov;
|
|
camera.aspect = aspect;
|
|
|
|
// Presenter runtime backdrop adaptation
|
|
const float dist = 75f;
|
|
float frustumHalfH = dist * Mathf.Tan(adaptedFov * Mathf.Deg2Rad * 0.5f);
|
|
float frustumHalfW = frustumHalfH * aspect;
|
|
float bdW = Mathf.Max(72f, frustumHalfW * 2f * 1.15f);
|
|
float bdH = Mathf.Max(56f, frustumHalfH * 1.35f);
|
|
float topHeight = frustumHalfH * 1.08f;
|
|
backdrop.localScale = new Vector3(bdW, bdH, 1f);
|
|
backdrop.position = camera.transform.position + camera.transform.forward * dist + camera.transform.up * (topHeight - bdH * 0.5f);
|
|
backdrop.rotation = camera.transform.rotation;
|
|
|
|
// Check backdrop bounds in camera space
|
|
Vector3 bdCamPos = camera.transform.InverseTransformPoint(backdrop.position);
|
|
float bdHalfW = backdrop.lossyScale.x * 0.5f;
|
|
float bdHalfH = backdrop.lossyScale.y * 0.5f;
|
|
float bdMinX = bdCamPos.x - bdHalfW;
|
|
float bdMaxX = bdCamPos.x + bdHalfW;
|
|
float bdMaxY = bdCamPos.y + bdHalfH;
|
|
|
|
bool leakLeft = bdMinX > -frustumHalfW;
|
|
bool leakRight = bdMaxX < frustumHalfW;
|
|
bool leakTop = bdMaxY < frustumHalfH;
|
|
|
|
if (leakLeft || leakRight || leakTop)
|
|
{
|
|
sb.AppendLine($"[FAIL] {res.name}: Backdrop Leak! Left={leakLeft}, Right={leakRight}, Top={leakTop}");
|
|
allPassed = false;
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"[PASS] {res.name} (FOV={adaptedFov:F1}°): Backdrop 100% covers visible frustum (X:[{bdMinX:F1},{bdMaxX:F1}] vs Frustum:[{-frustumHalfW:F1},{frustumHalfW:F1}], Y_top:{bdMaxY:F1} vs Frustum:{frustumHalfH:F1})");
|
|
}
|
|
|
|
// Check building world-to-screen UI anchoring
|
|
Vector2 canvasSize = new Vector2(1080f, 1080f / aspect);
|
|
foreach (var (bName, bTf, anchor) in buildings)
|
|
{
|
|
Vector3 worldPos = bTf != null ? bTf.position : anchor;
|
|
worldPos.x = anchor.x; worldPos.y = anchor.y; worldPos.z = anchor.z;
|
|
Vector3 vp = camera.WorldToViewportPoint(worldPos);
|
|
|
|
if (vp.z <= 0)
|
|
{
|
|
sb.AppendLine($"[FAIL] {res.name}: {bName} is behind camera");
|
|
allPassed = false;
|
|
continue;
|
|
}
|
|
|
|
float x = (vp.x - 0.5f) * canvasSize.x;
|
|
float y = (vp.y - 0.5f) * canvasSize.y;
|
|
float halfW = canvasSize.x * 0.5f;
|
|
float halfH = canvasSize.y * 0.5f;
|
|
float clampedX = Mathf.Clamp(x, -halfW + 110f + 15f, halfW - 110f - 15f);
|
|
float clampedY = Mathf.Clamp(y, -halfH + 280f, halfH - 180f);
|
|
|
|
// Ensure coordinates are finite and inside safe canvas
|
|
if (float.IsNaN(clampedX) || float.IsNaN(clampedY) || Mathf.Abs(clampedX) > halfW || Mathf.Abs(clampedY) > halfH)
|
|
{
|
|
sb.AppendLine($"[FAIL] {res.name}: {bName} anchoredPosition invalid: ({clampedX}, {clampedY})");
|
|
allPassed = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
EditorSceneManager.CloseScene(scene, true);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(hudGo);
|
|
}
|
|
|
|
sb.AppendLine($"\nOverall Result: {(allPassed ? "ALL TESTS PASSED" : "TESTS FAILED")}");
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
#endif
|