Touch in Windows Phone 7 game development 2 multi-touch

Operating environment

Windows Phone Windows Phone 7
XNA XNA Game Studio 4.0 Refresh

Tested environment

Windows Phone Windows Phone 7
XNA XNA Game Studio 4.0 Refresh

Content

プログラミング! - 2.マルチタッチを試してみる

今回のサンプルについて

ここではマルチタッチ入力を実現するためのプログラムについて説明したいと思います。Windows Phone 7 でタッチパネルによるマルチタッチ入力はユーザー入力として重要な項目のひとつになります。なぜなら Windows や Xbox 360 とは異なり、主となる入力インターフェースが「タッチパネル」になるからです。キーボードを搭載した Windows Phone も出るかもしれませんがもちろん必ず搭載さているとは限りません。となると、どの Windows Phone でもゲームを動作させるためには、タッチパネルに対応させる必要があります。

タッチパネルを「シングルタッチ」のみに限定した場合、ゲームを作るとなると必然的に作れるゲームの種類は限られてしまうでしょう。携帯ゲーム機を思い出してみてください(据え置きでもいいです)。ほとんどのゲーム機は両手でゲームコントローラー(または実機)を持つ形でいくつものボタンを同時に押す必要があるかと思います。

Windows Phone 7 ではゲームコントローラーのようにいくつものボタンを持ってはいませんし、キーボードもついているとは限りません。そのため、画面上に仮想的なキーやボタンを配置する場合もあり、それらを押す場合シングルタッチのみでは同時押しができないため不便です。

もちろん上記のような仮想ボタンの例だけではなく、マルチタッチでよくある「ピンチ、ストレッチ(2 点間を近づけたり離したりする操作)」や 1 つの画面を複数人で操作するようなゲームの場合だとマルチタッチが必要になってきます。

さて、前置きが長くなりましたが今回のサンプルではタッチパネル専用のクラスを使用してマルチタッチの情報取得を行いたいと思います。いきなりマルチタッチで遊べるようなサンプルを作成していくのも楽しくていいのですが、先にマルチタッチでどんな情報が取得できるかを調べてみたいと思います。癖がある部分もあるので先にそれらを知っておくことによって後々のプログラミングでうまく動かないときなど、原因追及にかける時間を減らせるかと思います。

このサンプルプログラムのゴール

マルチタッチを行った時の情報を表示させて確認します。

図 1 :タッチしたときにタッチ情報がテキストで表示される
図 1 :タッチしたときにタッチ情報がテキストで表示される

プログラム – フィールドの宣言

フィールドには「TouchPanelCapabilities 構造体」と「TouchCollection 構造体」を宣言しておきます。

/// <summary>
///  スプライトでテキストを描画するためのフォント
/// </summary>
SpriteFont font;

/// <summary>
///  タッチパネルの機能情報
/// </summary>
TouchPanelCapabilities capabilities;

/// <summary>
///  取得したタッチ情報の一覧
/// </summary>
TouchCollection touches;

「TouchPanelCapabilities 構造体」はタッチパネル自体の機能をパラメータとして持つことができる構造体です。更新時にタッチパネルが使用できるかをチェックするのに使用します。

「TouchCollection 構造体」は現在タッチされている状態の情報一覧を持ちます。複数のタッチを検出した場合は複数の「TouchLocation 構造体」を取得することができます。詳しい説明については後程説明します。

プログラム – フォントの読み込み

画面上にテキストを描画する際にあらかじめフォント定義をコンテンツプロジェクトに追加しておき、Game.LoadContent メソッドで読み込んでおきます。こちらはタッチパネル関連とは直接関係ないので詳しい説明は割愛させていただきます。

図 2 :コンテンツプロジェクトに「Font.spritefont」を追加しておく
図 2 :コンテンツプロジェクトに「Font.spritefont」を追加しておく

/// フォントをコンテンツパイプラインから読み込む
font = Content.Load<SpriteFont>("Font");

プログラム – タッチパネルの情報を取得

Game.Updateメソッド内でタッチパネルの情報を取得します。

/// タッチパネルの機能情報を取得
capabilities = TouchPanel.GetCapabilities();

/// タッチパネルが使用可能であるかチェック
if (capabilities.IsConnected)
{
  /// 現在のタッチパネルの入力情報を取得
  touches = TouchPanel.GetState();
}

