Unity タッチ操作のゲーム開発

<iframe width="560" height="315" src="https://www.youtube.com/embed/sZm_CLsW5y4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<h1>概要</h1>
スマートフォンでも，Unityで作成したゲームを遊べたらと思い，ボタンをタッチして操作できるゲームの開発をおこないました。
今回は，カーゲームをテーマとし，車のオブジェクトをボタンをタッチすることで操作できるシステムを構築していこうと思います。
車のオブジェクトは，前回同様，Unity Standard AssetsのCarオブジェクトに自作のカーモデルを貼り付けたものを使用しました。
また，前回以上にゴリ押しの部分が多々あります。その部分を了承の上，お読みください。

<h1>ボタン配置</h1>
![button.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312991/27a4b973-bab5-27fa-a3b8-a8b73f64aca6.png)

Hierarchy上で右クリックをおこない，UI→Buttonとクリックしていくと，CanvasとEventSystemというオブジェクトが出てきます。Canvasのオブジェクト内にButtonのオブジェクトが入っているため，複製し名前を変更したりして，ボタンを増やしていきます。

今回は，
forwardボタン：加速（アクセル），backボタン：減速（ブレーキ・バック），
rightボタン：右旋回，leftボタン：左旋回
としました。

<h1>Event Triggerの設定</h1>
![Event Trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312991/cb7c8603-2eb2-aedb-d1f3-690bc56d1965.png)

Canvas内のforwardオブジェクト（forwardボタンのオブジェクト），backオブジェクト，rightオブジェクト，leftオブジェクトでそれぞれ，Add Componentをクリックし，Event Triggerを設定します。
Add New Event Typeをクリックし，Pointer Down，Pointer Upをクリックすることで，ボタンを押した時と上げた時に実行するプログラムの関数を設定することができます。

したがって，
Carオブジェクトを操作するCarUserControl.csというファイル内に，
forwardButtonDown()：forwardボタンを押した時の処理
forwardButtonUp()：forwardボタンを上げた時の処理
backButtonDown()：backボタンを押した時の処理
backButtonUp()：backボタンを上げた時の処理
rightButtonDown()：rightボタンを押した時の処理
rightButtonUp()：rightボタンを上げた時の処理
leftButtonDown()：leftボタンを押した時の処理
leftButtonUp()：leftボタンを上げた時の処理

という関数を用意する。
そして，Event Trigger内で，

forwardオブジェクト
Pointer Down→forwardButtonDown()
Pointer Up→forwardButtonUp()

backオブジェクト
Pointer Down→backButtonDown()
Pointer Up→backButtonUp()

rightオブジェクト
Pointer Down→rightButtonDown()
Pointer Up→rightButtonUp()

leftオブジェクト
Pointer Down→leftButtonDown()
Pointer Up→leftButtonUp()

とボタンを押した時，上げた時におこなう命令を設定します。

<h1>プログラム</h1>
bool型の変数forwardMove，backMove，rightMove，leftMoveを宣言し，初期状態をすべてfalseとしました。

```
bool forwardMove = false;
bool backMove = false;
bool rightMove = false;
bool leftMove = false;
```

この変数の値は，対応するボタンが押された時，trueとなり，離れた場合は，falseに戻るようにします。（forwardボタンが押された場合，forwardMoveの値はtrueになる）

```
//forwardボタンを押した時の処理
public void forwardButtonDown()
{
    forwardMove = true;
}

//forwardボタンを上げた時の処理
public void forwardButtonUp()
{
    forwardMove = false;
}

//backボタンを押した時の処理
public void backButtonDown()
{
    backMove = true;
}

//backボタンを上げた時の処理
public void backButtonUp()
{
    backMove = false;
}

//rightボタンを押した時の処理
public void rightButtonDown()
{
    rightMove = true;
}

//rightボタンを上げた時の処理
public void rightButtonUp()
{
    rightMove = false;
}

//leftボタンを押した時の処理
public void leftButtonDown()
{
    leftMove = true;
}

//leftボタンを上げた時の処理
public void leftButtonUp()
{
    leftMove = false;
}
```

元々，float型の変数v，hにGetAxis関数を使用し，キーボードから入力で得た値を，代入し，そのあと，CarController.csのMove関数に変数v, hを引数として渡すことで，Carオブジェクトは動いていましたが，キーボード入力はおこなわないため，GetAxis関数の部分をコメントアウトし，

