feat(gameplay): 更新单位属性和材质配置 - 调整多个单位的基础生命值和攻击力数值平衡 - 修改CoreBattleGround材质的纹理缩放比例 - 更新地砖材质颜色为白色 - 优化纹理资源设置,调整最大纹理尺寸并添加PlayableAds平台支持 - 修复弓箭手技能目标查找逻辑,改用统一的敌人检测方法 - 为水晶矿添加中立阵营设置和光球视觉效果同步 - 调整预制体中的全局缩放参数 - 为狼人和石巨人添加NavMeshAgent组件支持 - 优化水晶矿血条显示位置计算方式 ```
306 lines
12 KiB
C#
306 lines
12 KiB
C#
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
/// <summary>
|
|
/// 建筑攻击标志控制器(双阵营):每个阵营各自一个攻击标志,默认指向敌方主城堡。
|
|
/// 本地玩家点击建筑切换自己阵营的标志;两方标志同时显示,指向同一建筑时并排错开。
|
|
/// 单位寻敌:周围有敌人打敌人,否则直奔己方标志建筑;到达后是我方阵营(增援完成)→ 己方标志切回主城堡。
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>获取阵营当前的攻击目标(无效时兜底敌方主城堡)。单位出生时调用一次做使命快照。</summary>
|
|
public GameObject GetTeamTarget(int teamId)
|
|
{
|
|
GameObject t = teamId == 0 ? blueTarget : redTarget;
|
|
if (t != null && t.activeInHierarchy) return t;
|
|
return GetEnemyCastleOfTeam(teamId);
|
|
}
|
|
|
|
/// <summary>获取阵营的敌方主城堡(teamId 0→红色城堡,1→蓝色城堡),带缓存。</summary>
|
|
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<Renderer>();
|
|
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<Renderer>();
|
|
redMarker = CreateMarker("红方攻击标志", RedColor);
|
|
redMarkerRenderers = redMarker.GetComponentsInChildren<Renderer>();
|
|
}
|
|
|
|
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<MeshFilter>().sharedMesh = CreateArrowMesh();
|
|
|
|
var mat = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
|
|
mat.SetColor(TintColorId, color);
|
|
markerGo.GetComponent<Renderer>().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<Vector3>();
|
|
var normals = new System.Collections.Generic.List<Vector3>();
|
|
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;
|
|
}
|
|
}
|