using System; using Cielonos.MainGame.Buffs.Character; using Cielonos.MainGame.Characters; using Lean.Pool; using Sirenix.OdinInspector; using SLSUtilities.General; using SLSUtilities.UI; using UnityEngine; namespace Cielonos.MainGame.UI { public class EnemyInfoUnitBase : UIElementBase { [HideInInspector] public CharacterBase enemy; [TitleGroup("Bars")] public RectTransform barContainer; [TitleGroup("Bars")] public AttributeBarBase healthBar; [TitleGroup("Bars")] public AttributeBarBase energyBar; [TitleGroup("Bars")] public AttributeBarBase stanceBar; [TitleGroup("Buffs")] public RectTransform buffIconContainer; [TitleGroup("Distance & Scale Settings")] public float minShowDistance = 5f; // 距离小于此值,血条消失(过近)4 [TitleGroup("Distance & Scale Settings")] public float maxShowDistance = 30f; // 距离大于此值,血条消失(过远) [TitleGroup("Distance & Scale Settings")] public float maxScale = 1f; // 距离最近 (minShowDistance) 时的最大缩放比例 [TitleGroup("Distance & Scale Settings")] public float minScale = 0.4f; // 距离最远 (maxShowDistance) 时的最小缩放比例 private float distanceFade = 1f; [Header("Occlusion Settings")] public LayerMask obstacleLayer; // 遮挡物层级(墙壁、地面等) public float occlusionTimer = 0f; // 被遮挡的计时器 public float occlusionDelay = 0.5f; // 被遮挡后延迟多久开始消失 private float occlusionFade = 1f; [Header("Alpha & Edge Fade Settings")] public float fadeSpeed = 8f; // 透明度渐变速度 (越大消失越快) [Tooltip("从中心到边缘,开始衰减的百分比 (0~1)")] public float fadeStartPct = 0.80f; // 80% 开始消失 [Tooltip("从中心到边缘,完全透明的百分比 (0~1)")] public float fadeEndPct = 0.95f; // 95% 完全消失 private float screenFade = 1f; [TitleGroup("Colors")] public Color normalStanceColor = new Color(1f, 0.78f, 0.58f); [TitleGroup("Colors")] public Color paralyzedStanceColor = new Color(1f, 0.3f, 0.3f); public void Initialize(CharacterBase character) { enemy = character; distanceFade = 1f; occlusionFade = 1f; screenFade = 1f; UpdateHealth(true); UpdateEnergy(true); UpdateStance(true); } private void Update() { UpdatePosition(); UpdateHealth(false); UpdateEnergy(false); UpdateStance(false); canvasGroup.alpha = Mathf.Min(distanceFade, occlusionFade, screenFade); } public void UpdatePosition() { if(enemy == null || enemy.statusSm.isDead) LeanPool.Despawn(gameObject); if(enemy?.bodyPartsSc?.infoUIPoint == null) return; Vector3 enemyPos = enemy.CenterPosition; Vector3 infoUIPos = enemy.bodyPartsSc.infoUIPoint.position; Camera playerCamera = MainGameManager.Player.viewSc.playerCamera; float cameraDistance = Vector3.Distance(playerCamera.transform.position, infoUIPos); float midDistance = (minShowDistance + maxShowDistance) / 2f; // 计算距离衰减,在midDistance处达到最大值,minShowDistance和maxShowDistance处为0 distanceFade = Mathf.InverseLerp(cameraDistance < midDistance ? minShowDistance : maxShowDistance, midDistance, cameraDistance); distanceFade = Mathf.Min(distanceFade * 4f, 1f); // 在距离范围内,快速达到完全显示 Vector3 viewportPos = playerCamera.WorldToViewportPoint(infoUIPos); bool isBehindCamera = viewportPos.z < 0; if (isBehindCamera) { screenFade = 0f; } else { float normalizedDistX = Mathf.Abs(viewportPos.x - 0.5f) * 2f; float normalizedDistY = Mathf.Abs(viewportPos.y - 0.5f) * 2f; float maxNormalizedDist = Mathf.Max(normalizedDistX, normalizedDistY); screenFade = Mathf.InverseLerp(fadeEndPct, fadeStartPct, maxNormalizedDist); } // Occlusion check if (Physics.Linecast(playerCamera.transform.position, infoUIPos, out RaycastHit hitInfo)) { if (hitInfo.collider.GetComponentInParent() != enemy) { occlusionTimer += Time.deltaTime; } else { occlusionTimer = 0f; } } else { // Nothing between camera and enemy — clear line of sight occlusionTimer = 0f; } if (occlusionTimer > occlusionDelay) { float progress = (occlusionTimer - occlusionDelay) * fadeSpeed; occlusionFade = Mathf.Lerp(1f, 0f, progress); } else { occlusionFade = 1f; } // Always update position and scale, even when fading out, // so the UI is in the correct place when it becomes visible again. float t = Mathf.InverseLerp(minShowDistance, maxShowDistance, cameraDistance); float currentScale = Mathf.Lerp(maxScale, minScale, t); RectTransform areaRect = PlayerCanvas.EnemyInfoUIArea.rectTransform; Vector2 enemyScreenPos = SpaceConverter.WorldPointToUILocalPoint(areaRect, infoUIPos, playerCamera, null); rectTransform.localScale = Vector3.one * currentScale; rectTransform.anchoredPosition = enemyScreenPos; } public void UpdateHealth(bool isInstant = false) { float currentHealth = enemy.attributeSm[CharacterAttribute.Health]; float maximumHealth = enemy.attributeSm[CharacterAttribute.MaximumHealth]; //float ratio = currentHealth / maximumHealth; healthBar.UpdateFillImage(currentHealth, maximumHealth); //if(!isInstant) healthBar.Blink(Color.white); } public void UpdateEnergy(bool isInstant = false) { float currentEnergy = enemy.attributeSm[CharacterAttribute.Energy]; float maximumEnergy = enemy.attributeSm[CharacterAttribute.MaximumEnergy]; energyBar.UpdateFillImage(currentEnergy, maximumEnergy); //if(!isInstant) energyBar.Blink(Color.white); } public void UpdateStance(bool isInstant = false) { if (stanceBar == null) return; if (!enemy.attributeSm.Has(CharacterAttribute.MaximumStance) || enemy.attributeSm[CharacterAttribute.MaximumStance] <= 0) { if (stanceBar.gameObject.activeSelf) stanceBar.gameObject.SetActive(false); return; } if (!stanceBar.gameObject.activeSelf) stanceBar.gameObject.SetActive(true); if (enemy.buffSm.TryGetBuff(out ElectronicParalysis buff) && buff.independentStackSubmodule.totalStackAmount > 0) { float remainingTime = buff.independentStackSubmodule.LongestUnit.remainingTime; float maximumTime = buff.independentStackSubmodule.LongestUnit.duration; stanceBar.UpdateFillColor(paralyzedStanceColor, true); float elapsed = maximumTime - remainingTime; stanceBar.UpdateFillImage(elapsed, maximumTime); } else { float currentStance = enemy.attributeSm[CharacterAttribute.Stance]; float maximumStance = enemy.attributeSm[CharacterAttribute.MaximumStance]; stanceBar.UpdateFillColor(normalStanceColor, true); stanceBar.UpdateFillImage(currentStance, maximumStance); } } } }