forwardMoveの値がtrueなら，v = 1，
backMoveの値がtrueなら，v = -1，
rightMoveの値がtrueならh = 0.8，
leftMoveの値がtrueならh = -0.8
という形で，数値を固定しました。

```
// キーボード入力のプログラムをコメントアウト
//float h = CrossPlatformInputManager.GetAxis("Horizontal");
//float v = CrossPlatformInputManager.GetAxis("Vertical");

if (forwardMove == true)
{
    v = 1;//加速（アクセル）
}

if (backMove == true)
{
    v = -1;//減速（ブレーキ・バック）
}

//forwardボタン・backボタンどちらも押されていない場合，加減速中止
if (forwardMove == false && backMove == false)
{
    v = 0;
}

if (rightMove == true)
{
    h = 0.8f;//右旋回
}

if (leftMove == true)
{
    h = -0.8f;//左旋回
}

//rightボタン・leftボタンどちらも押されていない場合，旋回中止
if (rightMove == false && leftMove == false)
{
    h = 0;
}
```

<h1>同時押しをおこなったときのバグ</h1>
プログラムを記述し，実行したところ，画面のボタンの部分にタッチすることで，Carオブジェクトは動くことができました。
しかし，二つのボタンを同時に押して（例：leftボタンを押しながら，forwardボタンを押した場合など），両方のボタンから指を離した場合，一方のボタンは，解除されるが，もう一方のボタンは解除されないバグが発生しました。

<h1>解決策</h1>
解決策として，思い浮かんだのは，同時押しをおこない，離したあと，ボタンが解除されてない状況でも，そのボタンに指がタッチしていない状況であれば，

fowardボタンなら，forwardButtonUp()
backボタンなら，backButtonUp()
rightボタンなら，rightButtonUp()
leftボタンなら，leftButtonUp()
の関数を実行し，強制的にボタンを押していない状況にするようにしました。

具体的には，上の写真のように，画面の右は，forwardボタン，backボタン，左側はrightボタン，leftボタンと分かれているので，forwardボタン，backボタンが押されている状態（forwardMoveやbackMoveの値がtrueの状態）でも画面右側をタッチしていない場合，強制的にforwardButtonUp()や
backButtonUp()の関数を実行します。
rightボタン，leftボタンも同様に，画面の左側をタッチしていない場合は，rightButtonDown()やleftButtonDown()の関数を実行します。
また，このままだと，画面右側のボタン同士（forwardボタンとbackボタン），画面左側のボタン同士（rightボタンとleftボタン）を同時に押した場合，バグが出る可能性があるため，

fowardボタンを押している場合，backButtonUp()
backボタンを押している場合，forwardButtonUp()
rightボタンを押している場合，leftButtonUp()
leftボタンを押している場合，rightButtonUp()
を実行し，画面の右と左でどちらかのボタンしか，押せないようにしました。

