【ゲーム制作】入力バッファリング(先行入力)とは?|押したのに反応しない問題を解決する

input buffer thumbnail ゲーム制作

「着地する少し前にジャンプキーを押したのに反応しない…これって仕様?」「先行入力って格闘ゲームでよく聞くけど、どういう仕組みなの?」「IsTriggerだけじゃ足りないの?」

こんな疑問はありませんか?

今回は、アクションゲームの操作性を大きく左右する入力バッファリング(先行入力)について、実際にコンパイル・実行した結果を交えながら解説していきます。「押したのに反応しない」を実際に再現してから、その解決策を見ていきます。

入力バッファリングとは?

入力バッファリングとは、

・押されたキー入力を一定フレームの間だけ「記憶」しておき、後から条件が揃ったタイミングで改めて使えるようにする仕組み

です。トリガー入力だけを使うと「押した瞬間」しか反応できませんが、実際のプレイヤーは「地面に着く直前」のようにタイミングがシビアな場面で、少し早めにボタンを押してしまうことがよくあります。これを毎回「反応しない」で切り捨てると、操作がシビアで気持ちよくないゲームになってしまいます。

【実験】バッファリング無しだとどうなるか

プレイヤーが空中にいるframe 10でジャンプキーを押し、frame 13で実際に着地する、という状況を再現してみます。まずはバッファリング無しの版です。

bool jumped = false;
if (pressedThisFrame && isGrounded) { // 押した瞬間、かつ地面にいる場合だけジャンプ
    jumped = true;
}

実行結果です。

=== NG: バッファリング無し(着地した瞬間しかジャンプを受け付けない) ===
frame  8: grounded=0 pressed=0 -> -
frame  9: grounded=0 pressed=0 -> -
frame 10: grounded=0 pressed=1 -> -
frame 11: grounded=0 pressed=0 -> -
frame 12: grounded=0 pressed=0 -> -
frame 13: grounded=1 pressed=0 -> -
frame 14: grounded=1 pressed=0 -> -
frame 15: grounded=1 pressed=0 -> -

frame 10でボタンは確かに押されているのに、その時点ではまだ空中(grounded=0)なので弾かれ、着地するframe 13にはもうボタンが押されていないため、結局最後までジャンプが1回も発生しません。プレイヤー視点では「ちゃんと押したのに反応しなかった」という、理不尽な操作ミス扱いになってしまいます。

入力バッファリングを実装してみる

押された入力を「何フレーム前まで」なら有効とみなすか、というバッファ期間を持つクラスを実装します。

class InputBuffer {
public:
    static const int BUFFER_FRAMES = 6; // このフレーム数だけ「押した」を覚えておく

    void Record(int currentFrame) {
        s_PressedFrame = currentFrame;
    }

    // まだ有効期限内の入力が残っているか(currentFrame時点で判定)
    bool IsBuffered(int currentFrame) const {
        if (s_PressedFrame < 0) return false;
        return (currentFrame - s_PressedFrame) <= BUFFER_FRAMES;
    }

    // 使ったら消す(1回だけ消費させるため)
    void Consume() {
        s_PressedFrame = -1;
    }

private:
    int s_PressedFrame = -1;
};

ボタンを押した瞬間はRecordで「いつ押されたか」を記録するだけにとどめ、実際にジャンプさせるかどうかの判定は着地判定側に任せます。

if (pressedThisFrame) {
    jumpBuffer.Record(frame); // 押した瞬間はとりあえず記録するだけ
}

bool jumped = false;
if (isGrounded && jumpBuffer.IsBuffered(frame)) {
    jumped = true;
    jumpBuffer.Consume();
}

同じ状況(frame 10でボタン、frame 13で着地)で実行した結果です。

=== OK: 入力バッファリングあり(先行入力を着地まで覚えておく) ===
frame  8: grounded=0 pressed=0 buffered=0 -> -
frame  9: grounded=0 pressed=0 buffered=0 -> -
frame 10: grounded=0 pressed=1 buffered=1 -> -
frame 11: grounded=0 pressed=0 buffered=1 -> -
frame 12: grounded=0 pressed=0 buffered=1 -> -
frame 13: grounded=1 pressed=0 buffered=0 -> ジャンプ!
frame 14: grounded=1 pressed=0 buffered=0 -> -
frame 15: grounded=1 pressed=0 buffered=0 -> -

frame 10で押されたボタンの記憶がframe 11・12でもbuffered=1として残り続け、frame 13で着地した瞬間にちゃんとジャンプが発動しました。プレイヤーからすれば、少し早めに押しただけなのに、狙い通りにジャンプできたように感じられます。

入力バッファリングのよくある失敗例と対処法

①バッファ期間を決めずにトリガー判定だけで済ませる

上で実演した通りです。特にジャンプ・回避のようにタイミングがシビアな操作には、数フレーム分のバッファリングを検討しましょう。

②使った入力をConsumeし忘れて何度も発動する

ジャンプが成立した後もConsume()を呼び忘れると、バッファ期間が切れるまで毎フレームジャンプ判定が成立し続け、1回の入力で連続ジャンプしてしまいます。使ったら必ず消費しましょう。

③バッファ期間を長くしすぎる

便利だからといってバッファ期間を極端に長くすると、「だいぶ前に押したボタン」が予想外のタイミングで発動し、逆に操作の意図が伝わりにくくなります。数フレーム(60FPSなら3〜8フレーム程度)を目安に、実際にプレイして調整するのが安全です。

注意点

  • 押した瞬間は記録するだけ、実際の発動判定は使う側で行う
  • 発動したら必ずConsumeして消費する
  • バッファ期間は長すぎず短すぎず、実プレイで調整する

まとめ

  • 入力バッファリングは押された入力を数フレーム覚えておく仕組み
  • バッファリング無しだと「押したのに反応しない」が実際に起きることを確認した
  • バッファリングありだと、条件が揃った瞬間に先行入力が発動する
  • 発動後は必ずConsumeで消費する

「反応が悪い」と感じるゲームの多くは、実はプレイヤーの反射神経ではなく、こうしたバッファの有無が原因だったりします。数フレームの余裕を持たせるだけで体感が大きく変わることを、実際に数値で確認できました。

ここまで読んでくださりありがとうございました。

関連記事