トリガー入力とは?|「押した瞬間」と「押しっぱなし」を区別するキー入力の作り方

trigger input thumbnail ゲーム制作

「ジャンプキーを1回押しただけなのに、何回もジャンプしてしまう」「押しっぱなしと押した瞬間って、コード上でどう区別するの?」「IsKeyDownだけじゃダメなの?」

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

今回は、ゲームの入力処理でほぼ必ず必要になるトリガー入力(押した瞬間/離した瞬間の判定)について解説していきます。実際にコンパイル・実行した結果を交えながら、仕組みと、判定を間違えると何が起きるかまで紹介します。

トリガー入力とは?

キーボードやゲームパッドの入力には、大きく分けて3つの状態があります。

IsPressed(押しっぱなし)…押されている間、毎フレームtrue
IsTrigger(押した瞬間)…押した最初の1フレームだけtrue
IsRelease(離した瞬間)…離した最初の1フレームだけtrue

移動のように「押している間ずっと動き続けてほしい」処理はIsPressedを使いますが、ジャンプや決定ボタンのように「1回の入力につき1回だけ反応してほしい」処理にはIsTriggerが必要になります。この区別を持たずにIsPressedだけで組んでしまうと、思わぬバグにつながります。

実装してみる

「今フレーム押されているか」と「前フレーム押されていたか」の2つの状態を持ち、その差分から3つの判定を作るKeyLoggerを実際に書いてコンパイル・実行してみます。

class KeyLogger {
public:
    static void Update(bool currentFrameDown) {
        s_Prev = s_Current;
        s_Current = currentFrameDown;
    }

    static bool IsPressed() { return s_Current; }                // 押しっぱなし含む
    static bool IsTrigger() { return s_Current && !s_Prev; }      // 押した瞬間だけ
    static bool IsRelease() { return !s_Current && s_Prev; }      // 離した瞬間だけ

private:
    static bool s_Current;
    static bool s_Prev;
};

trueが3フレーム続き、falseが2フレーム続く、を2セット入力したときの実行結果です。

frame | raw   | IsPressed | IsTrigger | IsRelease
------+-------+-----------+-----------+----------
   0  | false |    0      |    0      |    0
   1  | true  |    1      |    1      |    0
   2  | true  |    1      |    0      |    0
   3  | true  |    1      |    0      |    0
   4  | false |    0      |    0      |    1
   5  | false |    0      |    0      |    0
   6  | false |    0      |    0      |    0
   7  | true  |    1      |    1      |    0
   8  | true  |    1      |    0      |    0
   9  | true  |    1      |    0      |    0
  10  | true  |    1      |    0      |    0
  11  | false |    0      |    0      |    1

IsPressedはtrueが続く4フレーム間(frame 1〜3, 7〜10)ずっと1になっているのに対し、IsTrigger押し始めた瞬間(frame 1とframe 7)だけ1になっています。IsReleaseも同様に、離した瞬間(frame 4とframe 11)だけ1です。この差分の作り方さえ押さえておけば、どんな入力ライブラリでも同じ考え方が使えます。

【重要】IsPressedでジャンプを実装すると何が起きるか

「区別しないとどうなるか」を実際に確かめてみます。ジャンプキーを3フレームだけ押した状況で、NG版(IsPressedでジャンプ処理を呼ぶ)とOK版(IsTriggerでジャンプ処理を呼ぶ)を比較します。

// NG: 押している間ずっとtrueになるIsPressedでジャンプ判定
if (KeyLogger::IsPressed()) {
    jumpCountByPressed++;
}

// OK: 押した瞬間だけtrueになるIsTriggerでジャンプ判定
if (KeyLogger::IsTrigger()) {
    jumpCountByTrigger++;
}

実行結果です。

frame | raw   | NG:IsPressedでジャンプ | OK:IsTriggerでジャンプ
   0  | false |  -         (累計0回)          |  -         (累計0回)
   1  | true  |  ジャンプ! (累計1回)          |  ジャンプ! (累計1回)
   2  | true  |  ジャンプ! (累計2回)          |  -         (累計1回)
   3  | true  |  ジャンプ! (累計3回)          |  -         (累計1回)
   4  | false |  -         (累計3回)          |  -         (累計1回)
   5  | false |  -         (累計3回)          |  -         (累計1回)
   6  | false |  -         (累計3回)          |  -         (累計1回)
   7  | false |  -         (累計3回)          |  -         (累計1回)

3フレームしか押していないのに、NG版の総ジャンプ回数=3回 / OK版=1回

キーを1回(3フレーム分)押しただけなのに、IsPressedで判定したNG版は3回ジャンプしてしまいました。実際のゲームでは60FPSでキーを0.2秒押しただけでも十数回分の「ジャンプ」処理が走ることになり、多段ジャンプや変な挙動の原因になります。IsTriggerで判定したOK版は、想定通り1回だけです。

トリガー入力のよくある失敗例と対処法

①1回だけ反応させたい処理にIsPressedを使ってしまう

上で実演した通りです。ジャンプ・決定・メニューを開く、などの「1入力=1反応」であるべき処理には必ずIsTrigger(またはIsRelease)を使いましょう。

②KeyLogger::Update()を呼ぶタイミングがズレる

Updateは「前フレームの状態」を作るために必ず毎フレーム1回だけ呼ぶ必要があります。複数回呼んでしまうと、1フレームの間にs_Prevが2回更新されてしまい、IsTriggerが正しく1にならなくなります。

③複数キーで同じ操作を割り当てたときの判定漏れ

「ZキーでもEnterキーでもジャンプできる」ようにしたいとき、それぞれ別々のKeyLoggerでIsTriggerを取って||で繋いでしまうと、片方を押しっぱなしのままもう片方を押したときに反応しないことがあります。まとめて1つの論理入力として扱う設計にするのが安全です。

注意点

  • 移動など連続してほしい処理はIsPressed
  • ジャンプ・決定など1回だけ反応してほしい処理はIsTrigger
  • Update毎フレーム必ず1回だけ呼ぶ
  • 複数キーの割り当ては、論理入力としてまとめて扱う

まとめ

  • トリガー入力は「今のフレーム」と「前のフレーム」の差分から作る
  • IsPressed(押しっぱなし)とIsTrigger(押した瞬間)は明確に使い分ける
  • 間違えると、1回の入力で処理が何度も走ってしまう(実際に3倍ジャンプするのを確認した)
  • 対処法はシンプルで、「1回だけ反応すべきか」を最初に決めてから実装するだけ

入力判定はどのゲームでも必ず通る道ですが、IsPressedとIsTriggerを取り違えるだけでこれだけ動作が変わることを、実際に数値で見ると納得感がありました。

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

関連記事