zune の touch pad の反応確認

一昨日、「ゲームをやるには致命的に使いにい」って書いた zune の入力、反応具合を確認するためのコードを書いてみた。


まず、Draw メソッド内に以下のようなコードを記述。これで、現在押されているボタンの一覧を拾って表示する。
ちなみに、this.bullet は適当な画像を読み込んだ Texture2D。
zune だからといって特殊なことは何もなくて、多分、このままで Windows/XBOX でも動く。

graphics.GraphicsDevice.Clear(Color.DarkGray);

var gamePadState = GamePad.GetState(PlayerIndex.One);
var stick = gamePadState.ThumbSticks.Left;
var width = this.Window.ClientBounds.Width;
var height = this.Window.ClientBounds.Height;
var canvasSize = Math.Min(width, height);
int bulletSize = this.bullet.Width;

var buttons = new[]
    {
        new { Button = gamePadState.Buttons.A, Name = "A" },
        new { Button = gamePadState.Buttons.B, Name = "B" },
        new { Button = gamePadState.DPad.Left, Name = "Left" },
        new { Button = gamePadState.DPad.Right, Name = "Right" },
        new { Button = gamePadState.DPad.Down, Name = "Down" },
        new { Button = gamePadState.DPad.Up, Name = "Up" },
    };

var q =
    from b in buttons
    let Active = b.Button == ButtonState.Pressed
    select new { Active, b.Name };

spriteBatch.Begin();

int sx = (int)(canvasSize * (1 + stick.X) - bulletSize) / 2;
int sy = (int)(canvasSize * (1 - stick.Y) - bulletSize) / 2;
spriteBatch.Draw(this.bullet, new Rectangle(sx, sy, bulletSize, bulletSize), Color.White);

float y = 0;

foreach (var x in q)
{
    Color c = x.Active ?
        Color.White :
        Color.Gray;

    spriteBatch.DrawString(this.font, x.Name, new Vector2(0, y), c);
    y += this.font.MeasureString(x.Name).Y;
}

spriteBatch.End();

で、触ってみると分かるんですけども、やっぱり結構、思った方向ボタンだけを押せない。上だけ押すつもりが右ボタンも拾うことが多々。あと、時々、A(決定)ボタンも巻き込むことが。しばらく練習すると、ゆっくりしっかり押せば思った方向だけを押せるようになるんですけども、急ぐと無理。時限のあるゲームを作るとおそらく致命的。

で、そうなると、せっかくタッチパッドがあるんだから、方向操作はアナログ入力でやった方がよさそう。

でも、タッチパッドと方向キー・Aボタンが一体化してるわけで、ボタンを押そうとするとアナログ入力も拾っちゃう。

なので、以下のようなコードを入れて、ボタンを押すまでにどれくらいタッチパッド入力を拾っちゃうか試してみた。

/// <summary>
/// zune では、ボタンを押そうとするとタッチパッドが反応しちゃうんで、
/// その反応がどの程度のものかを確認する。
///
/// ボタンが押されるまでに何フレームタッチパッド 入力があったか/タッチパッド上で動きがあったか を検出。
///
/// 結果は、
/// ボタンが押されるまでにタッチパッドに触れられていたフレーム数 → touchedBeforeKeyDown、
/// ボタンが押されるまでにタッチパッドの座標が変化したフレーム数 → movedBeforeKeyDown。
///
/// タッチパッドから手が離れると、タッチパッド入力が (0, 0) になるので、そこで値をリセット。
///
/// 動いたかどうかは、前フレームとの差分の二乗絶対値の値が閾値(MoveThreshold)以上かどうかで判定。
/// </summary>

void CheckTouchPadState()
{
    var buttons = GamePad.GetState(PlayerIndex.One).Buttons;
    var dpad = GamePad.GetState(PlayerIndex.One).DPad;
    var touchPad = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left;

    var buttonPressed = buttons.A == ButtonState.Pressed;
    buttonPressed |= dpad.Left == ButtonState.Pressed;
    buttonPressed |= dpad.Right == ButtonState.Pressed;
    buttonPressed |= dpad.Up == ButtonState.Pressed;
    buttonPressed |= dpad.Down == ButtonState.Pressed;

    if (buttonPressed)
    {
        this.touchedBeforeKeyDown = this.touchedFrames;
        this.movedBeforeKeyDown = this.movedFrames;
    }
    else if (touchPad.X == 0 && touchPad.Y == 0)
    {
        this.touchedFrames = 0;
        this.movedFrames = 0;
    }
    else
    {
        ++this.touchedFrames;

        if (this.prevTouchPad.X != 0 || this.prevTouchPad.Y != 0)
        {
            var deference = touchPad - this.prevTouchPad;

            if (deference.LengthSquared() > MoveThreshold)
            {
                ++this.movedFrames;
            }
        }
    }

    this.prevTouchPad = touchPad;
}

Vector2 prevTouchPad;
const float MoveThreshold = 0.01f;

int touchedFrames = 0;
int movedFrames = 0;

int touchedBeforeKeyDown = 0;
int movedBeforeKeyDown = 0;

だいたい、ボタンが押されるまでに大体2・3フレームくらいはタッチパッドの入力を拾っちゃう。ちなみに、zune の TargetElapsedTime は標準で 30[fps] っぽい。

タッチパッドからの入力の差分に閾値かけて、あまり動いていないときはタッチパッドを無視するようにすればそこそこいい感じになるかも。