204 lines
6.7 KiB
C#
204 lines
6.7 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Lean.Pool;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame
|
||
{
|
||
public partial class TimeSubmodule : AttackAreaSubmoduleBase
|
||
{
|
||
public float lifeTime;
|
||
public float delayTime;
|
||
public float enablingTimer;
|
||
public float remainingEnableTime;
|
||
public float remainingLifeTime;
|
||
private bool hasInvokedEnableAction;
|
||
public Action enableAction;
|
||
public Action timeOutAction;
|
||
public List<ScheduledAction> scheduledActions;
|
||
private List<ScheduledAction> toBeExecutedScheduledActions = new List<ScheduledAction>();
|
||
|
||
/// <summary>
|
||
/// enable 阶段开始前允许反应的提前量(秒),默认 0 表示无提前 grace window。
|
||
/// </summary>
|
||
public float reactionGraceBefore;
|
||
|
||
|
||
public TimeSubmodule(AttackAreaBase attackArea, float lifeTime, Action timeOutAction = null) : base(attackArea)
|
||
{
|
||
this.isEnabling = true;
|
||
this.lifeTime = lifeTime;
|
||
this.remainingLifeTime = lifeTime;
|
||
this.enablingTimer = 0;
|
||
this.scheduledActions = new List<ScheduledAction>();
|
||
|
||
if (attackArea is NormalArea)
|
||
{
|
||
this.remainingEnableTime = 0.04f;
|
||
}
|
||
else if (attackArea is Projectile)
|
||
{
|
||
this.remainingEnableTime = lifeTime;
|
||
}
|
||
else
|
||
{
|
||
this.remainingEnableTime = lifeTime;
|
||
}
|
||
|
||
this.timeOutAction = timeOutAction ?? (() =>
|
||
{
|
||
if (attackArea is Projectile projectile)
|
||
{
|
||
projectile.Explode(projectile.transform.position);
|
||
}
|
||
else
|
||
{
|
||
LeanPool.Despawn(attackArea.topParent.gameObject);
|
||
}
|
||
});
|
||
|
||
this.reactionGraceBefore = 0f;
|
||
}
|
||
|
||
public TimeSubmodule(AttackAreaBase attackArea, float lifeTime, float delayTime, float enableTime,
|
||
Action enableAction, Action timeOutAction, float graceBefore = 0f) : base(attackArea)
|
||
{
|
||
this.isEnabling = true;
|
||
this.lifeTime = lifeTime;
|
||
this.delayTime = delayTime;
|
||
this.attackArea.isEnabling = delayTime <= 0;
|
||
this.scheduledActions = new List<ScheduledAction>();
|
||
|
||
this.timeOutAction = timeOutAction ?? (() =>
|
||
{
|
||
if (attackArea is Projectile projectile)
|
||
{
|
||
projectile.Explode(projectile.transform.position);
|
||
}
|
||
else
|
||
{
|
||
LeanPool.Despawn(attackArea.topParent.gameObject);
|
||
}
|
||
});
|
||
if (this.attackArea.isEnabling)
|
||
{
|
||
enableAction?.Invoke();
|
||
hasInvokedEnableAction = true;
|
||
}
|
||
this.remainingLifeTime = lifeTime;
|
||
this.enablingTimer = 0;
|
||
this.remainingEnableTime = enableTime;
|
||
this.enableAction = enableAction;
|
||
|
||
this.reactionGraceBefore = graceBefore;
|
||
}
|
||
|
||
public TimeSubmodule AddScheduleAction(Action action, float delay)
|
||
{
|
||
scheduledActions.Add(new ScheduledAction(action, delay));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断当前时刻是否处于反应窗口内(包含 grace 区间和 enable 阶段本身)。
|
||
/// before grace: delay 阶段末尾的 reactionGraceBefore 秒内。
|
||
/// after grace: enable 结束后的 reactionGraceAfter 秒内。
|
||
/// </summary>
|
||
public bool IsReactionActive()
|
||
{
|
||
// 在 enable 阶段本身,反应始终可用
|
||
if (attackArea.isEnabling)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// before grace: delay 尚未结束,但已进入 grace 窗口
|
||
if (delayTime > 0f && reactionGraceBefore > 0f && delayTime <= reactionGraceBefore)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 过渡帧桥接:delay 刚结束(delayTime 被扣至 <=0),但 enable 阶段尚未在下一帧
|
||
// 的 timeSm.Update() 中激活。此帧 isEnabling 为 false 且 delayTime 不再 >0,
|
||
// 若不补此条件会导致 1 帧的反应窗口真空,玩家的完美闪避/格挡可能被跳过。
|
||
if (reactionGraceBefore > 0f && delayTime <= 0f && !hasInvokedEnableAction)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public partial class TimeSubmodule
|
||
{
|
||
public void Update()
|
||
{
|
||
if (!isEnabling) return;
|
||
|
||
if (delayTime > 0)
|
||
{
|
||
delayTime -= attackArea.creator.selfTimeSm.DeltaTime;
|
||
return;
|
||
}
|
||
|
||
if (!hasInvokedEnableAction)
|
||
{
|
||
attackArea.isEnabling = true;
|
||
enableAction?.Invoke();
|
||
hasInvokedEnableAction = true;
|
||
}
|
||
|
||
toBeExecutedScheduledActions.Clear();
|
||
foreach (var scheduledAction in scheduledActions)
|
||
{
|
||
scheduledAction.delay -= attackArea.creator.selfTimeSm.DeltaTime;
|
||
if (scheduledAction.delay <= 0)
|
||
{
|
||
toBeExecutedScheduledActions.Add(scheduledAction);
|
||
}
|
||
}
|
||
foreach (var scheduledAction in toBeExecutedScheduledActions)
|
||
{
|
||
scheduledAction.action.Invoke();
|
||
scheduledActions.Remove(scheduledAction);
|
||
}
|
||
|
||
if (remainingLifeTime <= 0)
|
||
{
|
||
this.isEnabling = false;
|
||
timeOutAction?.Invoke();
|
||
}
|
||
|
||
if (remainingEnableTime <= 0)
|
||
{
|
||
attackArea.isEnabling = false;
|
||
}
|
||
|
||
enablingTimer += attackArea.creator.selfTimeSm.DeltaTime;
|
||
remainingLifeTime -= attackArea.creator.selfTimeSm.DeltaTime;
|
||
remainingEnableTime -= attackArea.creator.selfTimeSm.DeltaTime;
|
||
}
|
||
|
||
public void ModifyLifeTime(float modifyValue)
|
||
{
|
||
lifeTime += modifyValue;
|
||
remainingLifeTime += modifyValue;
|
||
}
|
||
}
|
||
|
||
public partial class TimeSubmodule
|
||
{
|
||
public class ScheduledAction
|
||
{
|
||
public Action action;
|
||
public float delay;
|
||
|
||
public ScheduledAction(Action action, float delay)
|
||
{
|
||
this.action = action;
|
||
this.delay = delay;
|
||
}
|
||
}
|
||
}
|
||
} |