WisdomSoft - for your serial experiences.

連続入力の防止

ボタンを押すと状態が切り替わるトグルボタンのような機能を実現するには、単にボタンの状態を調べるだけでは、ボタンを押したままの状態でフレームごとに切り替わりが発生してしまいます。ボタンが押されている間、フレーム毎に連続してボタンが押されている入力判定が行われないように工夫が必要です。

一度だけの入力処理

XNA Framework による入力処理は、イベント駆動ではなく Update() メソッドでフレーム毎に入力状態を調べるという方法を取るため、ボタンが押された時に一度だけ処理をするといった動作には注意が必要です。例えば、一度 B ボタンを画面の背景色を赤に変更し、もう一度押すと青に変更するというトグル処理を考えてください。これをバカ正直に実装するとコード1のようになるでしょう。

コード1
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private bool isRed;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void Update(GameTime gameTime)
    {
        GamePadState state = GamePad.GetState(PlayerIndex.One);
        if (state.Buttons.B == ButtonState.Pressed)
        {
            Window.Title = "Pressed";
            isRed = !isRed;
        }
        else Window.Title = "Released";

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(isRed ? Color.Red : Color.Blue);
        base.Draw(gameTime);
    }
}
実行結果

コード1を実行すれば、すぐに問題があることに気付きます。このプログラムでは bool 型の isRed フィールドが true であれば背景色を赤で、そうでなければ青で塗りつぶします。Update() メソッド内でコントローラの A ボタンが押されているかどうかを調べ、押されていれば isRed フィールドの値を反転させます。

Update() メソッドは連続で呼び出されているため、B ボタンが押されている間は isRed の値が連続で切り替わり、結果として短い時間でも B ボタンを押している間は背景色が連続で切り替わります。B ボタンが押された瞬間に一度だけ実行すればよい処理でありながら、何度も連続で実行されてしまいます。この性質は、シューティングゲームなどでボタンが押されている間に弾丸が発射され続けるとった振舞いには都合が良いのですが、メニューの選択などの微細な操作には問題があります。

この問題を解決するには、直前の入力状態をフィールドに保存し、直前のボタンの状態が離されていて、かつ現在のボタンの状態が押されているときに押されたと判定する方法が考えられます。

コード2
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private GamePadState prevState;
    private bool isRed;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void Update(GameTime gameTime)
    {
        GamePadState state = GamePad.GetState(PlayerIndex.One);
        if (state.Buttons.B == ButtonState.Pressed)
        {
            Window.Title = "Pressed";
            if (prevState.Buttons.B == ButtonState.Released) isRed = !isRed;
        }
        else Window.Title = "Released";

        prevState = state;

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(isRed ? Color.Red : Color.Blue);
        base.Draw(gameTime);
    }
}
実行結果

コード2は、B ボタンが押された時に一度だけ色を切り替えます。ボタンを押し続けても連続で色が切り替わることはありません。Update() メソッドでコントローラの状態を prevState フィールドに保存し、次の Update() メソッドの処理で、直前のコントローラの状態と比較するために利用します。現在のフレームでボタンが押されていて、かつ直前の状態でボタンが押されていなければ、ボタンが押された瞬間の最初の処理であることを認識できます。