← ALL ARTICLES

输入与动作状态

全库目录221
本文目录13

动作游戏框架要把设备输入、玩家意图、动作规则、移动和表现拆开。直接在更新中读取按键并播放动画,短期很快,加入 AI、回放、网络和连招后会立刻失控。

输入与动作规则

设备输入与语义命令

输入设备只负责读取键盘、手柄或触摸的原始值。输入映射把它转换为 Jump、Attack、移动等语义动作。输入缓冲保存带时间戳的按下、释放和方向信息,为连招、土狼时间和网络重放提供依据。

输入采样与逻辑消费可能处于不同频率。一次短按必须被缓存到至少一个逻辑 Tick,而不是依赖两个更新恰好碰上。模拟摇杆需要死区、归一化和响应曲线;方向相对相机时,应把输入向量投影到水平面的相机基向量。

AI、玩家和网络回放都可以产生同一类命令。这样动作层只处理意图,不关心来源。

搓招识别把最近一段输入缓冲与有序模式匹配。模式可包含方向、按键、按住/松开、最大帧间隔和可接受的简化输入;匹配通常从最新输入向前回溯,优先选择更长、更具体的指令。识别结果应生成语义命令,再由请求层判断当前资源、状态和取消窗口,不能在输入层直接发动技能。

输入缓冲与土狼时间

cpp
bool TryJump(Character& c, InputBuffer& buffer, float now)
{
    auto press = buffer.FindPress(Action::Jump, now - jumpBufferTime, now);
    bool grounded = c.hasGroundedHistory &&
                    c.lastGroundedTime <= now &&
                    c.lastGroundedTime >= now - coyoteTime;
    if (press && grounded && !c.jumpUsedSinceGrounded) {
        buffer.Consume(*press);
        c.jumpUsedSinceGrounded = true; // 由新的有效着地恢复为false
        c.StartJump();
        return true;
    }
    return false;
}

两个时间窗补偿两类输入时机:jumpBufferTime(按项目配置)补偿“按早了几帧”,coyoteTime(按项目配置)补偿“走出台阶边沿才按”。Consume 很关键:一次按压只触发一次跳跃,否则缓冲窗口内会连跳。验证时以略高于和略低于窗口边界的时间差各注入一排测试帧,边界两侧的判定必须稳定且符合预期。

这是依赖角色接触状态与输入缓冲的C++机制片段。时间单位为秒,now与事件时间使用同一单调逻辑时间轴,两个窗口均非负。FindPress接收动作、起始时刻与结束时刻,在闭区间内按时间及稳定事件ID选择最早的未消费按下事件,返回可选事件句柄;Consume仅消费该句柄。未来事件保留到对应逻辑步。

角色生成时hasGroundedHistory为false。每个逻辑步确认有效接地后,都将它设为true并刷新lastGroundedTime;只有首次有效着地或离地后的新着地才恢复跳跃许可。起跳后残留的接触报告保持许可已消费,直到确认离地并再次着地。这样站立两秒后按跳跃仍可使用刚刷新的接地时间,空中生成者则先取得真实地面历史。

输入缓冲保存一次按下事件,土狼时间保存最近一次有效地面接触,两者同时满足才可能起跳。但起跳以后,最近接地时间仍在窗口内;若又收到第二次按下,仅消费输入无法防止再次使用同一份地面资格。因此角色还要保存本次离地是否已消费跳跃许可,重新有效着地后才恢复。

按时间线看,离开台阶后50毫秒按下跳跃可在100毫秒土狼窗口内起跳;再过20毫秒重复按下,地面许可已被消费,应拒绝。相反,落地前50毫秒预按,在100毫秒输入缓冲内落地,可以由新地面许可消费一次。两个窗口与许可状态分别记录,才能解释拒绝来自哪一条规则。

网络或回放以逻辑Tick消费语义命令。显示帧只采集输入边沿,不能每次渲染都扣资源;动作开始事件使用动作实例ID,重放相同实例时避免重复播放不可逆表现。输入、规则与动画分层的意义在这条时序上体现。

请求层与行为层

请求层检查“想做什么”,例如请求攻击、闪避或切换武器。行为层决定“当前能否做、开始后如何推进”。请求可以被接受、排队、拒绝或替换,并记录原因。

状态机保存 Idle、移动、Attack、Hit 等互斥状态。层次状态机可以让 Grounded 和 Airborne 共享上层规则。多个可并行维度不必硬塞进一张状态图,例如移动、上半身动作和装备可以使用独立层,再通过明确约束协调。

