feat(gameplay): 添加建筑系统和改进摄像机控制

- 移除人类士兵数据资产文件,重构为建筑放置系统
- 在CharacterData中添加productionInterval和buildingCost字段用于建筑生产
- 实现CameraFollow脚本的完整重写,支持键盘WASD拖动、鼠标拖动、触摸拖动
- 添加摄像机缩放功能,支持上下键和触摸双指缩放
- 更新Projecttile.cs以同时处理单位和建筑目标的伤害检测
- 修改SummonCardButton从法力值召唤改为金币建筑放置模式
- 在UnitBase.cs中更新攻击逻辑以支持对建筑造成伤害
- 升级WorldHealthBar以显示建筑生产进度和更好的缩放补偿
- 修复MainCharacterDebugMovement移除键盘移动输入冲突
- 更新包管理器中的codely.bridge版本从1.0.80到1.0.81
```
This commit is contained in:
huadexuan committed 2026-09-13 23:35:59 +08:00
1 parent fb56cd7ede
commit 769aea30f3
55 files changed
+9328 -90

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
using UnityEngine;
using UnityEngine.UI;
public class Barracks : MonoBehaviour
{
[Header("角色数据")]
public CharacterData characterData;
[Header("阵营")]
public string unitTag = "Friend";
[Header("生产设置")]
public Transform spawnPoint; // 单位生成位置
[Header("UI显示")]
public Image progressBar; // 生产进度条
public Image characterIcon; // 角色头像
private float spawnTimer = 0f;
private float spawnInterval = 5f;
private bool isProducing = true;
private BuildingHealth buildingHealth;
private void Start()
{
buildingHealth = GetComponent<BuildingHealth>();
if (characterData != null)
{
spawnInterval = Mathf.Max(0.1f, characterData.productionInterval);
// 设置角色头像
if (characterIcon != null && characterData.cardIcon != null)
{
characterIcon.sprite = characterData.cardIcon;
}
Debug.Log($"兵营初始化:{characterData.characterName},生产间隔{spawnInterval}秒");
}
else
{
Debug.LogError("兵营缺少角色数据!");
}
// 如果没有spawnPoint,使用兵营自己的位置
if (spawnPoint == null)
{
spawnPoint = transform;
}
}
private void Update()
{
if (!isProducing || characterData == null) return;
spawnTimer += Time.deltaTime;
// 更新进度条
if (buildingHealth != null)
{
buildingHealth.SetProductionProgress(spawnTimer / spawnInterval);
}
else if (progressBar != null)
{
progressBar.fillAmount = spawnTimer / spawnInterval;
}
// 时间到,生产单位
if (spawnTimer >= spawnInterval)
{
SpawnUnit();
spawnTimer = 0f;
}
}
private void SpawnUnit()
{
if (characterData == null || characterData.unitPrefab == null)
{
Debug.LogError("无法生产单位:缺少unitPrefab");
return;
}
// 在spawnPoint位置生成单位
Vector3 spawnPosition = spawnPoint.position;
GameObject unit = Instantiate(characterData.unitPrefab, spawnPosition, Quaternion.identity);
unit.name = characterData.characterName;
unit.tag = unitTag;
// 如果单位有UnitBase组件,使用1级属性初始化
var unitBase = unit.GetComponent<UnitBase>();
if (unitBase != null)
{
float health = characterData.GetMaxHealth(1);
float attack = characterData.GetAttackPower(1);
float moveSpeed = characterData.GetMoveSpeed(1);
unitBase.InitializeFromPlayerData(health, attack, moveSpeed);
}
Debug.Log($"兵营生产了单位:{characterData.characterName}");
}
public void StopProduction()
{
isProducing = false;
}
public void StartProduction()
{
isProducing = true;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DXIdsyioBigBGeEulZR6ss5x+lvhWguS629tEmVGciD4+vPwhCsbs3I=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+42
View File
@@ -0,0 +1,42 @@
using UnityEngine;
[RequireComponent(typeof(WorldHealthBar))]
public class BuildingHealth : MonoBehaviour
{
[Header("建筑生命")]
[Min(1f)] public float maxHealth = 500f;
private WorldHealthBar healthBar;
public float ProductionProgress => healthBar != null
? Mathf.Clamp01(healthBar.currentEnergy / healthBar.maxEnergy)
: 0f;
private void Awake()
{
healthBar = GetComponent<WorldHealthBar>();
healthBar.maxHealth = maxHealth;
healthBar.currentHealth = maxHealth;
healthBar.maxEnergy = 1f;
healthBar.currentEnergy = 0f;
}
public void SetProductionProgress(float normalizedProgress)
{
if (healthBar != null)
{
healthBar.SetEnergy(Mathf.Clamp01(normalizedProgress) * healthBar.maxEnergy);
}
}
public bool ReceiveDamage(float damage, GameObject attacker = null)
{
if (damage <= 0f || healthBar == null)
{
return false;
}
healthBar.TakeDamage(damage);
return true;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: D3wZsXikW33LIZvtpWVBFbW5HacE/bCypHDY2MmwlMdqllT4Bj7rSz8=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+242
View File
@@ -0,0 +1,242 @@
using System.Collections.Generic;
using UnityEngine;
public class BuildingPlacementManager : MonoBehaviour
{
public static BuildingPlacementManager Instance { get; private set; }
[Header("建筑预制体")]
public GameObject barracksBasePrefab;
public GameObject goldMinePrefab;
[Header("虚拟网格")]
[Min(0.1f)] public float gridSize = 1f;
[Min(0f)] public float placementHeight;
public Vector2 xLimits = new Vector2(-7f, 7f);
public Vector2 zLimits = new Vector2(-10f, 20f);
[Header("放置检测")]
public LayerMask groundLayer;
[Min(0.1f)] public float buildingRadius = 1.25f;
public Material previewMaterialValid;
public Material previewMaterialInvalid;
private readonly List<GameObject> placedBuildings = new List<GameObject>();
private CharacterData currentCharacterData;
private GameObject currentPrefab;
private GameObject previewObject;
private Camera mainCamera;
private bool isPlacing;
private bool previewPositionValid;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
private void Start()
{
mainCamera = Camera.main;
RegisterExistingBuilding(GameObject.Find("主要基地"));
}
private void Update()
{
if (!isPlacing)
{
return;
}
UpdatePreview();
if (Input.GetMouseButtonDown(0))
{
TryPlaceBuilding();
}
else if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(KeyCode.Escape))
{
CancelPlacement();
}
}
public void StartPlacingBuilding(CharacterData data)
{
if (data == null)
{
return;
}
StartPlacingBarracks(data);
}
public void StartPlacingBarracks(CharacterData data)
{
if (data == null || barracksBasePrefab == null)
{
Debug.LogError("兵营预制体或角色数据为空");
return;
}
BeginPlacement(barracksBasePrefab, data);
}
public void StartPlacingGoldMine()
{
if (goldMinePrefab == null)
{
Debug.LogError("金矿预制体为空");
return;
}
BeginPlacement(goldMinePrefab, null);
}
private void BeginPlacement(GameObject prefab, CharacterData data)
{
CancelPlacement();
currentPrefab = prefab;
currentCharacterData = data;
previewObject = Instantiate(prefab);
previewObject.name = "建筑预览";
SetCollidersEnabled(previewObject, false);
isPlacing = true;
mainCamera = Camera.main;
}
private void UpdatePreview()
{
if (previewObject == null || mainCamera == null)
{
return;
}
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
int mask = groundLayer.value == 0 ? Physics.DefaultRaycastLayers : groundLayer.value;
if (!Physics.Raycast(ray, out RaycastHit hit, 1000f, mask))
{
previewPositionValid = false;
return;
}
Vector3 position = hit.point;
position.x = Mathf.Round(position.x / gridSize) * gridSize;
position.y = placementHeight;
position.z = Mathf.Round(position.z / gridSize) * gridSize;
previewObject.transform.position = position;
previewPositionValid = IsValidPosition(position);
UpdatePreviewMaterial(previewPositionValid);
}
private bool IsValidPosition(Vector3 position)
{
if (position.x < xLimits.x || position.x > xLimits.y ||
position.z < zLimits.x || position.z > zLimits.y)
{
return false;
}
foreach (GameObject building in placedBuildings)
{
if (building != null && Vector3.Distance(position, building.transform.position) < buildingRadius * 2f)
{
return false;
}
}
return true;
}
private void TryPlaceBuilding()
{
if (!previewPositionValid || previewObject == null || currentPrefab == null)
{
return;
}
int cost = currentCharacterData != null ? currentCharacterData.buildingCost : 100;
if (GoldSystem.Instance == null || !GoldSystem.Instance.TrySpend(cost))
{
Debug.Log($"金币不足,需要{cost}");
return;
}
Vector3 position = previewObject.transform.position;
GameObject building = Instantiate(currentPrefab, position, Quaternion.identity);
building.name = currentCharacterData != null
? "兵营_" + currentCharacterData.characterName
: "金矿";
building.tag = "Friend";
if (building.GetComponent<BuildingHealth>() == null)
{
building.AddComponent<BuildingHealth>();
}
if (currentCharacterData != null)
{
Barracks barracks = building.GetComponent<Barracks>();
if (barracks == null)
{
barracks = building.AddComponent<Barracks>();
}
barracks.characterData = currentCharacterData;
}
else if (building.GetComponent<GoldMine>() == null)
{
building.AddComponent<GoldMine>();
}
placedBuildings.Add(building);
CancelPlacement();
}
private void UpdatePreviewMaterial(bool valid)
{
Material material = valid ? previewMaterialValid : previewMaterialInvalid;
if (material == null || previewObject == null)
{
return;
}
foreach (Renderer renderer in previewObject.GetComponentsInChildren<Renderer>())
{
renderer.sharedMaterial = material;
}
}
private void SetCollidersEnabled(GameObject target, bool enabled)
{
foreach (Collider collider in target.GetComponentsInChildren<Collider>())
{
collider.enabled = enabled;
}
}
public void RegisterExistingBuilding(GameObject building)
{
if (building != null && !placedBuildings.Contains(building))
{
placedBuildings.Add(building);
}
}
public void CancelPlacement()
{
if (previewObject != null)
{
Destroy(previewObject);
}
previewObject = null;
currentPrefab = null;
currentCharacterData = null;
isPlacing = false;
previewPositionValid = false;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: Cy5NsnukUC5AkjhzooAm9meZl92B+OUU/7BkXaSTEi6zwYIGOiVmF7o=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+128 -20
View File
@@ -2,36 +2,144 @@ using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Transform target; // 角色的Transform组件
public Vector3 offset; // 摄像机相对于角色的偏移量
public float smoothSpeed = 0.125f; // 摄像机移动的平滑速度
[Header("拖动控制")]
public float keyboardDragSpeed = 10f; // WASD拖动速度(模拟手指拖动屏幕)
public float mouseDragSpeed = 0.5f; // 鼠标拖动速度
public float touchDragSpeed = 0.5f; // 触摸拖动速度
[Header("缩放控制(正交相机用orthographicSize,透视相机用fieldOfView)")]
public float zoomSpeed = 5f; // 缩放速度
public float minOrthographicSize = 5f; // 正交相机最小size
public float maxOrthographicSize = 20f; // 正交相机最大size
public float minFieldOfView = 30f; // 透视相机最小FOV
public float maxFieldOfView = 90f; // 透视相机最大FOV
[Header("边界限制")]
public bool enableBounds = true; // 是否启用边界限制
public float minX = -6f; // X轴最小边界
public float maxX = 6f; // X轴最大边界
public float minZ = -30f; // Z轴最小边界(摄像机位置,不是主角位置)
public float maxZ = 91f; // Z轴最大边界(摄像机位置,不是主角位置)
public bool enableBounds = true;
public float minX = -6f;
public float maxX = 6f;
public float minZ = -30f;
public float maxZ = 91f;
private void LateUpdate()
private Vector3 lastMousePosition;
private bool isDragging = false;
private Camera cam;
private void Start()
{
if (target == null)
cam = GetComponent<Camera>();
if (cam == null)
{
return;
Debug.LogError("CameraFollow需要挂载在Camera组件上");
}
}
private void Update()
{
HandleKeyboardDrag();
HandleMouseDrag();
HandleKeyboardZoom();
}
private void HandleKeyboardDrag()
{
float horizontal = 0f;
float vertical = 0f;
if (Input.GetKey(KeyCode.W)) vertical = 1f;
if (Input.GetKey(KeyCode.S)) vertical = -1f;
if (Input.GetKey(KeyCode.A)) horizontal = -1f;
if (Input.GetKey(KeyCode.D)) horizontal = 1f;
if (horizontal != 0f || vertical != 0f)
{
Vector3 movement = new Vector3(horizontal, 0, vertical) * keyboardDragSpeed * Time.deltaTime;
MoveCameraWithBounds(movement);
}
}
private void HandleMouseDrag()
{
// 鼠标拖动
if (Input.GetMouseButtonDown(0))
{
lastMousePosition = Input.mousePosition;
isDragging = true;
}
else if (Input.GetMouseButton(0) && isDragging)
{
Vector3 delta = Input.mousePosition - lastMousePosition;
Vector3 movement = new Vector3(-delta.x, 0, -delta.y) * mouseDragSpeed * Time.deltaTime;
MoveCameraWithBounds(movement);
lastMousePosition = Input.mousePosition;
}
else if (Input.GetMouseButtonUp(0))
{
isDragging = false;
}
Vector3 desiredPosition = target.position + offset;
// 触摸拖动(移动设备)
if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved)
{
Vector2 touchDelta = Input.GetTouch(0).deltaPosition;
Vector3 movement = new Vector3(-touchDelta.x, 0, -touchDelta.y) * touchDragSpeed * Time.deltaTime;
MoveCameraWithBounds(movement);
}
}
private void HandleKeyboardZoom()
{
if (cam == null) return;
float zoomDelta = 0f;
// 上下键控制缩放
if (Input.GetKey(KeyCode.UpArrow)) zoomDelta = -1f; // 缩小(拉近)
if (Input.GetKey(KeyCode.DownArrow)) zoomDelta = 1f; // 放大(拉远)
// 触摸双指缩放
if (Input.touchCount == 2)
{
Touch touch0 = Input.GetTouch(0);
Touch touch1 = Input.GetTouch(1);
Vector2 touch0PrevPos = touch0.position - touch0.deltaPosition;
Vector2 touch1PrevPos = touch1.position - touch1.deltaPosition;
float prevMagnitude = (touch0PrevPos - touch1PrevPos).magnitude;
float currentMagnitude = (touch0.position - touch1.position).magnitude;
float difference = currentMagnitude - prevMagnitude;
zoomDelta = -difference * 0.01f; // 负号:手指分开=拉近,手指合拢=拉远
}
if (zoomDelta != 0f)
{
if (cam.orthographic)
{
// 正交相机:调整 orthographicSize
float newSize = cam.orthographicSize + zoomDelta * zoomSpeed * Time.deltaTime;
cam.orthographicSize = Mathf.Clamp(newSize, minOrthographicSize, maxOrthographicSize);
}
else
{
// 透视相机:调整 fieldOfView
float newFov = cam.fieldOfView + zoomDelta * zoomSpeed * Time.deltaTime;
cam.fieldOfView = Mathf.Clamp(newFov, minFieldOfView, maxFieldOfView);
}
}
}
private void MoveCameraWithBounds(Vector3 movement)
{
Vector3 newPosition = transform.position + movement;
// 边界限制:摄像机停在边界处,主角可以继续移动
if (enableBounds)
{
desiredPosition.x = Mathf.Clamp(desiredPosition.x, minX, maxX);
desiredPosition.z = Mathf.Clamp(desiredPosition.z, minZ, maxZ);
newPosition.x = Mathf.Clamp(newPosition.x, minX, maxX);
newPosition.z = Mathf.Clamp(newPosition.z, minZ, maxZ);
}
transform.position = Vector3.Lerp(
transform.position,
desiredPosition,
smoothSpeed);
transform.position = newPosition;
}
}
+7
View File
@@ -22,6 +22,13 @@ public class CharacterData : ScriptableObject
[Min(0f)]
public float cooldownTime = 1f;
[Header("兵营生产")]
[Min(0.1f)]
public float productionInterval = 5f;
[Min(0)]
public int buildingCost = 100;
[Header("基础属性(1级)")]
[Min(0f)]
public float baseMaxHealth = 100f;
+36
View File
@@ -0,0 +1,36 @@
using UnityEngine;
public class GoldMine : MonoBehaviour
{
[Header("产出配置")]
[Min(0)] public int goldPerTick = 5;
[Min(0.1f)] public float productionInterval = 3f;
private float timer;
private BuildingHealth buildingHealth;
private void Awake()
{
buildingHealth = GetComponent<BuildingHealth>();
}
private void Update()
{
if (GoldSystem.Instance == null)
{
return;
}
timer += Time.deltaTime;
if (buildingHealth != null)
{
buildingHealth.SetProductionProgress(timer / productionInterval);
}
while (timer >= productionInterval)
{
timer -= productionInterval;
GoldSystem.Instance.AddGold(goldPerTick);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: XisXvCP7W3ud7In4Aw0vAdASTrL7i67o+okMla+dirFaiiA027bCSYk=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+60
View File
@@ -0,0 +1,60 @@
using UnityEngine;
using UnityEngine.UI;
public class GoldSystem : MonoBehaviour
{
public static GoldSystem Instance { get; private set; }
[Header("金币配置")]
[Min(0)] public int startingGold = 200;
[Min(0)] public int maxGold = 9999;
[Header("UI(可选)")]
public Text goldText;
public int CurrentGold { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
CurrentGold = startingGold;
UpdateUI();
}
public bool TrySpend(int amount)
{
if (amount < 0 || CurrentGold < amount)
{
return false;
}
CurrentGold -= amount;
UpdateUI();
return true;
}
public void AddGold(int amount)
{
if (amount <= 0)
{
return;
}
CurrentGold = Mathf.Min(CurrentGold + amount, maxGold);
UpdateUI();
}
private void UpdateUI()
{
if (goldText != null)
{
goldText.text = CurrentGold.ToString();
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: WnkatS//B38zOhf5GkU6P+3LfnaKjNkhHT8apytdd5KyzfqefjIzQJE=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+1 -15
View File
@@ -19,9 +19,7 @@ public class MainCharacterDebugMovement : MonoBehaviour
private Vector3 clickTarget;
private bool hasClickTarget;
public bool IsMoving =>
hasClickTarget ||
new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).sqrMagnitude > 0.001f;
public bool IsMoving => hasClickTarget;
private void Start()
{
@@ -34,18 +32,6 @@ public class MainCharacterDebugMovement : MonoBehaviour
{
HandleClickTarget();
Vector3 keyboardInput = new Vector3(
Input.GetAxisRaw("Horizontal"),
0f,
Input.GetAxisRaw("Vertical"));
if (keyboardInput.sqrMagnitude > 0.001f)
{
hasClickTarget = false;
Move(keyboardInput.normalized);
return;
}
if (!hasClickTarget)
{
SetWalking(false);
+10 -2
View File
@@ -115,13 +115,21 @@ public class Projectile : MonoBehaviour
}
UnitBase targetUnit = hitObject.GetComponentInParent<UnitBase>();
if (targetUnit == null)
BuildingHealth targetBuilding = hitObject.GetComponentInParent<BuildingHealth>();
if (targetUnit == null && targetBuilding == null)
{
return;
}
hasHit = true;
targetUnit.ReceiveDamage(damage, owner);
if (targetUnit != null)
{
targetUnit.ReceiveDamage(damage, owner);
}
else
{
targetBuilding.ReceiveDamage(damage, owner);
}
if (registerEnergyOnHit && owner != null)
{
UnitBase unit = owner.GetComponent<UnitBase>();
+14 -7
View File
@@ -67,19 +67,26 @@ public class SummonCardButton : MonoBehaviour
public void OnClick()
{
if (isOnCooldown || characterData == null)
if (characterData == null)
{
return;
}
if (combat == null)
// 检查建筑费用是否足够
if (GoldSystem.Instance == null || GoldSystem.Instance.CurrentGold < characterData.buildingCost)
{
combat = FindObjectOfType<MainCharacterCombat>();
Debug.Log($"金币不足,需要{characterData.buildingCost}");
return;
}
if (combat != null && combat.RequestSummon(summonSlot))
// 触发建筑放置模式
if (BuildingPlacementManager.Instance != null)
{
StartCooldown();
BuildingPlacementManager.Instance.StartPlacingBuilding(characterData);
}
else
{
Debug.LogError("找不到 BuildingPlacementManager");
}
}
@@ -138,12 +145,12 @@ public class SummonCardButton : MonoBehaviour
private bool CanAfford()
{
if (ManaSystem.Instance == null || characterData == null)
if (GoldSystem.Instance == null || characterData == null)
{
return false;
}
return ManaSystem.Instance.CurrentManaInt >= characterData.manaCost;
return GoldSystem.Instance.CurrentGold >= characterData.buildingCost;
}
private float GetCooldownTime()
+29 -9
View File
@@ -561,12 +561,17 @@ public class UnitBase : MonoBehaviour
private void ApplyDirectDamage(GameObject opponent)
{
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
if (targetUnit == null || !targetUnit.ReceiveDamage(attackPower, gameObject))
if (targetUnit != null && targetUnit.ReceiveDamage(attackPower, gameObject))
{
RegisterAttackHit();
return;
}
RegisterAttackHit();
BuildingHealth targetBuilding = opponent.GetComponentInParent<BuildingHealth>();
if (targetBuilding != null && targetBuilding.ReceiveDamage(attackPower, gameObject))
{
RegisterAttackHit();
}
}
public void ApplySkillHit(float damageMultiplier, bool registerEnergy)
@@ -576,8 +581,17 @@ public class UnitBase : MonoBehaviour
return;
}
float damage = attackPower * Mathf.Max(0f, damageMultiplier);
UnitBase targetUnit = target.GetComponentInParent<UnitBase>();
if (targetUnit == null || !targetUnit.ReceiveDamage(attackPower * Mathf.Max(0f, damageMultiplier), gameObject))
bool hit = targetUnit != null && targetUnit.ReceiveDamage(damage, gameObject);
if (!hit)
{
BuildingHealth targetBuilding = target.GetComponentInParent<BuildingHealth>();
hit = targetBuilding != null && targetBuilding.ReceiveDamage(damage, gameObject);
}
if (!hit)
{
return;
}
@@ -621,18 +635,24 @@ public class UnitBase : MonoBehaviour
}
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
if (targetUnit == null)
{
continue;
}
BuildingHealth targetBuilding = opponent.GetComponentInParent<BuildingHealth>();
WorldHealthBar healthBar = targetUnit != null
? targetUnit.GetComponent<WorldHealthBar>()
: targetBuilding != null ? targetBuilding.GetComponent<WorldHealthBar>() : null;
WorldHealthBar healthBar = targetUnit.GetComponent<WorldHealthBar>();
if (healthBar == null || !hitHealthBars.Add(healthBar))
{
continue;
}
targetUnit.ReceiveDamage(damage, gameObject);
if (targetUnit != null)
{
targetUnit.ReceiveDamage(damage, gameObject);
}
else
{
targetBuilding.ReceiveDamage(damage, gameObject);
}
}
if (registerEnergy)
+28 -4
View File
@@ -94,6 +94,7 @@ public class WorldHealthBar : MonoBehaviour
{
if (barRect == null) return;
barRect.localPosition = GetBarRootPosition();
ApplyScaleCompensation();
// 优先使用场景中名称完全为 Main Camera 的摄像机。
Camera camera = FindMainCamera();
@@ -167,13 +168,19 @@ public class WorldHealthBar : MonoBehaviour
// 清空技能能量并刷新能量条。
public void ResetEnergy()
{
SetEnergy(0f);
}
// 设置下方条的当前填充值;单位用于技能能量,建筑用于生产进度。
public void SetEnergy(float value)
{
if (isDead)
{
return;
}
currentEnergy = 0f;
currentEnergy = Mathf.Clamp(value, 0f, maxEnergy);
UpdateEnergyFill();
}
@@ -327,7 +334,10 @@ public class WorldHealthBar : MonoBehaviour
worldWidth = barWidth;
// 水平方向固定对齐单位根节点,只使用模型顶部计算血条高度。
Vector3 top = transform.InverseTransformPoint(new Vector3(transform.position.x, bounds.max.y, transform.position.z));
localBarPosition = new Vector3(0f, top.y + heightOffset, 0f);
float localHeightOffset = transform.lossyScale.y != 0f
? heightOffset / transform.lossyScale.y
: heightOffset;
localBarPosition = new Vector3(0f, top.y + localHeightOffset, 0f);
}
// 创建世界空间 Canvas、边框和绿色血量填充。
private void CreateBar()
@@ -343,6 +353,7 @@ public class WorldHealthBar : MonoBehaviour
barRect.sizeDelta = new Vector2(PixelWidth, TotalPixelHeight);
barRect.localScale = Vector3.one * (worldWidth / PixelWidth);
barRect.localPosition = GetBarRootPosition();
ApplyScaleCompensation();
RectTransform healthRow = CreateRow(barObject.transform, "HealthBar", PixelWidth, PixelHeight, 0);
Image background = CreateImage("Border", healthRow, RoundedSprite(), borderColor);
@@ -383,12 +394,25 @@ public class WorldHealthBar : MonoBehaviour
energyFillImage = energyInner;
}
private void ApplyScaleCompensation()
{
Vector3 parentScale = transform.lossyScale;
float targetScale = worldWidth / PixelWidth;
barRect.localScale = new Vector3(
parentScale.x != 0f ? targetScale / parentScale.x : targetScale,
parentScale.y != 0f ? targetScale / parentScale.y : targetScale,
parentScale.z != 0f ? targetScale / parentScale.z : targetScale);
}
// 返回整体 Canvas 的位置,使血条中心保持原来的高度。
private Vector3 GetBarRootPosition()
{
float pixelToWorld = worldWidth / PixelWidth;
float energyOffset = (TotalPixelHeight - PixelHeight) * 0.5f * pixelToWorld;
return localBarPosition - Vector3.up * energyOffset;
float worldEnergyOffset = (TotalPixelHeight - PixelHeight) * 0.5f * pixelToWorld;
float localEnergyOffset = transform.lossyScale.y != 0f
? worldEnergyOffset / transform.lossyScale.y
: worldEnergyOffset;
return localBarPosition - Vector3.up * localEnergyOffset;
}
// 创建一行固定像素高度的世界空间 UI。