更新
This commit is contained in:
@@ -13,11 +13,10 @@ using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame
|
||||
{
|
||||
public abstract partial class AttackAreaBase : PooledObject
|
||||
public abstract partial class AttackAreaBase : MonoBehaviour
|
||||
{
|
||||
[Title("References")]
|
||||
public CharacterBase creator;
|
||||
public bool isGeneratedByPlayer => creator == MainGameManager.Instance.player;
|
||||
public ItemBase itemSource;
|
||||
public List<Fraction> targetFractions;
|
||||
public Transform topParent;
|
||||
@@ -39,13 +38,6 @@ namespace Cielonos.MainGame
|
||||
[HideInEditorMode] public ForceSubmodule forceSm;
|
||||
[HideInEditorMode] public ReactionSubmodule reactionSm;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void Reset()
|
||||
{
|
||||
isAutoDespawn = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
public T Initialize<T>(CharacterBase creator, params Fraction[] targetFractions) where T : AttackAreaBase
|
||||
{
|
||||
return Initialize<T>(creator, null, targetFractions);
|
||||
@@ -278,7 +270,10 @@ namespace Cielonos.MainGame
|
||||
//float disruptionBreakLevel = attackModule.modifiedAttackValue.baseDisruptionBreakLevel;
|
||||
//SetReaction(player, disruptionBreakLevel);
|
||||
|
||||
target.GetHit(attackSm.modifiedAttackValue.breakthroughType, attackSm.modifiedAttackValue.disruptionType);
|
||||
BreakthroughType breakthroughType = attackSm.modifiedAttackValue.breakthroughType;
|
||||
DisruptionType disruptionType = attackSm.modifiedAttackValue.disruptionType;
|
||||
Vector3 direction = (target.flexibleCenterPoint.position - creator.flexibleCenterPoint.position).normalized;
|
||||
target.GetHit(breakthroughType, out float recoveryTime, disruptionType, direction);
|
||||
}
|
||||
|
||||
//应用额外力
|
||||
@@ -316,14 +311,14 @@ namespace Cielonos.MainGame
|
||||
|
||||
protected virtual GameObject GenerateHitEffect(CharacterBase target, Collider hitCollider, Vector3 hitPosition)
|
||||
{
|
||||
GameObject hitEffect = attackSm.SpawnHitVFX(hitPosition);
|
||||
GameObject hitEffect = attackSm.SpawnHitVFX(creator, hitPosition);
|
||||
attackSm.modifyHitEffectAction?.Invoke(hitEffect, target);
|
||||
return hitEffect;
|
||||
}
|
||||
|
||||
protected virtual GameObject GenerateHitEffect(Vector3 hitPosition)
|
||||
{
|
||||
GameObject hitEffect = attackSm.SpawnHitVFX(hitPosition);
|
||||
GameObject hitEffect = attackSm.SpawnHitVFX(creator, hitPosition);
|
||||
attackSm.modifyHitEffectAction?.Invoke(hitEffect, null);
|
||||
return hitEffect;
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ namespace Cielonos.MainGame
|
||||
this.damageNumberCriticalPrefab = MainGameManager.BasePrefabs.hudTextCollection["DefaultDamageNumber_Critical"];
|
||||
}
|
||||
|
||||
public GameObject SpawnHitVFX(Vector3 position, Vector3 direction = default)
|
||||
public GameObject SpawnHitVFX(CharacterBase creator, Vector3 position, Vector3 direction = default)
|
||||
{
|
||||
if (isOverridingHitEffect) return null;
|
||||
|
||||
if (hitVFXPrefab != null)
|
||||
{
|
||||
direction = direction == default ? direction : Vector3.up;
|
||||
GameObject hitEffect = LeanPool.Spawn(hitVFXPrefab, position, Quaternion.LookRotation(direction));
|
||||
GameObject hitEffect = VFXObject.Spawn(hitVFXPrefab, creator, position, Quaternion.LookRotation(direction));
|
||||
return hitEffect;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Cielonos.MainGame
|
||||
return;
|
||||
}
|
||||
|
||||
currentIntervalTime += Time.deltaTime; //attackArea.creator.selfTimeModule.EntityDeltaTime;
|
||||
currentIntervalTime += attackArea.creator.selfTimeSm.DeltaTime;
|
||||
if (currentIntervalTime >= hitInterval)
|
||||
{
|
||||
checkedObjects.Clear();
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Cielonos.MainGame
|
||||
|
||||
if (delayTime > 0)
|
||||
{
|
||||
delayTime -= Time.deltaTime; //attackArea.creator.selfTimeModule.EntityDeltaTime;
|
||||
delayTime -= attackArea.creator.selfTimeSm.DeltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@ namespace Cielonos.MainGame
|
||||
enableAction?.Invoke();
|
||||
}
|
||||
|
||||
enablingTime += Time.deltaTime; //attackArea.creator.selfTimeModule.EntityDeltaTime;
|
||||
remainingLifeTime -= Time.deltaTime; //attackArea.creator.selfTimeModule.EntityDeltaTime;
|
||||
remainingEnableTime -= Time.deltaTime; //attackArea.creator.selfTimeModule.EntityDeltaTime;
|
||||
enablingTime += attackArea.creator.selfTimeSm.DeltaTime;
|
||||
remainingLifeTime -= attackArea.creator.selfTimeSm.DeltaTime;
|
||||
remainingEnableTime -= attackArea.creator.selfTimeSm.DeltaTime;
|
||||
|
||||
if (remainingLifeTime <= 0)
|
||||
{
|
||||
|
||||
@@ -43,18 +43,17 @@ namespace Cielonos.MainGame
|
||||
}
|
||||
|
||||
public abstract bool OnBuffApply(out CharacterBuffBase existingBuff);
|
||||
|
||||
public override void OnAfterFirstApply()
|
||||
{
|
||||
statusSubmodule?.AddStatus();
|
||||
attachedCharacter.buffSm.buffList.Exclude(this).For(buff => buff.eventSubmodule?.onOtherBuffFirstApplied.Invoke(this));
|
||||
}
|
||||
|
||||
protected float DeltaTime => attachedCharacter.selfTimeSm.DeltaTime;
|
||||
|
||||
public override void OnBuffUpdate()
|
||||
{
|
||||
timeSubmodule?.Update(DeltaTime);
|
||||
independentStackSubmodule?.Update(DeltaTime);
|
||||
timeSubmodule?.Update(attachedCharacter.selfTimeSm.DeltaTime);
|
||||
independentStackSubmodule?.Update(attachedCharacter.selfTimeSm.DeltaTime);
|
||||
}
|
||||
|
||||
public override void OnBuffRemove()
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.FunctionalAnimation
|
||||
{
|
||||
public class SpawnVFX : FuncAnimPayloadBase
|
||||
{
|
||||
public string vfxKey = "VFXKey";
|
||||
|
||||
public override void Invoke()
|
||||
{
|
||||
VFXObject vfxObject = runtimeFuncAnim.executor.vfxData.SpawnVFX(vfxKey).GetComponent<VFXObject>();
|
||||
if (vfxObject != null)
|
||||
{
|
||||
vfxObject.SetCreator(runtimeFuncAnim.executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0127f520fea3f7947afd7ecb0e9b1940
|
||||
@@ -17,7 +17,6 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
[TitleGroup("Data & Presets")]
|
||||
public List<FuncAnimData> fullBodyFuncAnims = new List<FuncAnimData>();
|
||||
public VFXData vfxData;
|
||||
public AttackData attackData;
|
||||
[HideInInspector]
|
||||
private List<string> registeredFunctionNames = new List<string>();
|
||||
@@ -35,7 +34,7 @@ namespace Cielonos.MainGame.Characters
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
vfxData.Initialize(this);
|
||||
|
||||
RegisterFullBodyFuncAnims();
|
||||
//RegisterFunctionsToAnimSc(LightAttack0);
|
||||
}
|
||||
@@ -49,16 +48,18 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
public partial class Automata
|
||||
{
|
||||
public override bool GetHit(BreakthroughType breakthroughType,
|
||||
public override bool GetHit(BreakthroughType breakthroughType, out float recoveryTime,
|
||||
DisruptionType disruptionType = DisruptionType.NormalExternal, Vector3 direction = default)
|
||||
{
|
||||
if (base.GetHit(breakthroughType, disruptionType, direction))
|
||||
if (base.GetHit(breakthroughType, out recoveryTime, disruptionType, direction))
|
||||
{
|
||||
navMeshAgent.isStopped = true;
|
||||
statusSm.AddStatus(StatusType.Stun);
|
||||
getHitRecovery?.Dispose();
|
||||
getHitRecovery = Observable.Timer(TimeSpan.FromSeconds(1f)).Subscribe(_ =>
|
||||
getHitRecovery = Observable.Timer(TimeSpan.FromSeconds(recoveryTime)).Subscribe(_ =>
|
||||
{
|
||||
navMeshAgent.isStopped = false;
|
||||
statusSm.RemoveStatus(StatusType.Stun);
|
||||
}).AddTo(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Cielonos.MainGame.Characters
|
||||
private void ThreeSwings_2() => GenerateSlash("ThreeSwings_2");
|
||||
private void ClawStabBlast()
|
||||
{
|
||||
GenerateBlast("ClawStabBlast", "ClawStabBlast", 6);
|
||||
GenerateClawStabBlast("ClawStabBlast", "ClawStabBlast", 25);
|
||||
feedbackSc["ImpaleWave_Stab"].Play();
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
private void JumpAttackBlast()
|
||||
{
|
||||
GenerateBlast("JumpAttackBlast", "JumpAttackBlast", 30);
|
||||
GenerateJumpAttackBlast("JumpAttackBlast", "JumpAttackBlast", 40);
|
||||
feedbackSc["JumpAttack_Blast"].Play();
|
||||
}
|
||||
|
||||
@@ -125,7 +125,19 @@ namespace Cielonos.MainGame.Characters
|
||||
slash.hitSm.AddHitSound("GeneralHit");
|
||||
}
|
||||
|
||||
private void GenerateBlast(string vfxName, string attackDataName, float force)
|
||||
private void GenerateClawStabBlast(string vfxName, string attackDataName, float force)
|
||||
{
|
||||
NormalArea slash = vfxData.SpawnVFX(vfxName).GetComponentInChildren<NormalArea>();
|
||||
slash.Initialize<NormalArea>(this, null, Fraction.Player)
|
||||
.SetAttackSubmodule<NormalArea>(attackData[attackDataName])
|
||||
.SetTimeSubmodule<NormalArea>(1.2f)
|
||||
.SetHitSubmodule<NormalArea>()
|
||||
.SetForceSubmodule<NormalArea>(transform.forward * force)
|
||||
.SetReactionSubmodule<NormalArea>(false, false, false, true, true, false);
|
||||
}
|
||||
|
||||
|
||||
private void GenerateJumpAttackBlast(string vfxName, string attackDataName, float force)
|
||||
{
|
||||
NormalArea slash = vfxData.SpawnVFX(vfxName).GetComponentInChildren<NormalArea>();
|
||||
slash.Initialize<NormalArea>(this, null, Fraction.Player)
|
||||
@@ -143,7 +155,7 @@ namespace Cielonos.MainGame.Characters
|
||||
.SetAttackSubmodule<NormalArea>(attackData["ImpaleWave"])
|
||||
.SetTimeSubmodule<NormalArea>(3f, 0.2f, 1.3f)
|
||||
.SetHitSubmodule<NormalArea>()
|
||||
.SetForceSubmodule<NormalArea>(16f, true)
|
||||
.SetForceSubmodule<NormalArea>(transform.forward * 30f)
|
||||
.SetReactionSubmodule<NormalArea>(false, false, false, true, true, false);
|
||||
|
||||
slash.transform.DOLocalMoveZ(8f, 0.8f).From(0f).Play();
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
[TitleGroup("Data & Presets")]
|
||||
public AttributeData attributeData;
|
||||
public VFXData vfxData;
|
||||
public BaseAnimationGroup baseAnimationGroup;
|
||||
|
||||
[TitleGroup("Submodules")]
|
||||
@@ -50,8 +51,8 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
InitializeSubmodules();
|
||||
InitializeSubcontrollers();
|
||||
InitializeSubmodules();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -63,6 +64,8 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
vfxData.Initialize(this);
|
||||
|
||||
if (fraction == Fraction.Enemy)
|
||||
{
|
||||
BattleManager.EnemySm.activeEnemiesList.Add(this);
|
||||
@@ -175,16 +178,45 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
public partial class CharacterBase
|
||||
{
|
||||
public virtual bool GetHit(BreakthroughType breakthroughType,
|
||||
public virtual bool GetHit(BreakthroughType breakthroughType, out float recoveryTime,
|
||||
DisruptionType disruptionType = DisruptionType.NormalExternal, Vector3 direction = default)
|
||||
{
|
||||
renderSc.GetHitBlink();
|
||||
if (animationSc.SetGetHitDisruption(disruptionType, breakthroughType))
|
||||
{
|
||||
animationSc.PlayGetHitMediumAnimation(direction);
|
||||
switch (breakthroughType)
|
||||
{
|
||||
case BreakthroughType.Medium:
|
||||
animationSc.PlayGetHitMediumAnimation(out recoveryTime, direction);
|
||||
break;
|
||||
case BreakthroughType.Heavy:
|
||||
animationSc.PlayGetHitHeavyAnimation(out recoveryTime,direction);
|
||||
break;
|
||||
case BreakthroughType.Disruption:
|
||||
case BreakthroughType.Forced:
|
||||
animationSc.PlayGetHitDisruptionAnimation(out recoveryTime,direction);
|
||||
break;
|
||||
default:
|
||||
animationSc.PlayGetHitBoneShake(0.4f, direction);
|
||||
recoveryTime = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float intensity = breakthroughType switch
|
||||
{
|
||||
BreakthroughType.None => 0,
|
||||
BreakthroughType.Weak => 0.2f,
|
||||
BreakthroughType.Medium => 0.4f,
|
||||
BreakthroughType.Heavy or BreakthroughType.Disruption or BreakthroughType.Forced => 0.8f,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
recoveryTime = 0f;
|
||||
animationSc.PlayGetHitBoneShake(intensity, direction);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using SLSFramework.General;
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
@@ -57,6 +58,7 @@ namespace Cielonos.MainGame.Characters
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
fullBodyFuncAnimSm?.UpdateEvents();
|
||||
BoneShakeLateUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +81,90 @@ namespace Cielonos.MainGame.Characters
|
||||
}
|
||||
}
|
||||
|
||||
public partial class AnimationSubcontrollerBase
|
||||
{
|
||||
private class BoneShakeState
|
||||
{
|
||||
public Transform bone;
|
||||
public Vector3 shakeAxis; // 震动轴
|
||||
// 物理变量
|
||||
public float currentAngle = 0f; // 当前偏离角度 (位移 x)
|
||||
public float velocity = 0f; // 当前震动速度 (速度 v)
|
||||
}
|
||||
|
||||
[Header("Bone Shake Settings")]
|
||||
// 刚度:越大越硬,回弹越快。对于重型机甲,建议 150-300;轻型无人机,建议 80-150。
|
||||
public float stiffness = 200f;
|
||||
// 阻尼:越大停得越快。建议 10-20。如果太小,机器人会像果冻一样晃。
|
||||
public float damping = 15f;
|
||||
// 冲击力倍率:将受击力度转化为弹簧的初始速度
|
||||
public float impactForceMultiplier = 500f;
|
||||
|
||||
public List<Transform> testShakeBones; // 用于测试的骨骼列表
|
||||
private List<BoneShakeState> activeShakes = new List<BoneShakeState>();
|
||||
|
||||
private void BoneShakeLateUpdate()
|
||||
{
|
||||
float dt = Time.deltaTime;
|
||||
|
||||
for (int i = activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var state = activeShakes[i];
|
||||
|
||||
if (state.bone == null) {
|
||||
activeShakes.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- 核心:阻尼弹簧物理公式 (Hooke's Law + Damping) ---
|
||||
// F = -k * x - d * v
|
||||
// force = -stiffness * displacement - damping * velocity
|
||||
|
||||
float force = -stiffness * state.currentAngle - damping * state.velocity;
|
||||
|
||||
// a = F / m (假设质量为1,简化计算)
|
||||
// v += a * dt
|
||||
state.velocity += force * dt;
|
||||
|
||||
// x += v * dt
|
||||
state.currentAngle += state.velocity * dt;
|
||||
|
||||
// --- 应用旋转 ---
|
||||
// 将计算出的角度应用到轴向上
|
||||
Quaternion shakeRot = Quaternion.AngleAxis(state.currentAngle, state.shakeAxis);
|
||||
state.bone.localRotation = state.bone.localRotation * shakeRot;
|
||||
|
||||
// --- 移除条件 ---
|
||||
// 当能量非常小(速度和位移都接近0)时移除,节省性能
|
||||
if (Mathf.Abs(state.currentAngle) < 0.1f && Mathf.Abs(state.velocity) < 0.1f)
|
||||
{
|
||||
activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyBoneShake(Transform hitBone, Vector3 hitDirection, float intensity)
|
||||
{
|
||||
var state = activeShakes.Find(x => x.bone == hitBone);
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
state = new BoneShakeState();
|
||||
state.bone = hitBone;
|
||||
activeShakes.Add(state);
|
||||
}
|
||||
|
||||
// 机械特质:受击瞬间不是直接设置位移,而是给予一个巨大的“初速度” (Impulse)
|
||||
// 这会让骨骼瞬间弹出去,然后被弹簧拉回来,非常有力量感
|
||||
state.velocity += intensity * impactForceMultiplier;
|
||||
|
||||
// 计算震动轴:依旧是垂直于攻击方向
|
||||
Vector3 axis = Vector3.Cross(hitDirection, Vector3.up).normalized;
|
||||
if (axis == Vector3.zero) axis = Vector3.right;
|
||||
state.shakeAxis = axis;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class AnimationSubcontrollerBase
|
||||
{
|
||||
public virtual void RegisterDefaultFunctions()
|
||||
@@ -116,47 +202,68 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void PlayGetHitMediumAnimation(Vector3 direction = default)
|
||||
|
||||
public virtual void PlayGetHitBoneShake(float intensity, Vector3 direction = default)
|
||||
{
|
||||
if (direction == default)
|
||||
{
|
||||
direction = owner.transform.right;
|
||||
}
|
||||
else
|
||||
{
|
||||
direction = Quaternion.Euler(0, 90, 0) * direction;
|
||||
}
|
||||
|
||||
foreach (Transform bone in testShakeBones)
|
||||
{
|
||||
ApplyBoneShake(bone, direction, intensity);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void PlayGetHitAnimation(string getHitAnimPrefix, out float animDuration, Vector3 direction = default)
|
||||
{
|
||||
int fullBodyActionIndex = animator.GetLayerIndex("FullBodyAction");
|
||||
|
||||
if (animator.HasState(fullBodyActionIndex, Animator.StringToHash("GetHitMediumFront")))
|
||||
string getHitFrontAnim = getHitAnimPrefix + "Front";
|
||||
string getHitAnim = getHitAnimPrefix + "Front";
|
||||
float normalizedTransitionDuration = 0.1f / animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
animDuration = 0f;
|
||||
if (animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnim)))
|
||||
{
|
||||
float normalizedTransitionDuration = 0.1f / animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
|
||||
if (direction == default)
|
||||
{
|
||||
animator.CrossFade("GetHitMediumFront", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
direction.y = 0;
|
||||
direction = direction.normalized;
|
||||
float angle = Vector3.SignedAngle(transform.forward, direction, Vector3.up);
|
||||
|
||||
if (angle > -45f && angle <= 45f)
|
||||
|
||||
string directionStr = angle switch
|
||||
{
|
||||
animator.CrossFade("GetHitMediumBack", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
}
|
||||
else if (angle > 45f && angle <= 135f)
|
||||
> -45f and <= 45f => "Back",
|
||||
> 45f and <= 135f => "Left",
|
||||
> -135f and <= -45f => "Right",
|
||||
_ => "Front"
|
||||
};
|
||||
|
||||
getHitAnim = getHitAnimPrefix + directionStr;
|
||||
if (direction == default || !animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnim)))
|
||||
{
|
||||
animator.CrossFade("GetHitMediumLeft", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
}
|
||||
else if (angle > -135f && angle <= -45f)
|
||||
{
|
||||
animator.CrossFade("GetHitMediumRight", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animator.CrossFade(getHitFrontAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
animator.CrossFade("GetHitMediumFront", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animator.CrossFade(getHitAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
}
|
||||
animDuration = animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
}
|
||||
else if (animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnimPrefix)))
|
||||
{
|
||||
getHitAnim = getHitAnimPrefix;
|
||||
animator.CrossFade(getHitAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animDuration = animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animator.HasState(fullBodyActionIndex, Animator.StringToHash("GetHit")))
|
||||
{
|
||||
animator.CrossFade("GetHit", 0.1f, fullBodyActionIndex, 0);
|
||||
animator.CrossFade("GetHit", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animDuration = animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -164,5 +271,20 @@ namespace Cielonos.MainGame.Characters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void PlayGetHitMediumAnimation(out float animDuration, Vector3 direction = default)
|
||||
{
|
||||
PlayGetHitAnimation("GetHitMedium", out animDuration, direction);
|
||||
}
|
||||
|
||||
public virtual void PlayGetHitHeavyAnimation(out float animDuration, Vector3 direction = default)
|
||||
{
|
||||
PlayGetHitAnimation("GetHitHeavy", out animDuration, direction);
|
||||
}
|
||||
|
||||
public virtual void PlayGetHitDisruptionAnimation(out float animDuration, Vector3 direction = default)
|
||||
{
|
||||
PlayGetHitAnimation("GetHitDisruption", out animDuration, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
public class BodyPartsSubcontroller : SubcontrollerBase<CharacterBase>
|
||||
{
|
||||
[Title("Main Parts")]
|
||||
public Transform centerPoint;
|
||||
[FormerlySerializedAs("centerPoint")] [Title("Main Parts")]
|
||||
public Transform flexibleCenterPoint;
|
||||
public Transform staticCenterPoint;
|
||||
public Transform footPoint;
|
||||
public Transform head;
|
||||
public Transform leftHand;
|
||||
|
||||
@@ -48,6 +48,32 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
characterTransform.DOLookAt(target.transform.position, duration, AxisConstraint.Y);
|
||||
}
|
||||
|
||||
public void TurnToDirection(Vector3 direction, float duration = 0f)
|
||||
{
|
||||
Vector3 dashRotation = Vector3.zero;
|
||||
float angle = Vector3.SignedAngle(Vector3.forward, direction, Vector3.up);
|
||||
|
||||
if (owner is Player player)
|
||||
{
|
||||
dashRotation.y = player.viewSc.isLockedOn
|
||||
? player.viewSc.cameraRotationSm.cinemachineEndLockYaw + angle
|
||||
: player.viewSc.cameraRotationSm.cinemachineTargetYaw + angle;
|
||||
}
|
||||
else
|
||||
{
|
||||
dashRotation = new Vector3(0, angle, 0);
|
||||
}
|
||||
|
||||
if (duration > 0)
|
||||
{
|
||||
characterTransform.DORotateQuaternion(Quaternion.Euler(dashRotation), duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterTransform.rotation = Quaternion.Euler(dashRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class MovementSubcontrollerBase
|
||||
|
||||
@@ -133,8 +133,27 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
getHitBlinkTween?.Kill(true);
|
||||
getHitBlinkTween = DOTween.Sequence();
|
||||
|
||||
getHitBlinkTween.OnPlay(() =>
|
||||
|
||||
foreach (Material mat in baseRenderMaterials)
|
||||
{
|
||||
Tweener rimTween = mat.DOVector(new Vector4(1, 1, 4, 1), "_RimParams", 0.5f)
|
||||
.From(new Vector4(0, 1, 4, 1))
|
||||
.OnPlay(() =>
|
||||
{
|
||||
mat.EnableKeyword("_RIM");
|
||||
})
|
||||
.SetEase(Ease.OutQuad)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
mat.DisableKeyword("_RIM");
|
||||
});
|
||||
|
||||
getHitBlinkTween.Join(rimTween);
|
||||
}
|
||||
|
||||
getHitBlinkTween.Play();
|
||||
|
||||
/*getHitBlinkTween.OnPlay(() =>
|
||||
{
|
||||
effectContainers["GetHitBlink"].SetActive(true);
|
||||
});
|
||||
@@ -157,7 +176,7 @@ namespace Cielonos.MainGame.Characters
|
||||
getHitBlinkTween.OnComplete(() =>
|
||||
{
|
||||
effectContainers["GetHitBlink"].SetActive(false);
|
||||
});
|
||||
});*/
|
||||
|
||||
getHitBlinkTween.Play();
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
public AdditionalForceSubmodule(CharacterBase character) : base(character)
|
||||
{
|
||||
additionalForceXZ = new LerpVector3(Vector3.zero, 5f);
|
||||
additionalForceY = new LerpFloat(0f, 5f);
|
||||
additionalForceXZ = new LerpVector3(Vector3.zero, 1f);
|
||||
additionalForceY = new LerpFloat(0f, 1f);
|
||||
}
|
||||
|
||||
public void AddForce(Vector3 force)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Characters;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -23,8 +24,11 @@ namespace Cielonos.MainGame
|
||||
|
||||
|
||||
public float currentPlaySpeedMultiplier = 1f;
|
||||
public float currentPlayTime => currentRuntimeFuncAnim.currentPlayTime;
|
||||
[ShowInInspector]
|
||||
public float currentPlayTime => currentRuntimeFuncAnim?.currentPlayTime ?? 0f;
|
||||
public float currentNormalizedPlayTime => Mathf.Min(1, currentPlayTime / currentClip.length);
|
||||
[ShowInInspector]
|
||||
public float currentFrame => currentRuntimeFuncAnim?.currentPlayTime * currentRuntimeFuncAnim?.funcAnimData.animationClip.frameRate ?? 0f;
|
||||
public float currentScaledClipLength => currentClip.length / currentPlaySpeedMultiplier;
|
||||
|
||||
|
||||
@@ -216,7 +220,7 @@ namespace Cielonos.MainGame
|
||||
return;
|
||||
}
|
||||
|
||||
currentRuntimeFuncAnim.currentPlayTime += Time.deltaTime * currentData.animInfo.overridePlaySpeed * currentPlaySpeedMultiplier;
|
||||
currentRuntimeFuncAnim.currentPlayTime += owner.owner.selfTimeSm.DeltaTime * currentData.animInfo.overridePlaySpeed * currentPlaySpeedMultiplier;
|
||||
|
||||
if (currentPlayTime >= currentClip.length)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Characters;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
public class SelfTimeSubmodule : SubmoduleBase<CharacterBase>
|
||||
public partial class SelfTimeSubmodule : SubmoduleBase<CharacterBase>
|
||||
{
|
||||
public FloatReactiveProperty timeScaleCoefficient;
|
||||
public float TimeScale => timeScaleCoefficient.Value * Time.timeScale;
|
||||
@@ -13,10 +16,150 @@ namespace Cielonos.MainGame.Characters
|
||||
public SelfTimeSubmodule(CharacterBase entity) : base(entity)
|
||||
{
|
||||
timeScaleCoefficient = new FloatReactiveProperty(1);
|
||||
|
||||
if (entity.animationSc != null)
|
||||
{
|
||||
timeScaleCoefficient.Subscribe(x =>
|
||||
{
|
||||
entity.animationSc.fullBodyFuncAnimSm.currentPlaySpeedMultiplier = x;
|
||||
});
|
||||
}
|
||||
|
||||
if (entity.animationSc.animator != null)
|
||||
{
|
||||
timeScaleCoefficient.Subscribe(x => { entity.animationSc.animator.speed = x; });
|
||||
timeScaleCoefficient.Subscribe(x =>
|
||||
{
|
||||
entity.animationSc.animator.speed = x;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SelfTimeSubmodule
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加一个基于本地时间(Local DeltaTime)的计时器
|
||||
/// </summary>
|
||||
public IDisposable AddLocalTimer(float duration, Action onComplete, Action onUpdate = null)
|
||||
{
|
||||
// 用于记录累积时间
|
||||
float accumulatedTime = 0f;
|
||||
|
||||
return Observable.EveryUpdate()
|
||||
.Select(_ => DeltaTime) // 1. 获取每帧的真实 DeltaTime
|
||||
.TakeWhile(dt =>
|
||||
{
|
||||
// 2. 累加时间
|
||||
accumulatedTime += dt;
|
||||
// 3. 如果累积时间小于总时长,继续流;否则停止流并触发 OnCompleted
|
||||
return accumulatedTime < duration;
|
||||
})
|
||||
.Subscribe(
|
||||
_ => onUpdate?.Invoke(), // 每帧更新时执行 Action
|
||||
() => onComplete?.Invoke() // 4. 流结束时(TakeWhile 返回 false)执行 Action
|
||||
).AddTo(owner); // 5. 绑定生命周期到角色,防止内存泄漏
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SelfTimeSubmodule
|
||||
{
|
||||
// 缓存一个默认的抛物线曲线,避免每次 null 时都 new 一个
|
||||
// 形状:(0,0) -> (0.5, 1) -> (1, 0)
|
||||
private static readonly AnimationCurve DefaultParabola = new AnimationCurve(
|
||||
new Keyframe(0f, 0f),
|
||||
new Keyframe(0.5f, 1f),
|
||||
new Keyframe(1f, 0f)
|
||||
);
|
||||
|
||||
private IDisposable hitStopDisposable;
|
||||
|
||||
/// <summary>
|
||||
/// 应用顿帧(Hit Stop)
|
||||
/// </summary>
|
||||
/// <param name="duration">持续时间(秒,基于全局游戏时间)</param>
|
||||
/// <param name="targetScale">目标缩放倍率(通常为 0 或 0.1)</param>
|
||||
public void ModifyTimeScale(float duration, float targetScale = 0f)
|
||||
{
|
||||
// 1. 如果之前有正在进行的顿帧,先取消它(防止旧的恢复逻辑覆盖新的设置)
|
||||
hitStopDisposable?.Dispose();
|
||||
|
||||
// 2. 设置当前的缩放倍率
|
||||
timeScaleCoefficient.Value = targetScale;
|
||||
|
||||
// 3. 开启计时器
|
||||
// 注意:这里使用 Scheduler.MainThread,它是基于 Time.time (全局时间) 的。
|
||||
// 这意味着:
|
||||
// - 它会受到 Time.timeScale (全局暂停) 的影响(符合预期,游戏暂停时顿帧也该暂停)。
|
||||
// - 它 *不会* 受到 timeScaleCoefficient (我们自己改的本地时间) 的影响(关键!)。
|
||||
hitStopDisposable = Observable.Timer(TimeSpan.FromSeconds(duration), Scheduler.MainThread)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
// 计时结束,恢复为 1
|
||||
timeScaleCoefficient.Value = 1f;
|
||||
hitStopDisposable = null;
|
||||
})
|
||||
.AddTo(owner); // 安全性:如果角色在顿帧期间死亡/销毁,自动取消计时器
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用曲线动态修改本地时间流速
|
||||
/// </summary>
|
||||
/// <param name="duration">持续时间(秒)</param>
|
||||
/// <param name="start">曲线值为0时对应的时间倍率(通常是初始值)</param>
|
||||
/// <param name="peak">曲线值为1时对应的时间倍率(通常是极值)</param>
|
||||
/// <param name="curve">时间变化曲线(归一化:X轴0~1,Y轴通常0~1)。如果为null,则使用默认的“先升后降”抛物线。</param>
|
||||
public void ModifyTimeScale(float duration, float start, float peak, AnimationCurve curve = null)
|
||||
{
|
||||
// 1. 清理旧的计时器
|
||||
hitStopDisposable?.Dispose();
|
||||
|
||||
// 2. 处理默认曲线逻辑
|
||||
curve ??= DefaultParabola;
|
||||
|
||||
// 3. 记录开始时的累计时间
|
||||
float timer = 0f;
|
||||
|
||||
// 4. 开启每帧更新的流
|
||||
hitStopDisposable = Observable.EveryUpdate()
|
||||
.TakeWhile(_ => timer < duration) // 当时间超过 duration 时结束流
|
||||
.Subscribe(
|
||||
_ =>
|
||||
{
|
||||
// 累加时间 (使用 Time.deltaTime 以响应全局暂停)
|
||||
timer += Time.deltaTime;
|
||||
|
||||
// 计算归一化进度 (0.0 ~ 1.0)
|
||||
float progress = Mathf.Clamp01(timer / duration);
|
||||
|
||||
// 核心逻辑:
|
||||
// A. 从曲线获取当前的“强度” (Y轴值)
|
||||
float curveValue = curve.Evaluate(progress);
|
||||
|
||||
// B. 在 start 和 peak 之间根据强度进行插值
|
||||
// 当 curveValue = 0 时,结果为 start
|
||||
// 当 curveValue = 1 时,结果为 peak
|
||||
float currentScale = Mathf.Lerp(start, peak, curveValue);
|
||||
|
||||
// C. 应用到响应式属性
|
||||
timeScaleCoefficient.Value = currentScale;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// 5. 计时结束后的收尾工作
|
||||
// 通常为了安全,结束后我们会强制恢复到 1.0 (正常速度)
|
||||
// 或者你可以恢复到 start,视具体需求而定
|
||||
timeScaleCoefficient.Value = 1f;
|
||||
hitStopDisposable = null;
|
||||
}
|
||||
)
|
||||
.AddTo(owner); // 绑定生命周期
|
||||
}
|
||||
|
||||
// 可选:提供一个强制恢复的方法,用于因为某些逻辑需要立刻打断顿帧时调用
|
||||
public void ResetTimeScale()
|
||||
{
|
||||
hitStopDisposable?.Dispose();
|
||||
timeScaleCoefficient.Value = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Cielonos.MainGame.Characters
|
||||
Disarm = 2, //缴械,无法攻击(使用主武器)
|
||||
Restraint = 3, //束缚,无法移动
|
||||
|
||||
Stun = 100, //眩晕
|
||||
|
||||
//正面状态
|
||||
Invincible = 1000, //无敌
|
||||
Invisible = 1001, //隐身
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Cielonos.MainGame.Characters
|
||||
public AnimationClip jumpStart;
|
||||
[FormerlySerializedAs("jumpInAir")] public AnimationClip inAir;
|
||||
public AnimationClip jumpLand;
|
||||
public AnimationClip getHitLightFront, getHitLightBack, getHitLightLeft, getHitLightRight;
|
||||
public AnimationClip getHitMediumFront, getHitMediumBack, getHitMediumLeft, getHitMediumRight;
|
||||
public AnimationClip getHitHeavyFront, getHitHeavyBack, getHitHeavyLeft, getHitHeavyRight;
|
||||
public AnimationClip getHitKnockedFront, getHitKnockedBack, getHitKnockedLeft, getHitKnockedRight;
|
||||
public AnimationClip getHitDisruptionFront, getHitDisruptionBack, getHitDisruptionLeft, getHitDisruptionRight;
|
||||
public AnimationClip riseUpFront, riseUpBack, riseUpLeft, riseUpRight;
|
||||
public AnimationClip incapacitation, death;
|
||||
|
||||
@@ -43,6 +43,19 @@ namespace Cielonos.MainGame.Characters
|
||||
animatorOverride["JumpStart"] = jumpStart;
|
||||
animatorOverride["InAir"] = inAir;
|
||||
animatorOverride["JumpLand"] = jumpLand;
|
||||
|
||||
animatorOverride["GetHitMediumFront"] = getHitMediumFront;
|
||||
animatorOverride["GetHitMediumBack"] = getHitMediumBack;
|
||||
animatorOverride["GetHitMediumLeft"] = getHitMediumLeft;
|
||||
animatorOverride["GetHitMediumRight"] = getHitMediumRight;
|
||||
animatorOverride["GetHitHeavyFront"] = getHitHeavyFront;
|
||||
animatorOverride["GetHitHeavyBack"] = getHitHeavyBack;
|
||||
animatorOverride["GetHitHeavyLeft"] = getHitHeavyLeft;
|
||||
animatorOverride["GetHitHeavyRight"] = getHitHeavyRight;
|
||||
animatorOverride["GetHitDisruptionFront"] = getHitDisruptionFront;
|
||||
animatorOverride["GetHitDisruptionBack"] = getHitDisruptionBack;
|
||||
animatorOverride["GetHitDisruptionLeft"] = getHitDisruptionLeft;
|
||||
animatorOverride["GetHitDisruptionRight"] = getHitDisruptionRight;
|
||||
}
|
||||
|
||||
animSc.fullBodyFuncAnimSm.ReSet(dash);
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace Cielonos.MainGame.Characters
|
||||
if (IsMoving)
|
||||
{
|
||||
operation.Dash();
|
||||
player.landMovementSc.TurnToDirection(new Vector3(Move.x, 0, Move.y));
|
||||
preinputSubmodule.RegisterPreinputAction(() => operation.Dash(), 10);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSFramework.General;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -11,15 +12,22 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
private const float RotateThreshold = 0.01f;
|
||||
|
||||
[Title("Cinemachine Settings")]
|
||||
public float cinemachineTargetYaw;
|
||||
public float cinemachineEndLockYaw;
|
||||
public float cinemachineTargetPitch;
|
||||
|
||||
public float topClamp = 70.0f;
|
||||
public float bottomClamp = -30.0f;
|
||||
public float cameraAngleOverride = 0.0f;
|
||||
public bool lockCameraPosition = false;
|
||||
|
||||
|
||||
[Title("Combat Recenter Settings")]
|
||||
public float recenterSmoothTime = 0.1f; // 平滑时间,越小转得越快
|
||||
private float recenterVelocity; // SmoothDamp使用的速度变量
|
||||
private float targetRecenterYaw; // 目标角度
|
||||
private bool isRecentering = false; // 是否正在校准
|
||||
private float recenterTimer = 0.0f; // 校准剩余时间
|
||||
|
||||
public CameraRotationSubmodule(PlayerViewSubcontroller owner, float initialYaw) : base(owner)
|
||||
{
|
||||
this.cinemachineTargetYaw = initialYaw;
|
||||
@@ -29,6 +37,7 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
if (inputSc.Look.sqrMagnitude >= RotateThreshold && !lockCameraPosition)
|
||||
{
|
||||
isRecentering = false;
|
||||
float deltaTimeMultiplier = inputSc.CurrentScheme == "KeyboardMouse" ? 1.0f : Time.deltaTime;
|
||||
//if (!viewSc.lockTargetModule.isLockedMelee)
|
||||
{
|
||||
@@ -36,13 +45,26 @@ namespace Cielonos.MainGame.Characters
|
||||
cinemachineTargetPitch += inputSc.Look.y * deltaTimeMultiplier;
|
||||
}
|
||||
}
|
||||
else if (isRecentering)
|
||||
{
|
||||
recenterTimer -= Time.deltaTime;
|
||||
|
||||
if (recenterTimer <= 0)
|
||||
{
|
||||
isRecentering = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
cinemachineTargetYaw = Mathf.SmoothDampAngle(cinemachineTargetYaw, targetRecenterYaw, ref recenterVelocity, recenterSmoothTime);
|
||||
}
|
||||
}
|
||||
|
||||
cinemachineTargetYaw = MathExtensions.ClampAngle(cinemachineTargetYaw, float.MinValue, float.MaxValue);
|
||||
cinemachineTargetPitch = MathExtensions.ClampAngle(cinemachineTargetPitch, bottomClamp, topClamp);
|
||||
|
||||
viewSc.cameraTarget.rotation = Quaternion.Euler(
|
||||
viewSc.cameraRoot.rotation = Quaternion.Euler(
|
||||
cinemachineTargetPitch + cameraAngleOverride /*- viewSc.muzzleLiftModule.currentMuzzlePositionY*/, cinemachineTargetYaw, 0.0f);
|
||||
/*
|
||||
/*
|
||||
if (player.motionController.characterRotationType == PlayerMotionController.CharacterRotationType.ByAiming)
|
||||
{
|
||||
player.transform.rotation = Quaternion.Euler(0.0f, cinemachineTargetYaw, 0.0f);
|
||||
@@ -60,7 +82,43 @@ namespace Cielonos.MainGame.Characters
|
||||
cinemachineEndLockYaw = viewSc.lockingCamera.transform.eulerAngles.y;
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [新增] 触发相机战斗校准
|
||||
/// </summary>
|
||||
/// <param name="attackDirection">攻击的方向向量 (通常是 player.transform.forward)</param>
|
||||
/// <param name="duration">校准持续时间 (建议 0.2f ~ 0.5f)</param>
|
||||
public void TriggerCameraRecenter(Vector3 attackDirection, float duration = 0.2f)
|
||||
{
|
||||
if (attackDirection.sqrMagnitude < 0.01f) return;
|
||||
|
||||
// 计算目标角度
|
||||
Quaternion targetRot = Quaternion.LookRotation(attackDirection);
|
||||
targetRecenterYaw = targetRot.eulerAngles.y;
|
||||
|
||||
// 激活状态
|
||||
recenterTimer = duration;
|
||||
isRecentering = true;
|
||||
}
|
||||
|
||||
// 将此方法添加到你控制相机旋转的脚本中 (例如 PlayerCameraController)
|
||||
public void SyncRotationWithCamera(Camera camera = null)
|
||||
{
|
||||
camera ??= viewSc.playerCamera;
|
||||
Vector3 currentEuler = camera.transform.eulerAngles;
|
||||
cinemachineTargetYaw = currentEuler.y;
|
||||
float currentPitch = currentEuler.x;
|
||||
if (currentPitch > 180)
|
||||
{
|
||||
currentPitch -= 360;
|
||||
}
|
||||
cinemachineTargetPitch = currentPitch - cameraAngleOverride;
|
||||
isRecentering = false;
|
||||
// 强制执行一次旋转更新,防止下一帧才生效导致的微小跳动 (可选)
|
||||
// viewSc.cameraRoot.rotation = Quaternion.Euler(
|
||||
// cinemachineTargetPitch + cameraAngleOverride, cinemachineTargetYaw, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class LockTargetCamera : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbc378284638d9549945b8c5090f5a1b
|
||||
@@ -67,7 +67,7 @@ namespace Cielonos.MainGame.Characters
|
||||
obstructionLayer = LayerMask.GetMask("FadableEnvironment");
|
||||
targetCamera = owner.playerCamera;
|
||||
playerHead = owner.player.bodyPartsSc.head;
|
||||
playerCenter = owner.player.bodyPartsSc.centerPoint;
|
||||
playerCenter = owner.player.bodyPartsSc.flexibleCenterPoint;
|
||||
playerFeet = owner.player.bodyPartsSc.footPoint;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using DG.Tweening;
|
||||
using SLSFramework.General;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Cielonos.MainGame.Characters
|
||||
@@ -11,28 +13,102 @@ namespace Cielonos.MainGame.Characters
|
||||
public Player player => owner;
|
||||
|
||||
public Camera playerCamera;
|
||||
public Transform cameraTarget;
|
||||
public Transform cameraRoot;
|
||||
public CinemachineStateDrivenCamera stateDrivenCamera;
|
||||
public CinemachineCamera freeLookCamera;
|
||||
public CinemachineCamera lockOnCamera;
|
||||
public bool isLockedOn = false;
|
||||
public bool isLockedSetRoot;
|
||||
public CharacterBase testEnemy;
|
||||
public Transform testEnemyTarget;
|
||||
|
||||
public CameraRotationSubmodule cameraRotationSm;
|
||||
public OcclusionFadeSubmodule occlusionFadeSm;
|
||||
|
||||
public LerpFloat cameraDistance;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
cameraRotationSm = new CameraRotationSubmodule(this, player.transform.eulerAngles.y);
|
||||
occlusionFadeSm = new OcclusionFadeSubmodule(this);
|
||||
cameraDistance = new LerpFloat(10f, 1f);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
testEnemyTarget = testEnemy.bodyPartsSc.staticCenterPoint;
|
||||
//SwitchToLockTarget( testEnemyTarget );
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Keyboard.current.tabKey.wasPressedThisFrame)
|
||||
{
|
||||
if (!isLockedOn)
|
||||
{
|
||||
SwitchToLockTarget( testEnemyTarget );
|
||||
}
|
||||
else
|
||||
{
|
||||
SwitchToFreeLook();
|
||||
}
|
||||
}
|
||||
|
||||
cameraDistance.Update(player.selfTimeSm.DeltaTime);
|
||||
freeLookCamera.GetComponent<CinemachineThirdPersonFollow>().CameraDistance = cameraDistance.currentValue;
|
||||
lockOnCamera.GetComponent<CinemachinePositionComposer>().CameraDistance = cameraDistance.currentValue;
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
cameraRotationSm.Update();
|
||||
if (!isLockedOn)
|
||||
{
|
||||
cameraRotationSm.Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isLockedSetRoot) cameraRoot.LookAt(testEnemyTarget);
|
||||
float distance = (testEnemyTarget.position - cameraRoot.transform.position).Flatten().magnitude;
|
||||
if(distance < 1f) SwitchToFreeLook();
|
||||
}
|
||||
occlusionFadeSm.Update();
|
||||
}
|
||||
|
||||
void SwitchToLockTarget(Transform target)
|
||||
{
|
||||
testEnemyTarget = target;
|
||||
isLockedOn = true;
|
||||
isLockedSetRoot = false;
|
||||
|
||||
// --- CM3 核心操作 ---
|
||||
// 1. 设置 LookAt 目标。在 CM3 中,LookingAt 是 Target 结构体的一部分,或者直接赋值给 LookAt 属性
|
||||
lockOnCamera.Target.LookAtTarget = testEnemyTarget;
|
||||
|
||||
// 2. 提高优先级,激活锁定相机
|
||||
stateDrivenCamera.GetComponent<Animator>().SetBool("isLockTarget", true);
|
||||
cameraRoot.DOLookAt(testEnemyTarget.position, 0.5f).SetEase(Ease.InOutSine).OnComplete(()=>isLockedSetRoot = true).Play();
|
||||
|
||||
// (可选) 你可以在这里播放锁定音效或显示 UI 准星
|
||||
Debug.Log($"Locked on: {target.name}");
|
||||
}
|
||||
|
||||
void SwitchToFreeLook()
|
||||
{
|
||||
cameraRotationSm.SyncRotationWithCamera(playerCamera);
|
||||
|
||||
isLockedOn = false;
|
||||
|
||||
// --- CM3 核心操作 ---
|
||||
// 1. 重置 LookAt 目标
|
||||
//lockOnCamera.Target.LookAtTarget = null;
|
||||
|
||||
// 2. 降低优先级,切换回自由视角相机
|
||||
stateDrivenCamera.GetComponent<Animator>().SetBool("isLockTarget", false);
|
||||
|
||||
// (可选) 你可以在这里播放解锁音效或隐藏 UI 准星
|
||||
Debug.Log("Switched to Free Look");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,12 @@ namespace Cielonos.MainGame
|
||||
public partial class VFXData
|
||||
{
|
||||
//Runtime
|
||||
|
||||
[NonSerialized]
|
||||
private Transform executorTransform;
|
||||
[NonSerialized] private CharacterBase executor;
|
||||
[NonSerialized] private Transform executorTransform;
|
||||
|
||||
public void Initialize(CharacterBase character)
|
||||
{
|
||||
executor = character;
|
||||
executorTransform = character.transform;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Cielonos.MainGame
|
||||
VFXUnit vfxUnit = Get(vfxName);
|
||||
|
||||
Transform startTransform = overrideStartTransform ?? executorTransform;
|
||||
GameObject vfxInstance = LeanPool.Spawn(vfxUnit.mainVFX, startTransform);
|
||||
GameObject vfxInstance = VFXObject.Spawn(vfxUnit.mainVFX, executor, startTransform);
|
||||
Transform vfxTransform = vfxInstance.transform;
|
||||
|
||||
vfxTransform.localPosition = vfxUnit.localPosition;
|
||||
@@ -53,12 +53,12 @@ namespace Cielonos.MainGame
|
||||
|
||||
public GameObject SpawnMuzzleVFX(string effectName, Transform muzzleTransform)
|
||||
{
|
||||
return LeanPool.Spawn(Get(effectName).muzzleVFX, muzzleTransform);
|
||||
return VFXObject.Spawn(Get(effectName).muzzleVFX, executor, muzzleTransform);
|
||||
}
|
||||
|
||||
public GameObject SpawnHitVFX(string effectName, Vector3 hitPosition)
|
||||
{
|
||||
return LeanPool.Spawn(Get(effectName).muzzleVFX, hitPosition, Quaternion.identity);
|
||||
return VFXObject.Spawn(Get(effectName).muzzleVFX, executor, hitPosition, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
87
Assets/Scripts/MainGame/Effects/VFXObject.cs
Normal file
87
Assets/Scripts/MainGame/Effects/VFXObject.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Characters;
|
||||
using Lean.Pool;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSFramework.LeanPoolAssistance;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame
|
||||
{
|
||||
public class VFXObject : PooledObject
|
||||
{
|
||||
[NonSerialized]
|
||||
public CharacterBase creator;
|
||||
|
||||
public List<ParticleSystem> particles = new List<ParticleSystem>();
|
||||
|
||||
[NonSerialized]
|
||||
public List<ParticleSystem.MainModule> particleMainModules = new List<ParticleSystem.MainModule>();
|
||||
|
||||
public static GameObject Spawn(GameObject vfxPrefab, CharacterBase creator, Transform parent = null)
|
||||
{
|
||||
VFXObject vfxObject = LeanPool.Spawn(vfxPrefab, parent).GetComponent<VFXObject>();
|
||||
vfxObject.SetCreator(creator);
|
||||
return vfxObject.gameObject;
|
||||
}
|
||||
|
||||
public static GameObject Spawn(GameObject vfxPrefab, CharacterBase creator, Vector3 position, Quaternion rotation, Transform parent = null)
|
||||
{
|
||||
VFXObject vfxObject = LeanPool.Spawn(vfxPrefab, position, rotation, parent).GetComponent<VFXObject>();
|
||||
vfxObject.SetCreator(creator);
|
||||
return vfxObject.gameObject;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
CollectParticles();
|
||||
}
|
||||
|
||||
public override void OnSpawn()
|
||||
{
|
||||
if (spawnExecuted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnSpawn();
|
||||
particleMainModules = new List<ParticleSystem.MainModule>();
|
||||
foreach (var ps in particles)
|
||||
{
|
||||
particleMainModules.Add(ps.main);
|
||||
}
|
||||
}
|
||||
|
||||
[Button("Collect Particles")]
|
||||
private void CollectParticles()
|
||||
{
|
||||
particles.Clear();
|
||||
|
||||
ParticleSystem[] foundParticles = GetComponentsInChildren<ParticleSystem>();
|
||||
foreach (var ps in foundParticles)
|
||||
{
|
||||
particles.Add(ps);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
float deltaTime = Time.deltaTime;
|
||||
float timeScale = 1f;
|
||||
|
||||
if (creator != null)
|
||||
{
|
||||
deltaTime = creator.selfTimeSm.DeltaTime;
|
||||
timeScale = creator.selfTimeSm.timeScaleCoefficient.Value;
|
||||
}
|
||||
|
||||
UpdateTimer(deltaTime);
|
||||
particleMainModules.ForEach(main => main.simulationSpeed = timeScale);
|
||||
}
|
||||
|
||||
public void SetCreator(CharacterBase character)
|
||||
{
|
||||
creator = character;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/MainGame/Effects/VFXObject.cs.meta
Normal file
2
Assets/Scripts/MainGame/Effects/VFXObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dc3f30af6da99e4aa3cece3b17619f8
|
||||
@@ -1,6 +1,7 @@
|
||||
using Cielonos.MainGame.Characters;
|
||||
using Cielonos.MainGame.Characters.Buffs;
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Inventory
|
||||
@@ -31,6 +32,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
functionSm["TripleAttack"].Execute();
|
||||
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(5);
|
||||
PlayTargetedAnimation("TripleAttack", target, 1f);
|
||||
//player.viewSc.cameraRotationSm.TriggerCameraRecenter(player.transform.forward);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
functionSm["LightAttack"].Execute();
|
||||
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(8);
|
||||
PlayTargetedAnimation("RunAttack", target, 1f);
|
||||
//player.viewSc.cameraRotationSm.TriggerCameraRecenter(player.transform.forward);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,6 +52,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
functionSm["LightAttack"].Execute();
|
||||
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(5);
|
||||
PlayTargetedAnimation("LightAttack" + comboSm.GetCurrentNodeName(), target, 1f);
|
||||
//player.viewSc.cameraRotationSm.TriggerCameraRecenter(player.transform.forward);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +65,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
functionSm["HeavyAttack"].Execute();
|
||||
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(5);
|
||||
PlayTargetedAnimation("ParryAttack", target, 1f);
|
||||
//player.viewSc.cameraRotationSm.TriggerCameraRecenter(player.transform.forward);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,6 +76,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
functionSm["DisruptAttack"].Execute();
|
||||
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(5);
|
||||
PlayTargetedAnimation("DisruptAttack", target, 1f);
|
||||
//player.viewSc.cameraRotationSm.TriggerCameraRecenter(player.transform.forward);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,6 +86,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
functionSm["HeavyAttack"].Execute();
|
||||
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(5);
|
||||
PlayTargetedAnimation("HeavyAttack", target, 1f);
|
||||
//player.viewSc.cameraRotationSm.TriggerCameraRecenter(player.transform.forward);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +148,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
.AddHitEvent((enemy, hitPosition) =>
|
||||
{
|
||||
feedbackSc["NormalHit"].Play();
|
||||
player.selfTimeSm.ModifyTimeScale(0.06f);
|
||||
enemy.selfTimeSm.ModifyTimeScale(0.06f);
|
||||
new ElectronicDisturbance(2).Apply(enemy);
|
||||
});
|
||||
|
||||
@@ -164,6 +173,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
.AddHitEvent((enemy, hitPosition) =>
|
||||
{
|
||||
feedbackSc["NormalHit"].Play();
|
||||
player.selfTimeSm.ModifyTimeScale(0.06f);
|
||||
enemy.selfTimeSm.ModifyTimeScale(0.06f);
|
||||
new ElectronicDisturbance(2).Apply(enemy);
|
||||
|
||||
if (enemy.statusSm.HasStatus(StatusType.Incapacitation))
|
||||
@@ -183,7 +194,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
|
||||
slash.Initialize<NormalArea>(player, this, Fraction.Enemy)
|
||||
.SetAttackSubmodule<NormalArea>(attackData["HeavyAttack"])
|
||||
.SetTimeSubmodule<NormalArea>(1f, 0.04f)
|
||||
.SetTimeSubmodule<NormalArea>(1f, 0.04f, 0.16f)
|
||||
.SetHitSubmodule<NormalArea>()
|
||||
.SetForceSubmodule<NormalArea>(3f, true);
|
||||
|
||||
@@ -192,6 +203,9 @@ namespace Cielonos.MainGame.Inventory
|
||||
.AddHitEvent((enemy, hitPosition) =>
|
||||
{
|
||||
feedbackSc["HeavyHit"].Play();
|
||||
player.selfTimeSm.ModifyTimeScale(0.12f);
|
||||
enemy.selfTimeSm.ModifyTimeScale(0.12f);
|
||||
player.viewSc.cameraDistance.currentValue -= 2f;
|
||||
new ElectronicDisturbance(5).Apply(enemy);
|
||||
});
|
||||
|
||||
@@ -215,6 +229,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
.AddHitEvent((enemy, hitPosition) =>
|
||||
{
|
||||
feedbackSc["HeavyHit"].Play();
|
||||
player.selfTimeSm.ModifyTimeScale(0.12f);
|
||||
enemy.selfTimeSm.ModifyTimeScale(0.12f);
|
||||
new ElectronicDisturbance(5).Apply(enemy);
|
||||
});
|
||||
|
||||
@@ -238,6 +254,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
.AddHitEvent((enemy, hitPosition) =>
|
||||
{
|
||||
feedbackSc["HeavyHit"].Play();
|
||||
player.selfTimeSm.ModifyTimeScale(0.12f);
|
||||
enemy.selfTimeSm.ModifyTimeScale(0.12f);
|
||||
new ElectronicDisturbance(5).Apply(enemy);
|
||||
});
|
||||
|
||||
@@ -262,6 +280,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
.AddHitEvent((enemy, hitPosition) =>
|
||||
{
|
||||
feedbackSc["NormalHit"].Play();
|
||||
player.selfTimeSm.ModifyTimeScale(0.06f);
|
||||
enemy.selfTimeSm.ModifyTimeScale(0.06f);
|
||||
new ElectronicDisturbance(2).Apply(enemy);
|
||||
});
|
||||
|
||||
|
||||
@@ -36,10 +36,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
public void Setup(float disappearTime = -1)
|
||||
{
|
||||
disappearTime = disappearTime <= 0 ? defaultDisappearTime : disappearTime;
|
||||
|
||||
comboTimer = Observable.Timer(TimeSpan.FromSeconds(disappearTime))
|
||||
.First()
|
||||
.Subscribe(_ => { comboTree.Reset(); });
|
||||
comboTimer = owner.player.selfTimeSm.AddLocalTimer(disappearTime, () => comboTree.Reset());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,9 +46,7 @@ namespace Cielonos.MainGame.Inventory
|
||||
{
|
||||
comboTimer?.Dispose();
|
||||
suspender?.Dispose();
|
||||
suspender = Observable.Timer(TimeSpan.FromSeconds(duration))
|
||||
.First()
|
||||
.Subscribe(_ => Setup());
|
||||
suspender = owner.player.selfTimeSm.AddLocalTimer(duration, () => Setup());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lean.Pool;
|
||||
@@ -13,12 +14,14 @@ namespace SLSFramework.LeanPoolAssistance
|
||||
|
||||
[ShowIf("isAutoDespawn")][Tooltip("自动回收时间")]
|
||||
public float autoDespawnTime = 1;
|
||||
|
||||
[ShowIf("isAutoDespawn")][Tooltip("自动回收计时器")][ReadOnly][HideInEditorMode]
|
||||
public float despawnTimer;
|
||||
|
||||
private List<IPoolable> children;
|
||||
|
||||
private bool spawnExecuted = false;
|
||||
protected bool spawnExecuted = false;
|
||||
|
||||
public void OnSpawn()
|
||||
public virtual void OnSpawn()
|
||||
{
|
||||
if (spawnExecuted)
|
||||
{
|
||||
@@ -26,14 +29,30 @@ namespace SLSFramework.LeanPoolAssistance
|
||||
}
|
||||
|
||||
spawnExecuted = true;
|
||||
despawnTimer = 0;
|
||||
|
||||
children = GetComponentsInChildren<IPoolable>().ToList();
|
||||
children.Remove(this);
|
||||
children.ForEach(child => child.OnSpawn());
|
||||
|
||||
if (isAutoDespawn)
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
UpdateTimer(Time.deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void UpdateTimer(float deltaTime)
|
||||
{
|
||||
if (!isAutoDespawn)
|
||||
{
|
||||
LeanPool.Despawn(gameObject, autoDespawnTime);
|
||||
return;
|
||||
}
|
||||
|
||||
despawnTimer += deltaTime;
|
||||
|
||||
if (despawnTimer >= autoDespawnTime)
|
||||
{
|
||||
LeanPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,8 @@ namespace SLSUtilities.FunctionalAnimation
|
||||
ActionDisruption = 30,
|
||||
[InspectorName("移动打断窗口 (Movement Disruption)")]
|
||||
MovementDisruption = 31,
|
||||
[InspectorName("强制动作打断窗口 (Forced Action Disruption) 如果没有此区间,默认整个动作都可被强制打断")]
|
||||
ForcedActionDisruption = 32,
|
||||
[InspectorName("Root Motion 作用区间,角色会使用动画中的位移")]
|
||||
RootMotion = 40,
|
||||
[InspectorName("自定义 (Custom)")]
|
||||
|
||||
Reference in New Issue
Block a user