取消窗口是动作数据,不应散落在动画事件回调中。动作定义可以包含前摇、有效帧、后摇、可取消目标、资源消耗和根运动策略。动画事件适合通知表现时刻,权威命中与资源扣除仍由玩法状态控制。

移动与状态解耦

移动模块处理加速度、转向、重力、地面和平台。动作状态给出移动约束或目标速度,而不是自行修改变换。根运动需要明确是权威位移、建议位移还是纯表现。

角色跳跃、落地、坡面和碰撞通常运行在固定时间步。相机和动画可在渲染帧插值。把同一速度在更新与固定步更新重复积分是常见错误。

分层框架

一种实用分层是:

  1. 信息层保存可查询的世界与角色状态;
  2. 输入层产生语义命令;
  3. 请求层做规则入口和优先级;
  4. 行为层推进状态与动作;
  5. 表现层驱动动画、音频、VFX 和相机。

分层不等于每层都需要抽象基类。稳定的数据契约和单向依赖更重要。跨层共享临时布尔值会让状态重新耦合。

模拟与显示

屏幕输入与坐标转换

指针事件给出窗口或屏幕坐标,输入映射先按视口、缩放与Y轴约定转换,再交给目标选择或动作命令。精灵的枢轴、缩放与投影变换见 空间变换。输入层提交选择意图,渲染矩阵由表现层消费,二者分别追踪。

最小游戏循环

一个 2D 小游戏也需要区分输入采样、固定步长模拟和显示帧。下面的 C++ 伪代码把真实帧时间累积起来,模拟阶段只消费固定步长:

cpp
double accumulator = 0.0;

while (!window.ShouldClose()) {
    double frameTime = Clamp(clock.Tick(), 0.0, maxFrameTime);
    accumulator += frameTime;
    input.Poll(window);

    while (accumulator >= fixedDelta) {
        CommandFrame commands = input.ConsumeForTick();
        game.FixedUpdate(commands, fixedDelta);
        accumulator -= fixedDelta;
    }

    renderer.Draw(game, accumulator / fixedDelta);
    window.Present();
}

Clamp 防止断点或窗口拖动产生一次超长模拟。ConsumeForTick 要保留短按,避免渲染帧和逻辑帧频率不同造成输入丢失。最后的比例用于表现插值,不参与权威状态更新。关卡加载、碰撞、粒子、音频和 UI 都应从明确的游戏状态读取结果,而不是各自维护另一份胜负状态。

ConsumeForTick按事件时间映射到的逻辑步消费输入边沿。同一显示帧追赶多个逻辑步时,按下事件只进入所属步,持续按住状态在后续步保持。复现保存按逻辑步组织的命令序列,再以不同显示帧率重放。

宿主保存最近两份已完成模拟状态,按累加器余量与固定步长的比值插值。Clamp截去的墙钟时间单独计入暂停或降速记录;联网权威模拟的追帧与失步策略见刚体物理求解。

数据驱动与调试

动作参数适合资产化,并通过验证器检查时间范围、引用、取消图和资源消耗。运行时应能显示最近输入、请求处理结果、当前状态、剩余时间和状态跳转原因。

回放一段命令流可以复现大部分框架错误。加入随机帧率、暂停、切换控制者和场景重载测试,能暴露时序与生命周期问题。

验证方法

  • 输入丢失回归:脚本在随机时刻注入 1 帧短按,跑上千次确认全部转化为语义命令;丢一次就说明缓冲没有跨逻辑 Tick 保留。
  • 搓招优先级表:构造同时匹配多个模式的输入序列,确认更长更具体的指令获胜;结果写成用例表随动作数据回归。
  • 取消窗口校验:验证器检查每个动作的取消窗口不超出动作时长、目标动作存在、资源消耗非负。
  • 状态机穷尽测试:对每个状态枚举全部请求类型,确认接受、排队、拒绝行为与状态表一致;被拒绝的请求必须带原因供回放。
  • 帧率独立性:固定输入序列在 30/60/120 fps 各跑一次,权威状态(位置、血量、动作进度)应逐 Tick 一致;表现层差异不参与比较。
  • 回放复现:录制真实玩家的命令流并重放,确认终局状态哈希一致;不一致先查随机数来源与浮点求值顺序。

实现与证据

正文跳跃和循环为C++机制片段,地面许可消耗边界配有手工时间线。片段验证从当前正文提取TryJump,以Clang 22.1.8、C++17编译隔离宿主并运行8组输入,覆盖长时间站立后的接地快照、空中生成、未来事件与接触、重复许可、超窗、边界和事件精确消费。宿主提供最小缓冲与角色状态,真实接地生成及完整控制器尚未运行;窗口数值是说明例子。

相关主题

参考资料