```CarUserControl.cs
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;

namespace UnityStandardAssets.Vehicles.Car
{
    [RequireComponent(typeof (CarController))]
    public class CarUserControl : MonoBehaviour
    {
        private CarController m_Car; 

        float h;
        float v;

        bool forwardMove = false;
        bool backMove = false;
        bool rightMove = false;
        bool leftMove = false;

        bool okForward = false;
        bool okBack = false;
        bool okRight = false;
        bool okLeft = false;

        private void Awake()
        {
            m_Car = GetComponent<CarController>();
        }


        private void FixedUpdate()
        {
            okForward = false;
            okBack = false;
            okRight = false;
            okLeft = false;

            // キーボード入力のプログラムをコメントアウト
            //float h = CrossPlatformInputManager.GetAxis("Horizontal");
            //float v = CrossPlatformInputManager.GetAxis("Vertical");

            if (forwardMove == true)
            {
                if (Input.GetMouseButton(0))//指が画面にタッチしている場合
                {
                    for (int i=0; i < Input.touchCount; i++)
                    {
                        if (Input.GetTouch(i).position.x > Screen.width /2)//画面の右側をタッチしている場合
                        {
                            v = 1;//加速（アクセル）
                            okForward = true;
                            break;
                        }
                    }

                    if (okForward == false)//画面の右側をタッチしていない場合
                    {
                        v = 0;
                        forwardButtonUp();
                    }
                }

                else
                {
                    v = 0;
                    forwardButtonUp();
                }
            }

            if (backMove == true)
            {
                if (Input.GetMouseButton(0))//指が画面にタッチしている場合
                {
                    for (int i = 0; i < Input.touchCount; i++)
                    {
                        if (Input.GetTouch(i).position.x > Screen.width / 2)//画面の右側をタッチしている場合
                        {
                            v = -1;//原則（ブレーキ・バック）
                            okBack = true;
                            break;
                        }
                    }

                    if (okBack == false)//画面の右側をタッチしていない場合
                    {
                        v = 0;
                        backButtonUp();
                    }
                }

                else
                {
                    v = 0;
                    backButtonUp();
                }
            }

            //forwardボタン・backボタンどちらも押されていない場合，加減速中止
            if (forwardMove == false && backMove == false)
            {
                v = 0;
            }

            if (rightMove == true)
            {
                if (Input.GetMouseButton(0))//指が画面にタッチしている場合
                {
                    for (int i = 0; i < Input.touchCount; i++)
                    {
                        if (Input.GetTouch(i).position.x < Screen.width / 2)//画面の左側をタッチしている場合
                        {
                            h = 0.8f;//右旋回
                            okRight = true;
                            break;
                        }
                    }

                    if (okRight == false)//画面の左側をタッチしていない場合
                    {
                        h = 0;
                        rightButtonUp();
                    }
                }

                else
                {
                    h = 0;
                    rightButtonUp();
                }
            }

            if (leftMove == true)
            {
                if (Input.GetMouseButton(0))//指が画面にタッチしている場合
                {
                    for (int i = 0; i < Input.touchCount; i++)
                    {
                        if (Input.GetTouch(i).position.x < Screen.width / 2)//画面の左側をタッチしている場合
                        {
                            h = -0.8f;//左旋回
                            okLeft = true;
                            break;
                        }
                    }

                    if (okLeft == false)//画面の左側をタッチしていない場合
                    {
                        h = 0;
                        leftButtonUp();
                    }
                }

                else
                {
                    h = 0;
                    leftButtonUp();
                }
            }

            //rightボタン・leftボタンどちらも押されていない場合，旋回中止
            if (rightMove == false && leftMove == false)
            {
                h = 0;
            }

#if !MOBILE_INPUT
            float handbrake = CrossPlatformInputManager.GetAxis("Jump");
            m_Car.Move(h, v, v, handbrake);
#else
            m_Car.Move(h, v, v, 0f);
#endif
        }

        //forwardボタンを押した時の処理
        public void forwardButtonDown()
        {
            backButtonUp();
            forwardMove = true;
        }

        //forwardボタンを上げた時の処理
        public void forwardButtonUp()
        {
            forwardMove = false;
        }

        //backボタンを押した時の処理
        public void backButtonDown()
        {
            forwardButtonUp();
            backMove = true;
        }

        //backボタンを上げた時の処理
        public void backButtonUp()
        {
            backMove = false;
        }

        //rightボタンを押した時の処理
        public void rightButtonDown()
        {
            leftButtonUp();
            rightMove = true;
        }

        //rightボタンを上げた時の処理
        public void rightButtonUp()
        {
            rightMove = false;
        }

        //leftボタンを押した時の処理
        public void leftButtonDown()
        {
            rightButtonUp();
            leftMove = true;
        }

        //leftボタンを上げた時の処理
        public void leftButtonUp()
        {
            leftMove = false;
        }
    }
}
```

<h1>おわりに</h1>
上記のようにCarUserControl.csを改良することで，同時押しをおこなったあと，片方のボタンが解除されないバグは，ほとんど起きなくなりました。たまに，WebGLビルドをおこない公開したときなどに起きたりしますが，気にせずプレイできるレベルになっています。
もともとは，自分が作成したゲームを通学中など，スマートフォンでプレイしたいと思い，ボタン操作でできるゲームをつくりましたが，思わぬところでつまづいきました。
このような，解決の仕方で合っているかわかりませんが，とりあえず，まともに動いているはずなので，この辺で失礼します。
長文になりましたが，読んでいただいて，ありがとうございます。

<h1>参考にさせていただいたWebサイト</h1>
【Unity】ボタンを使用してオブジェクトを移動させる方法【初心者】
https://lanstar-blog.hatenablog.com/entry/444840631.html

【unity】unityでスマホのタップした時の処理を実装する
https://mogi0506.com/unity-smart-tap/

Unity Documentation スクリプトリファレンス Input.touchCount
https://docs.unity3d.com/ja/2018.4/ScriptReference/Input-touchCount.html

Unity Documentation スクリプトリファレンス Input.GetTouch
https://docs.unity3d.com/ja/2018.4/ScriptReference/Input.GetTouch.html