public class ComboManager : MonoBehaviour
{
private ComboConfig config;
private ComboState state;
// 输入缓冲队列
private Queue<BufferedInput> inputBuffer = new Queue<BufferedInput>();
void Update()
{
// 1. 收集输入并加入缓冲
CaptureInput();
// 2. 处理缓冲的输入
ProcessBufferedInputs();
// 3. 检查连锁窗口是否过期
UpdateChainWindow();
// 4. 清理过期缓冲
CleanupBuffer();
}
private void CaptureInput()
{
if (Input.GetButtonDown("LightAttack"))
{
BufferInput(new BufferedInput
{
inputType = InputType.LightAttack,
timestamp = Time.time,
direction = GetInputDirection()
});
}
if (Input.GetButtonDown("HeavyAttack"))
{
BufferInput(new BufferedInput
{
inputType = InputType.HeavyAttack,
timestamp = Time.time,
direction = GetInputDirection()
});
}
}
private void BufferInput(BufferedInput input)
{
inputBuffer.Enqueue(input);
Debug.Log($"[Combo] 缓冲输入: {input.inputType}");
}
private void ProcessBufferedInputs()
{
if (inputBuffer.Count == 0) return;
var input = inputBuffer.Peek();
// 检查是否在连锁窗口内
if (IsInChainWindow() && CanChainToNext(input))
{
// 执行连招下一步
ExecuteComboNode(input);
inputBuffer.Dequeue();
}
else if (Time.time - input.timestamp > config.inputBufferDuration)
{
// 缓冲过期,尝试作为新连招起手
if (TryStartNewCombo(input))
{
inputBuffer.Dequeue();
}
else
{
// 彻底过期,丢弃
inputBuffer.Dequeue();
Debug.Log("[Combo] 输入过期");
}
}
}
private bool CanChainToNext(BufferedInput input)
{
if (state.currentNode == null) return false;
// 检查当前节点是否有匹配的下一步
foreach (var nextIndex in state.currentNode.nextNodeIndices)
{
var nextNode = config.comboChains[state.currentChainIndex].nodes[nextIndex];
if (InputMatches(input, nextNode.inputCondition))
{
state.nextNodeIndex = nextIndex;
return true;
}
}
return false;
}
private void ExecuteComboNode(BufferedInput input)
{
var chain = config.comboChains[state.currentChainIndex];
var node = chain.nodes[state.nextNodeIndex];
// 1. 播放动画
animator.Play(node.animation.name);
// 2. 增加连击计数
state.comboCount++;
state.comboCount = Mathf.Min(state.comboCount, chain.maxComboCount);
// 3. 计算伤害加成
float damageMultiplier = Mathf.Pow(chain.damageMultiplierPerHit, state.comboCount - 1);
// 4. 执行攻击
PerformAttack(node.attackData, damageMultiplier);
// 5. 更新状态
state.currentNode = node;
state.currentNodeIndex = state.nextNodeIndex;
state.chainWindowStartTime = Time.time + node.chainWindowStart;
state.chainWindowEndTime = Time.time + node.chainWindowEnd;
// 6. UI 反馈
OnComboHit?.Invoke(state.comboCount, damageMultiplier);
Debug.Log($"[Combo] 连击 {state.comboCount}: {node.animation.name} (伤害 x{damageMultiplier:F2})");
}
private bool IsInChainWindow()
{
float time = Time.time;
return time >= state.chainWindowStartTime && time <= state.chainWindowEndTime;
}
public bool TryCancelInto(string actionName)
{
if (state.currentNode == null) return false;
// 查找取消规则
foreach (var rule in config.cancelRules)
{
if (rule.fromActionName == state.currentNode.animation.name &&
rule.toActionName == actionName)
{
// 检查取消条件
if (!CheckCancelCondition(rule.condition))
return false;
// 检查是否在取消窗口内
float actionProgress = GetCurrentActionProgress();
if (actionProgress < rule.cancelWindowStart || actionProgress > rule.cancelWindowEnd)
return false;
// 检查资源
if (rule.requiresResource && !ConsumeResource(rule.resourceCost))
return false;
// 执行取消
PerformCancel(actionName);
return true;
}
}
return false;
}
private void ResetCombo(string reason)
{
if (state.comboCount > 0)
{
Debug.Log($"[Combo] 连招中断: {reason} (最高 {state.comboCount} 连击)");
OnComboBreak?.Invoke(state.comboCount);
}
state.Reset();
inputBuffer.Clear();
}
// 事件
public System.Action<int, float> OnComboHit; // (连击数, 伤害倍率)
public System.Action<int> OnComboBreak; // (最高连击数)
}
[System.Serializable]
public class ComboState
{
public int currentChainIndex = 0;
public int currentNodeIndex = -1;
public int nextNodeIndex = -1;
public ComboNode currentNode = null;
public int comboCount = 0;
public float chainWindowStartTime = 0f;
public float chainWindowEndTime = 0f;
public void Reset()
{
currentChainIndex = 0;
currentNodeIndex = -1;
nextNodeIndex = -1;
currentNode = null;
comboCount = 0;
chainWindowStartTime = 0f;
chainWindowEndTime = 0f;
}
}
public struct BufferedInput
{
public InputType inputType;
public float timestamp;
public Vector2 direction;
}