前回からの続きになります。
で、今回は画面をタッチされた時の当たり判定(ヒットテスト)についてまとめます。
タッチスクリーンのタッチイベントハンドラ
vGamePadはタブレットの画面をタッチすることで操作します。
このタッチするという操作に対してWPFでは3種類のポインティングデバイスが存在します。
- スタイラス
- タッチ
- マウス
上記3つの中で「タッチ」操作に対するイベントハンドラを利用することでボタン操作しています。
次にその「タッチ」の各イベントハンドラです。
UIElement.TouchDownイベント
タブレットの画面(タッチスクリーン)をタッチしたタイミングで発生するイベントです。
vGamePadでは各ボタンを「押す」という動作を実装しています。
すべてのボタンは以下のようなイベントハンドラで処理をしています。
private void vGamePadCanvas_TouchDown(object sender, TouchEventArgs e) { ((Canvas)sender).CaptureTouch(e.TouchDevice); // OnPointerDownの実態は省略 OnPointerDown(e.GetTouchPoint((UIElement)sender).Position, e.GetTouchPoint(this).TouchDevice.Id); }
まずはゲームの操作感をサクサクした感じにするためCaptureTouchをコールしておきます。
さらにタッチしたスクリーン座標はTouchEventArgs.GetTouchPointで取得することができます。
またマルチタッチに対応するためTouchDevice.Idを利用します。
これにより10点タッチ、5点タッチなど複数のタッチ操作をすべて区別することが可能になります。
UIElement.TouchEnterイベント
vGamePadでは使ってません。
UIElement.TouchLeaveイベント
vGamePadでは使ってません。
UIElement.TouchMoveイベント
タブレットの画面(タッチスクリーン)をタッチした状態でなぞる(移動させる)タイミングで発生するイベントです。
vGamePadではアナログスティックボタンを「動かす」という動作を実装しています。
private void vGamePadCanvas_TouchMove(object sender, TouchEventArgs e) { // OnPointerMoveの実態は省略 OnPointerMove(e.GetTouchPoint(this).Position, e.GetTouchPoint(this).TouchDevice.Id); }
移動中のスクリーン座標はTouchEventArgs.GetTouchPointで取得します。
UIElement.TouchUpイベント
タブレットの画面(タッチスクリーン)から指を放したしたタイミングで発生するイベントです。
vGamePadでは各ボタンを「放す」という動作を実装しています。
private void vGamePadCanvas_TouchUp(object sender, TouchEventArgs e) { // OnPointerUpの実態は省略 OnPointerUp(e.GetTouchPoint(this).Position, e.GetTouchPoint((UIElement)sender).TouchDevice.Id); ((Canvas)sender).ReleaseTouchCapture(e.TouchDevice); }
ここでは最後にReleaseTouchCaptureをコールします。
どのUIElementにイベントハンドラを定義するか
vGamePadではタッチスクリーン全域を覆う1枚のウィンドウ(Canvas)上に各ボタンになる部品を配置しているのですが、そのCanvasにイベントハンドラを登録しています。
・・・ <Canvas x:Name="vGamePadCanvas" TouchDown="vGamePadCanvas_TouchDown" TouchMove="vGamePadCanvas_TouchMove" TouchUp="vGamePadCanvas_TouchUp"> ・・・ </Canvas> ・・・
この理由ですがゲームの操作性を少しでも損なわないようにしたいためです。
とりあえずボタンをタッチするとオンになります。
ですがアナログスティックの操作やボタンを押した状態でゲームに集中すると指の位置とボタンの位置がどうしてもズレてきます。
このタイミングでボタンがオフになると、またボタンの押し直しをする必要が出るのでゲームの操作に大きな影響が出てしまいます。
特にアナログスティックはキャラクターの操作感に直結するのでここを何とかする必要があります。
とにかく、単純に指がボタンから離れてもボタンを押している状態をキープできれば操作感はかなりよくなるはずです。
これを実現するため、Canvasにイベントハンドラを登録して、TouchDownイベントでタッチしたボタンとそのTouchDevice.Idを保存。
TouchMoveイベント、TouchUpイベントではTouchDevice.Idでボタンを特定し、座標はアナログスティックの移動にしか利用しないようにしています。
vGamePadの当たり判定仕様
TouchDownイベントの座標を元にヒットテストを行いその場所にあるボタンを特定します。
このヒットテストはVisualTreeHelper.HitTestを使います。
少しでもシンプルにボタンを特定するため次のような単純なコードで実現します。
まずは例として1ボタンを表すXAMLの抜粋です。
<Grid x:Name="button01" Canvas.Top="0" Canvas.Left="144" Style="{StaticResource gridStyle}"> <Ellipse x:Uid="button01" Style="{StaticResource ellipseStyle}"/> <TextBlock x:Uid="button01" Text="1" Style="{StaticResource textBlockStyle}"/> </Grid>
次にその特定ソースです。
private void OnPointerDown(Point point, int id) { var result = VisualTreeHelper.HitTest(vGamePadCanvas, point); if ( result == null || ((UIElement)result.VisualHit).Uid == "" ) { // ヒットしなかった、または、ボタンではなかった時 return; } // このUidでボタンが特定されるのでidを保存、あとは色を変えたり音を鳴らしたりする var ui = vGamePadCanvas.FindName(((UIElement)result.VisualHit).Uid); : : }
XAMLを見るとボタン1を構成するエレメントにすべて同じUid(button01)を定義しています。
本来、Uidはローカライズに利用したりするのですが今回は処理を楽にするために利用しました。
結果、HitTestでヒットしたエレメントのUidをチェックすればどのボタンが押されたか早く検出することができます。
もしかしてこの方法はNGなのかもしれませんが、今でも問題が出ていないのでOKですよね…