スターフォックス・ライクな3Dシューティングを作っている時に、ローリングボタンを設置するスペースがなくなったので考案。
中々の精度で、円スワイプジェスチャーの判定が取れます。
(勿論、PC/スマホ可)
サンプル動画
以下の通り、一通りの図形ジェスチャーを試した。
- 円: 判定
- 渦巻き: 判定
- 反時計回り: 判定
- 三角: 無判定
- 四角: 無判定
- バツ: 無判定
- 直線: 無判定
- ジグザグ: 無判定
注意点
- 通常のゲーマーのスワイプ速度なら大丈夫だが、ゆっくり円を描くと円判定に失敗する場合がある。*
- MouseButtonUp(指を離した判定)は、毎フレームチェックしないといけない為、同コルーチンにまとめられない。
*(前スワイプ地点のベクトルと、現在スワイプ地点のベクトルの外積で、同じ回転方向にスワイプしているかチェックするのが、ゆっくりだとなんか上手くいっていない)
【コード】CircleSwipeGestureChecker
- 同名の新規スクリプトを作成して、コード全文貼っ付けてください。
GameManager等に混ぜ込んでも大丈夫です。
(シンプルな変数名にしているが、多分、被らないと思う)
using System.Collections;
using UnityEngine;
public class CircleSwipeGestureChecker : MonoBehaviour
{
float angle;
float angleSum;
Vector3 swipePosition;
Vector3 previousSwipePosition;
Vector3 vector;
Vector3 previousVector;
int sign;
int previousSign;
//スワイプ地点の角度の総和が、この値を超した場合は円を描いたと見なす。
static readonly float AngleSumThreshold = 330.0f;
//前回からのタッチ地点の差が、この距離未満だと無視する(sqrMagnitudeなので、2乗。ワールド座標ではなく、スクリーン座標なので大きめで)。
static readonly float SwipeDeltaSqrThreshold = 15.0f * 15.0f;
Coroutine checkCircleSwipeGesture;
Coroutine checkCircleSwipeGestureMouseButtonUp;
//スワイプジェスチャーのチェック間隔。
static readonly WaitForSeconds CheckCircleSwipeGestureWait = new WaitForSeconds(0.05f);
//ゲーム冒頭等、ジェスチャーチェック受付開始時に、1回だけコレを呼ぶ。
public void StartCheckCircleSwipeGesture()
{
//一応、重複チェックしている。
if (checkCircleSwipeGesture != null) {
StopCoroutine(checkCircleSwipeGesture);
}
checkCircleSwipeGesture = StartCoroutine(CheckCircleSwipeGesture());
if (checkCircleSwipeGestureMouseButtonUp != null) {
StopCoroutine(checkCircleSwipeGestureMouseButtonUp);
}
checkCircleSwipeGestureMouseButtonUp = StartCoroutine(CheckCircleSwipeGestureMouseButtonUp());
}
//停止時には、コレを呼ぶ。
public void StopCheckCircleSwipeGesture()
{
if (checkCircleSwipeGesture != null) {
StopCoroutine(checkCircleSwipeGesture);
checkCircleSwipeGesture = null;
}
if (checkCircleSwipeGestureMouseButtonUp != null) {
StopCoroutine(checkCircleSwipeGestureMouseButtonUp);
checkCircleSwipeGestureMouseButtonUp = null;
}
}
//計算に使用する値の初期化。
void ResetCheckCircleSwipeGesture()
{
previousSwipePosition = Input.mousePosition;
angleSum = 0;
vector = Vector3.zero;
previousVector = Vector3.zero;
sign = 0;
previousSign = 0;
}
IEnumerator CheckCircleSwipeGesture()
{
ResetCheckCircleSwipeGesture();
yield return CheckCircleSwipeGestureWait;
while (true) {
if (Input.GetMouseButton(0)) {
swipePosition = Input.mousePosition;
if ((swipePosition - previousSwipePosition).sqrMagnitude < SwipeDeltaSqrThreshold) {
yield return CheckCircleSwipeGestureWait;
continue;
}
angle = Vector3.Angle(previousVector, vector);
if (vector != Vector3.zero)
previousVector = vector;
vector = swipePosition - previousSwipePosition;
previousSwipePosition = swipePosition;
if (previousVector != Vector3.zero && previousSign == 0)
previousSign = Vector3.Cross(previousVector, vector).z < 0 ? 1 : -1;
if (previousVector != Vector3.zero) {
sign = Vector3.Cross(previousVector, vector).z < 0 ? 1 : -1;
//逆回転になっているので、角度の総和をリセット。
if (previousSign != sign) {
previousSign = sign;
angleSum = 0;
} else {
angleSum += angle;
}
} else {
angleSum += angle;
}
if (AngleSumThreshold <= angleSum) {
//円を描いたと判定された時の処理をココに記述。
//previousSignの値を見ると、時計回りか反時計回りか分かる(-1 == 反時計回り、1 == 時計回り)。
ResetCheckCircleSwipeGesture();
}
}
yield return CheckCircleSwipeGestureWait;
}
}
//スワイプが中断された判定は、毎フレームしないといけないので、別コルーチンに分ける。
IEnumerator CheckCircleSwipeGestureMouseButtonUp()
{
while (true) {
if (Input.GetMouseButtonUp(0)) {
ResetCheckCircleSwipeGesture();
}
yield return null;
}
}
}