「TouchPanel.GetCapabilities」メソッドを呼び出すとタッチパネルの機能情報を取得することができます。取得できる情報としては「タッチパネルが使用できるか」「タッチパネルで取得できる最大タッチポイント数」の 2 つです。どちらもゲーム中に変わることはないので Game.Initialize メソッドあたりで取得しておけば問題ないと思いますが、今後タッチパネルの取り外しなどができる機器(USB 接続可能なタッチパネルや Windows Phone 以外での実機)が登場した場合も考慮して Update メソッドに記述しています。

また、「タッチパネルが使用できるか」については Windows Phone 7 では必ず使用できるのでチェックの必要はないのですが、WindowsやXbox 360 とコードを共有する場合、これらのハードは必須入力インターフェースではないのでチェックの必要があります。

「TouchPanelCapabilities.IsConnected」プロパティでタッチパネルが使用可能であることを確認できたら「TouchPanel.GetState」メソッドで現在のタッチ状態を取得しています。

本来タッチ情報を取得した後になんらかの操作処理を行うのですが、今回のサンプルでは情報を表示するだけですので Game.Update メソッド内ではこれ以上の処理はしていません。

プログラム – タッチポイントの取得可能な最大数を取得

タッチポイントの取得可能な最大数は大体決まっているのでゲーム中に取得する必要はあまりありませんが、「TouchPanelCapabilities.MaximumTouchCount」プロパティから取得可能な最大タッチポイント数を取得できます。例えば MaximumTouchCount プロパティが「4」であれば、タッチパネルに 5 本の指でタッチしても 5 本目の位置情報を取得することができません。

今回サンプルで使用しているスマートフォン「HTC 7 Trophy」での取得数は「4」でした。(ちなみにXNA Game Studio 4.0 では必ず 4 を返すように定義されています。Windows Phone 7 の仕様では4ポイント以上なので 4 未満を返すことはありません)

/// タッチ可能な最大数を表示
spriteBatch.DrawString(font,  
                       "MaximumTouchCount : " +
                         capabilities.MaximumTouchCount,
                       new Vector2(20, 50),
                       Color.LightGreen);

図 3 :TouchPanelCapabilities.MaximumTouchCount プロパティの取得数
図 3 :TouchPanelCapabilities.MaximumTouchCount プロパティの取得数

プログラム – タッチ情報を取得

取得した「TouchCollection」構造体には複数のタッチ情報が含まれています。例えば 2 本の指でタッチしている場合は通常 2 つ分のタッチ情報が含まれています。

タッチ情報の数は「TouchCollection.Count」プロパティで取得できます。取得した数だけ for ステートメントで繰り返しタッチ情報を表示させています。本来 foreach でループさせても問題ないのですが、後の説明で配列のインデックス(int index)を使用するので for でループさせています。

// タッチ情報の数だけループする
for (int index = 0; index < touches.Count; index++)
{
  // 指定したインデックスのタッチ情報取得
  TouchLocation tl = touches[index];

  // タッチ情報を可視化
  string mes = "Index : " + index + Environment.NewLine +
               "Id : " + tl.Id + Environment.NewLine +
               "Position : " + tl.Position + Environment.NewLine +
               "State : " + tl.State;

  // 文字の描画
  spriteBatch.DrawString(font,
                         mes,
                         new Vector2(30 + (index % 4) * 10,
                                     80 + index * 140),
                         Color.White);
}

さて、各タッチ情報ですがこれらは「TouchLocation」構造体として取得します。for でループしている場合は「TouchCollection[index]」で取得できます。(foreach でも直接「TouchLocation」構造体を取得できます)

「TouchLocation」構造体で取得できる情報としては以下の 4 つがあり、サンプルではそれぞれの情報を表示させています。

表 1.「TouchLocation」構造体で取得できる情報
Idタッチを識別する ID を取得
Positionタッチ位置を画面左上を原点として取得
Stateタッチした瞬間、移動(ドラッグ)中、放した瞬間であるかを取得
TryGetPreviousLocation (メソッド)前回のタッチ情報

実際に実行してみると下のようにタッチ情報が表示されます。3 本の指でタッチしているので 3 つ分のタッチ情報が表示されています。ちなみに 5 本の指でタッチしても 4 つまでしか表示されません。(最大取得数が 4 のため)

図 4 :3 本の指でタッチしているところ
図 4 :3 本の指でタッチしているところ

プログラム – タッチ情報の取得で気を付けるべきこと 1 (インデックスと ID)

前述でタッチ情報を扱う際に注意すべきことがあると述べましたが、まず一つ目が「インデックス」と「ID」です。インデックスは単純に配列のインデックスのことを指します。

文章で説明するより実機で試していただいた方がわかりやすいかと思います。例えば「人差し指」と「中指」の 2 本で操作するものとし、初めに人差し指でタッチします。

