This commit is contained in:
huadexuan committed 2026-09-01 18:17:34 +08:00
1 parent 2241642a97
commit 4216d27310
132 files changed
+3575 -137

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
using UnityEngine;
public class HumanSoldierSkill : UnitSkill
{
[Min(0f)]
public float bonusAttackMultiplier = 0.5f;
[Header("技能动画")]
[Min(0f)]
public float skillAnimationDuration = 0f;
private bool isSkillReady;
public override void ReleaseSkill()
{
isSkillReady = true;
unit.PlaySkillAnimation(OnSkillFinished, skillAnimationDuration);
}
// 添加到技能攻击动画的第一段命中帧。
public void OnSkillHit1()
{
if (!isSkillReady)
{
return;
}
unit.ApplySkillHit(1f, true);
}
// 添加到技能攻击动画的第二段命中帧。
public void OnSkillHit2()
{
if (!isSkillReady)
{
return;
}
unit.ApplySkillHit(bonusAttackMultiplier, false);
isSkillReady = false;
}
private void OnSkillFinished()
{
isSkillReady = false;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: W34f4C/7USoWR/R5rwP9HkjbiTWMMxMnlwTiCpclEAjSKp6tZ2xZ9HQ=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+138
View File
@@ -0,0 +1,138 @@
using UnityEngine;
public class ShieldBarrier : MonoBehaviour
{
[Header("盾牌状态")]
[Min(0f)]
public float maxDurability = 220f;
[Min(0f)]
public float currentDurability;
[Min(0f)]
public float duration = 6f;
[Range(-1f, 1f)]
public float frontDotThreshold = 0.15f;
private UnitBase owner;
private Collider barrierCollider;
private float remainingTime;
public UnitBase Owner => owner;
public float CurrentDurability => currentDurability;
public bool IsActive =>
isActiveAndEnabled &&
barrierCollider != null &&
barrierCollider.enabled &&
currentDurability > 0f &&
remainingTime > 0f;
public void Configure(UnitBase shieldOwner, Collider shieldCollider)
{
owner = shieldOwner;
barrierCollider = shieldCollider;
}
public void Refresh(float durability, float activeDuration, Vector3 localPosition, Vector3 localSize)
{
if (barrierCollider == null)
{
barrierCollider = GetComponent<Collider>();
}
if (owner == null)
{
owner = GetComponentInParent<UnitBase>();
}
transform.localPosition = localPosition;
transform.localRotation = Quaternion.identity;
BoxCollider boxCollider = barrierCollider as BoxCollider;
if (boxCollider != null)
{
boxCollider.center = Vector3.zero;
boxCollider.size = localSize;
boxCollider.isTrigger = true;
}
maxDurability = Mathf.Max(0.01f, durability);
currentDurability = maxDurability;
duration = Mathf.Max(0.01f, activeDuration);
remainingTime = duration;
gameObject.SetActive(true);
if (barrierCollider != null)
{
barrierCollider.enabled = true;
}
}
public bool TryBlock(GameObject attacker, float damage)
{
if (!IsActive || damage <= 0f)
{
return false;
}
if (attacker != null && !IsIncomingFromFront(attacker))
{
return false;
}
currentDurability -= damage;
if (currentDurability <= 0f)
{
DisableBarrier();
}
return true;
}
public void DisableBarrier()
{
currentDurability = 0f;
remainingTime = 0f;
if (barrierCollider != null)
{
barrierCollider.enabled = false;
}
gameObject.SetActive(false);
}
private void Update()
{
if (!IsActive)
{
return;
}
remainingTime -= Time.deltaTime;
if (remainingTime <= 0f)
{
DisableBarrier();
}
}
private bool IsIncomingFromFront(GameObject attacker)
{
if (owner == null)
{
return true;
}
Vector3 direction = attacker.transform.position - owner.transform.position;
direction.y = 0f;
if (direction.sqrMagnitude <= 0.0001f)
{
return true;
}
return Vector3.Dot(owner.transform.forward, direction.normalized) >= frontDotThreshold;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: By8es3n/Vn8vDFRRNcUcF3DWUozAHLqfoGKYkWDk0WblebrKYNoIno8=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+44
View File
@@ -0,0 +1,44 @@
using UnityEngine;
public class ShieldGuardSkill : UnitSkill
{
[Header("盾牌参数")]
[Min(1f)]
public float shieldDurability = 220f;
[Min(0.1f)]
public float shieldDuration = 6f;
public Vector3 shieldLocalPosition = new Vector3(0f, 1f, 0.7f);
public Vector3 shieldSize = new Vector3(1.5f, 1.8f, 0.12f);
private ShieldBarrier shield;
public ShieldBarrier ActiveShield => shield;
public override void ReleaseSkill()
{
EnsureShield();
shield.Refresh(shieldDurability, shieldDuration, shieldLocalPosition, shieldSize);
}
private void EnsureShield()
{
if (shield != null)
{
return;
}
GameObject shieldObject = new GameObject("ShieldBarrier");
shieldObject.transform.SetParent(unit.transform, false);
BoxCollider boxCollider = shieldObject.AddComponent<BoxCollider>();
boxCollider.isTrigger = true;
boxCollider.size = shieldSize;
shield = shieldObject.AddComponent<ShieldBarrier>();
shield.Configure(unit, boxCollider);
shieldObject.SetActive(false);
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: XCkWsHz7Wn/4PQyuRdHqOq4UICVTHToo6q2RmJrNHMtmqcPpJ27eVZY=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+102 -2
View File
@@ -290,6 +290,53 @@ public class UnitBase : MonoBehaviour
return 0f;
}
// 查找当前 Animator 中使用的技能动画时长。
private float GetSkillAnimationDuration()
{
if (animator == null || animator.runtimeAnimatorController == null)
{
return 0f;
}
var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController;
if (overrideController != null)
{
var overrides = new List<KeyValuePair<AnimationClip, AnimationClip>>();
overrideController.GetOverrides(overrides);
foreach (var pair in overrides)
{
if (IsSkillAnimation(pair.Key))
{
return pair.Value != null ? pair.Value.length : pair.Key.length;
}
}
}
foreach (var clip in animator.runtimeAnimatorController.animationClips)
{
if (IsSkillAnimation(clip))
{
return clip.length;
}
}
return 0f;
}
// 根据动画名称判断是否为技能动画。
private bool IsSkillAnimation(AnimationClip clip)
{
if (clip == null)
{
return false;
}
string clipName = clip.name.ToLowerInvariant();
return clipName.Contains("skill")
|| clipName.Contains("技能");
}
//根据动画名称判断是否为攻击动画
private bool IsAttackAnimation(AnimationClip clip)
{
@@ -322,6 +369,8 @@ public class UnitBase : MonoBehaviour
{
target = friend;
FaceTarget(friend);
//根据目标攻击时长加速或减速完整攻击动画
SetAttackAnimationSpeed();
@@ -331,6 +380,24 @@ public class UnitBase : MonoBehaviour
attackStartTime = Time.time;
}
private void FaceTarget(GameObject opponent)
{
if (opponent == null)
{
return;
}
Vector3 direction = opponent.transform.position - transform.position;
direction.y = 0f;
if (direction.sqrMagnitude <= 0.0001f)
{
return;
}
Quaternion targetRotation = Quaternion.LookRotation(direction, Vector3.up);
transform.rotation = Quaternion.Euler(0f, targetRotation.eulerAngles.y, 0f);
}
//由攻击动画中最合适的帧通过 Animation Event 调用。
public void OnAttackEffect()
{
@@ -365,6 +432,26 @@ public class UnitBase : MonoBehaviour
RegisterAttackHit();
}
public void ApplySkillHit(float damageMultiplier, bool registerEnergy)
{
if (target == null)
{
return;
}
WorldHealthBar healthBar = target.GetComponentInParent<WorldHealthBar>();
if (healthBar == null)
{
return;
}
healthBar.TakeDamage(attackPower * Mathf.Max(0f, damageMultiplier));
if (registerEnergy)
{
RegisterAttackHit();
}
}
public void RegisterAttackHit()
{
AddAttackEnergy();
@@ -432,7 +519,18 @@ public class UnitBase : MonoBehaviour
}
}
public void PlaySkillAnimation(System.Action onFinished)
private float GetSkillAnimationSpeed(float targetDuration)
{
float animationDuration = GetSkillAnimationDuration();
if (animationDuration <= 0f || targetDuration <= 0f)
{
return 1f;
}
return animationDuration / targetDuration;
}
public void PlaySkillAnimation(System.Action onFinished, float targetDuration = 0f)
{
if (isPlayingSkill)
{
@@ -442,7 +540,7 @@ public class UnitBase : MonoBehaviour
skillFinishedCallback = onFinished;
isPlayingSkill = true;
state = State.Skill;
animator.speed = 1f;
animator.speed = GetSkillAnimationSpeed(targetDuration);
animator.SetInteger("speed", 0);
animator.SetTrigger("skill");
attackStartTime = Time.time;
@@ -520,6 +618,8 @@ public class UnitBase : MonoBehaviour
target = FindNearestOpponent();
if (target != null && IsWithinAttackReach(target))
{
// 攻击结束后可能已切换到新的近距离目标,先更新朝向再开始下一次攻击。
FaceTarget(target);
attack(target);
return;
}