using UnityEngine; using UnityEngine.EventSystems; /// /// 建筑攻击标志控制器(双阵营):每个阵营各自一个攻击标志,默认指向敌方主城堡。 /// 本地玩家点击建筑切换自己阵营的标志;两方标志同时显示,指向同一建筑时并排错开。 /// 单位寻敌:周围有敌人打敌人,否则直奔己方标志建筑;到达后是我方阵营(增援完成)→ 己方标志切回主城堡。 /// public class AttackTargetController : MonoBehaviour { public static AttackTargetController Instance { get; private set; } [Header("蓝方(teamId=0)攻击目标")] public GameObject blueTarget; [Header("红方(teamId=1)攻击目标")] public GameObject redTarget; [Header("标志视觉(头顶箭头)")] [Tooltip("标志 Prefab:拖入自定义图标替换默认箭头;留空时代码生成默认箭头")] [SerializeField] private GameObject markerPrefab; [Tooltip("标志相对建筑包围盒顶部的额外高度")] [SerializeField] private float markerHeightOffset = 0.8f; [Tooltip("双方标志指向同一建筑时的并排错开距离")] [SerializeField] private float markerSideOffset = 1.1f; [Tooltip("建筑子级下该名字的空物体 = 标志锚点(可选,编辑器可调位置)")] [SerializeField] private string anchorName = "标志锚点"; private Transform blueMarker; private Transform redMarker; private Renderer[] blueMarkerRenderers; private Renderer[] redMarkerRenderers; private GameObject cachedBlueEnemyCastle; // teamId 0 的敌方主城堡(红色城堡) private GameObject cachedRedEnemyCastle; // teamId 1 的敌方主城堡(蓝色城堡) private float nextCastleRefreshTime; private const float CastleRefreshInterval = 0.5f; private Camera mainCamera; private static readonly int TintColorId = Shader.PropertyToID("_TintColor"); private static readonly int ColorId = Shader.PropertyToID("_Color"); private static readonly Color BlueColor = new Color(0.2f, 0.55f, 1f, 0.95f); private static readonly Color RedColor = new Color(1f, 0.2f, 0.15f, 0.95f); private MaterialPropertyBlock propertyBlock; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } private void Start() { mainCamera = Camera.main; CreateMarkers(); blueTarget = GetEnemyCastleOfTeam(0); redTarget = GetEnemyCastleOfTeam(1); Debug.Log($"攻击标志初始: 蓝方→{blueTarget?.name ?? "未找到"} 红方→{redTarget?.name ?? "未找到"}"); } private void Update() { // 主城堡引用失效时定期重查(场景加载时序容错) if ((cachedBlueEnemyCastle == null || cachedRedEnemyCastle == null) && Time.time >= nextCastleRefreshTime) { nextCastleRefreshTime = Time.time + CastleRefreshInterval; cachedBlueEnemyCastle = FindBuilding("红色城堡"); cachedRedEnemyCastle = FindBuilding("蓝色城堡"); if (blueTarget == null && cachedBlueEnemyCastle != null) blueTarget = cachedBlueEnemyCastle; if (redTarget == null && cachedRedEnemyCastle != null) redTarget = cachedRedEnemyCastle; } HandleClick(); } // 点击建筑:只切换本地玩家阵营的攻击标志 private void HandleClick() { if (mainCamera == null) mainCamera = Camera.main; if (mainCamera == null) return; bool clicked = Input.GetMouseButtonDown(0) || (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began); if (!clicked) return; // 点击落在 UI 上不处理 if (EventSystem.current != null) { if (Input.touchCount > 0) { if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId)) return; } else if (EventSystem.current.IsPointerOverGameObject()) return; } Vector2 screenPos = Input.touchCount > 0 ? (Vector2)Input.GetTouch(0).position : Input.mousePosition; Ray ray = mainCamera.ScreenPointToRay(screenPos); if (Physics.Raycast(ray, out RaycastHit hit, 500f)) { Transform building = FindBuildingInParents(hit.collider.transform); if (building == null) return; int localTeam = GameManager.Instance != null ? GameManager.Instance.LocalPlayerTeamId : 0; // 点自己方主城堡无效 if (building.name == GetTeamCastleName(localTeam)) return; if (localTeam == 0) { blueTarget = building.gameObject; } else { redTarget = building.gameObject; } Debug.Log($"{(localTeam == 0 ? "蓝方" : "红方")}攻击标志 → {building.name}"); } } /// 获取阵营当前的攻击目标(无效时兜底敌方主城堡)。单位出生时调用一次做使命快照。 public GameObject GetTeamTarget(int teamId) { GameObject t = teamId == 0 ? blueTarget : redTarget; if (t != null && t.activeInHierarchy) return t; return GetEnemyCastleOfTeam(teamId); } /// 获取阵营的敌方主城堡(teamId 0→红色城堡,1→蓝色城堡),带缓存。 public GameObject GetEnemyCastleOfTeam(int teamId) { if (teamId == 0) { if (cachedBlueEnemyCastle == null || !cachedBlueEnemyCastle.activeInHierarchy) { cachedBlueEnemyCastle = FindBuilding("红色城堡"); } return cachedBlueEnemyCastle; } if (cachedRedEnemyCastle == null || !cachedRedEnemyCastle.activeInHierarchy) { cachedRedEnemyCastle = FindBuilding("蓝色城堡"); } return cachedRedEnemyCastle; } private static GameObject FindBuilding(string name) { var found = GameObject.Find(name); return found != null && found.CompareTag("Building") ? found : null; } private static string GetTeamCastleName(int teamId) { return teamId == 0 ? "蓝色城堡" : "红色城堡"; } private static Transform FindBuildingInParents(Transform t) { while (t != null) { if (t.CompareTag("Building")) return t; t = t.parent; } return null; } // 双标志:位置/朝向/颜色(指向同一建筑时并排) private void LateUpdate() { if (mainCamera == null) mainCamera = Camera.main; bool sameBuilding = blueTarget != null && blueTarget == redTarget; float blueSide = sameBuilding ? markerSideOffset : 0f; float redSide = sameBuilding ? -markerSideOffset : 0f; UpdateMarker(blueMarker, blueMarkerRenderers, blueTarget, BlueColor, blueSide); UpdateMarker(redMarker, redMarkerRenderers, redTarget, RedColor, redSide); } private void UpdateMarker(Transform marker, Renderer[] renderers, GameObject target, Color color, float sideOffset) { if (marker == null) return; if (target == null) { marker.gameObject.SetActive(false); return; } marker.gameObject.SetActive(true); // 位置:锚点优先(可选),否则建筑包围盒顶部 + 高度偏移 Vector3 pos; Transform anchor = target.transform.Find(anchorName); if (anchor != null) { pos = anchor.position; } else { Bounds bounds = CalculateTargetBounds(target); pos = new Vector3(target.transform.position.x, bounds.max.y + markerHeightOffset, target.transform.position.z); } // 指向同一建筑时沿相机右方向并排错开 if (sideOffset != 0f && mainCamera != null) { pos += mainCamera.transform.right * sideOffset; } marker.position = pos; // billboard:箭头尖朝下指着建筑 if (mainCamera != null) { Vector3 toCamera = mainCamera.transform.position - marker.position; marker.rotation = Quaternion.LookRotation(toCamera) * Quaternion.Euler(90f, 0f, 0f); } // 颜色按标志所属阵营 if (renderers == null) return; propertyBlock ??= new MaterialPropertyBlock(); foreach (Renderer rend in renderers) { if (rend == null) continue; rend.GetPropertyBlock(propertyBlock); propertyBlock.SetColor(TintColorId, color); propertyBlock.SetColor(ColorId, color); rend.SetPropertyBlock(propertyBlock); } } private static Bounds CalculateTargetBounds(GameObject target) { var renderers = target.GetComponentsInChildren(); if (renderers.Length == 0) return new Bounds(target.transform.position, Vector3.one); Bounds b = renderers[0].bounds; for (int i = 1; i < renderers.Length; i++) b.Encapsulate(renderers[i].bounds); return b; } private void CreateMarkers() { if (blueMarker != null && redMarker != null) return; blueMarker = CreateMarker("蓝方攻击标志", BlueColor); blueMarkerRenderers = blueMarker.GetComponentsInChildren(); redMarker = CreateMarker("红方攻击标志", RedColor); redMarkerRenderers = redMarker.GetComponentsInChildren(); } private Transform CreateMarker(string name, Color color) { GameObject markerGo; // 优先用可替换的标志 Prefab(可在 Inspector 拖入自定义图标) if (markerPrefab != null) { markerGo = Instantiate(markerPrefab, transform); markerGo.name = name; } else { // 兜底:代码生成默认倒锥箭头(尖朝下指着建筑) markerGo = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer)); markerGo.transform.SetParent(transform, false); markerGo.GetComponent().sharedMesh = CreateArrowMesh(); var mat = new Material(Shader.Find("Legacy Shaders/Particles/Additive")); mat.SetColor(TintColorId, color); markerGo.GetComponent().sharedMaterial = mat; } return markerGo.transform; } // 4面锥体:尖朝下,高2,底半径0.7 private static Mesh CreateArrowMesh() { var mesh = new Mesh(); mesh.name = "AttackMarkerArrow"; Vector3 tip = new Vector3(0f, -1.2f, 0f); Vector3[] baseQuad = { new Vector3(-0.7f, 0.8f, -0.7f), new Vector3(0.7f, 0.8f, -0.7f), new Vector3(0.7f, 0.8f, 0.7f), new Vector3(-0.7f, 0.8f, 0.7f) }; var vertices = new System.Collections.Generic.List(); var normals = new System.Collections.Generic.List(); void AddFace(Vector3 a, Vector3 b, Vector3 c, Vector3 n) { vertices.AddRange(new[] { a, b, c }); normals.AddRange(new[] { n, n, n }); } AddFace(baseQuad[0], baseQuad[1], tip, new Vector3(0f, 0f, -1f)); // 后 AddFace(baseQuad[1], baseQuad[2], tip, new Vector3(1f, 0f, 0f)); // 右 AddFace(baseQuad[2], baseQuad[3], tip, new Vector3(0f, 0f, 1f)); // 前 AddFace(baseQuad[3], baseQuad[0], tip, new Vector3(-1f, 0f, 0f)); // 左 mesh.SetVertices(vertices); mesh.SetNormals(normals); mesh.triangles = new int[] { 0,1,2, 3,4,5, 6,7,8, 9,10,11 }; mesh.RecalculateBounds(); return mesh; } }