図 5 :人差し指でタッチ
図 5 :人差し指でタッチ

画面に表示されている通りインデックスは「0」になります。続いて中指でタッチします。

図 6 :中指でタッチ
図 6 :中指でタッチ

この状態では 2 つの指でタッチしている状態なので 2 つの情報が表示されています。中指でタッチしたインデックスは「1」になっています。

ここで人差し指を放してみましょう。するとタッチ情報は下のようになります。

図 7 :人差し指を放す
図 7 :人差し指を放す

ここまでくればお気づきかと思いますが、中指でタッチしている情報のインデックスは「0」になります。もしキャラクターを動かすときにインデックスで管理していたりすると先ほどの操作は以下のような動作を招きます。

・人差し指でキャラクター「A」を移動させる
  ↓
・その状態で中指でキャラクター「B」を移動させる
  ↓
・中指でキャラクターを移動させたまま人差し指を放す
  ↓
・中指で操作していたキャラクターが突然「A」に切り替わる

また、別な現象として 2 本の指をタッチさせている状態でそのうちの 1 本の指を放したりタッチさせたりを繰り返していると一つ目のタッチ情報の描画文字がちらつくことがあります。これは 2 本目の指の操作がタッチした瞬間や放した瞬間にインデックス「0」に割り込んでいるためです。

上記のようにマルチタッチしたときのインデックスやタッチ情報の並び順はタッチした順番とは一致していないことになります。そのためなんらかの操作処理を行う場合は TouchCollection のインデックスでは管理してはいけません

では何で管理すべきかというと「TouchLocation」構造体には「Id」プロパティがあり、Id はタッチしなおすたびに新しい値に書き換わりますが、タッチしている間はタッチポイントと ID との関係性は保証されるのでこちらで管理していくことになります。

もちろん必ず ID で管理しなければいけないということではなく、タッチ位置だけでも十分管理できる場合もあります。例えば画面上にあるボタンをタッチするだけであれば ID 関係なしにタッチ位置で調べればボタンを押したことは判別することができます。ただしドラッグ処理などを含む場合は位置が常に変わるのでやはり ID で判別すべきだと思います。

プログラム – タッチ情報の取得で気を付けるべきこと2(タッチ情報の取得数)

記事のプログラムには入れていませんが、サンプルでは画面の一番下に「GetMaxTouchCount」という文字列とともに「同時に取得したタッチ情報の最大数」を描画させています。これは過去に「TouchPanel.GetState」メソッドで取得したタッチ情報数の最大値を表示させていますが、今回のテスト機では「TouchPanelCapabilities.MaximumTouchCount」プロパティが 4 なのでここに表示される数値の最大値は通常 4 になるはずです。ためしに 5 本の指を置いてもやはり 4 になります。

図 8 :5 本の指を置いたときの数値
図 8 :5 本の指を置いたときの数値

では、ちょっと実験してみましょう。タッチパネルを複数の指で高速にタッチを繰り返してみてください。テスト機にもよるかと思いますが、いつの間にか数値が 4 を超えてしまう場合があります。

図 9 :GetMaxTouchCount が 4 を超えている
図 9 :GetMaxTouchCount が 4 を超えている

実は「TouchPanel.GetState」メソッドで取得するタッチ情報は「TouchPanel.GetState」メソッドを呼び出した瞬間のタッチ情報ではなく、前回の更新タイミングからタッチした瞬間、放した瞬間などがキャッシュされています。そのため、同時に検出できるタッチ数は最大 4 つであるにも関わらず、同じ指でもタッチしなおせば別なタッチとして扱われるため、TouchPanelCapabilities.MaximumTouchCount を超えた数のタッチ情報が取得される場合はあります。

ですので TouchPanelCapabilities.MaximumTouchCount が 4 だったからといってタッチに関連するデータの配列要素数を 4 固定にしたりすると処理の仕方によってはインデックスオーバーのエラーが発生する場合もあります。

ちなみに XNA Game Studio のヘルプでは TouchCollection.Count プロパティの最大値は「8」と記載されていますので、配列の要素数を固定にしたい場合は最低でも要素数を 8 にしておけば安心かと思います。(今後のバージョンアップを考えた場合、インデックスオーバーしないようにガード句を入れおいてもいいかもしれません)

今回のサンプルのまとめ

今回はマルチタッチから得られる情報について各種パラメータを調べてみました。また、タッチパネルの情報を取得する際にいくつか注意点があることも確認しました。これらの知識をもとに実際にマルチタッチを使用したサンプルを説明していきたいと思います。

Download