めーぷるのおもちゃばこ

- アイドルになりたいエンジニア女子の制作日記 -

【Unity】画面の上にあるジョイスティックを作る

まずジョイスティックを作る

🐼ジョイスティックの見た目を作る

まずはジョイスティックの実装からします。
Unityを開き、ヒエラルキーでCreate>UI>Imageでキャンバスとイメージを作ります。

ジョイスティックは丸いので、丸い画像をセットしたいです。
ここではデフォルトで入ってる画像にKnobという名前の丸い画像があるのでそれを付けます。
丸い画像がある人はそれをセットすると良いと思います。
サイズは、ここではwidthとheightを300にしておきます。

f:id:maplesyrup-cs6:20200110222052p:plain
画像のセット


次に、今作ったImage子に今度はImageではなくButtonを作り、同じく丸い画像をセットします。
サイズは、widthとheightをそれぞれ150くらいにしておきました。

今のとここんな感じです↓↓

f:id:maplesyrup-cs6:20200111005355p:plain
こんな感じ🐼


これを動くようにしていきます。

🐼ジョイスティックの実装

新しいスクリプトを作りJoyStickという名前にします。
以下のスクリプトを貼り付けます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class JoyStick : MonoBehaviour
{
    [SerializeField] 
    private float _radius;
    private GameObject _joyStick;
    private Vector2 _defaultPos;

    public Vector2 Position { private set; get; }
    public Vector2 Direction { private set; get; }

    void Start()
    {
        _joyStick = this.gameObject;
        _defaultPos = _joyStick.transform.position;
        Debug.Log(_defaultPos);
    }

    public void PointerUp()
    {
        _joyStick.transform.position = _defaultPos;
    }

    public void Drag()
    {
        _joyStick.transform.position = Input.mousePosition;

        float moveDist = CalculateDist(_joyStick.transform.position);

        if (moveDist > _radius)
        {
            float radian = Mathf.Atan2(_joyStick.transform.localPosition.y, _joyStick.transform.localPosition.x);

            Vector3 limitedPosition = Vector3.zero;
            limitedPosition.x = _radius * Mathf.Cos(radian);  
            limitedPosition.y = _radius * Mathf.Sin(radian); 

            _joyStick.transform.localPosition = limitedPosition;
        }

        Position = new Vector2(
            _joyStick.transform.localPosition.x / _radius,
            _joyStick.transform.localPosition.y / _radius
        );
    }

    public float CalculateDist(Vector2 jPos)
    {
        Vector2 diff = jPos - _defaultPos;
        Direction = diff;
        float dist = Mathf.Sqrt(Mathf.Pow(diff.x, 2) + Mathf.Pow(diff.y, 2));
        // Debug.Log("diff:::" + diff);

        return dist;
    }
}


このスクリプトをButtonのインスペクターにドラッグアンドドロップでセットします。
セットしたら、インスペクタからRadiusの値を決めます。このRadiusは、ジョイスティックの可動範囲の値です。ジョイスティックのサイズによって変わってきますが、ここでのサイズ(ベースが300、スティックが150のサイズ)だとRadius30と79で以下のような違いになります。 

f:id:maplesyrup-cs6:20200111130642g:plain
Radius30(左)とRadius79(右)


ジョイスティックのサイズによって変わってくるので、Buttonを移動させてみてちょうどいい位置の値を入れるようにしてください。

f:id:maplesyrup-cs6:20200111131224p:plain
ジョイスティックの可動範囲を決める


次に、ButtonのインスペクターのAdd ComponentからEvent Triggerをセットします。

f:id:maplesyrup-cs6:20200111131633p:plain
Event Triggerのセット


①Event TriggerのAdd New Event TypeからPointer UpとDragを追加し、
②プラスボタンを押してドラッグ&ドロップでButtonをセットし、
③それぞれFunctionをJoyStick>PointerUpと JoyStick>Dragに設定します。

f:id:maplesyrup-cs6:20200111132241g:plain
Event Triggerの設定


これで実行してみると.....JoyStickだ!!

f:id:maplesyrup-cs6:20200111015253g:plain
ジョイスティックだ!


🐼ジョイスティックのスクリプト解説

【離した時の挙動】
public void PointerUp()
{
    _joyStick.transform.position = _defaultPos;
}

ButtonのPointerUPのイベントトリガーに設定している関数です。
Start()でボタンの初期位置を代入した_defaultPosをジョイスティックのポジションに入れています。

【ドラッグ時の挙動】

まずジョイスティックのトランスフォームにマウスの値を入れています。
その値をCalculateDistに渡して帰ってきた値をmoveDistに入れます。

_joyStick.transform.position = Input.mousePosition;

float moveDist = CalculateDist(_joyStick.transform.position);


CalculateDistはデフォルトの位置から現在のジョイスティックの位置までの距離を計算しています。三平方の定理っていうやつです。

public float CalculateDist(Vector2 jPos)
{
    Vector2 diff = jPos - _defaultPos;
    float dist = Mathf.Sqrt(Mathf.Pow(diff.x, 2) + Mathf.Pow(diff.y, 2));
    // Debug.Log("diff:::" + diff);

    return dist;
}


a^2 + b^2 = c^2で求めたい部分を計算しています。(PhotoShop完全に理解した)

f:id:maplesyrup-cs6:20200112120852p:plain
三平方の定理で距離を求める


そして距離が返ってきたら、返ってきた距離が_radiusに入れた値よりも大きければそれ以上移動しないようにします。
ボタンは円形なので、円形に可動限界値を求めなければいけません。

if (moveDist > _radius)
{
    float radian = Mathf.Atan2(_joyStick.transform.localPosition.y, _joyStick.transform.localPosition.x);

    Vector3 limitedPosition = Vector3.zero;
    limitedPosition.x = _radius * Mathf.Cos(radian);
    limitedPosition.y = _radius * Mathf.Sin(radian);

    _joyStick.transform.localPosition = limitedPosition;
}


Atan2(アークタンジェント)はXとYを渡すとθが返ってきます。
座標(0.0, 0.0)と引数に渡した座標(x, y)を結んだ直線とx軸の角度(tanθ)がラジアンの値で返ってきます。

f:id:maplesyrup-cs6:20200111142808p:plain
Atan2はθが返ってくる


この値をMathf.Cos()に渡すとCosθが返ってきて、Mathf.Sin()に渡すとSinθが返ってきます。
それに_radiusの数字を掛けてあげることで、_radiusの半径サイズの円周上でのジョイスティックの可動限界値を求めることができます。

f:id:maplesyrup-cs6:20200111161650p:plain
円周上の位置を求める


そして最後にジョイスティックのローカルポジションの値を、Positionに代入しています。

Position = new Vector2(
    _joyStick.transform.localPosition.x / _radius,
    _joyStick.transform.localPosition.y / _radius
);


次はオブジェクトを動かしましょう🐼


==============
追記: あとからご指摘をいただき知ったのですが、
三平方の定理を使わなくてもUnityは便利なのでVector2.Distance(ポジションA, ポジションB)で求められるとのこと。
なので、CaluculateDistの中身、距離を求める部分はこれだけでいけるみたいです!便利。

public float CalculateDist(Vector2 jPos)
{
    float dist = Vector2.Distance(jPos, _defaultPos);
    return dist;
}

円形に可動範囲を求めた部分は、方向が分かっていればそのベクトルを正規化して半径かければ簡単にリミット求められるとのこと。
つまりアークタンジェントどうのなどの計算を使わずに、これ↓だけでいけるみたいです!すごいですね!

if (moveDist > _radius)
{
    Vector3 limitedPosition = Direction.normalized * _radius;
    _joyStick.transform.localPosition = limitedPosition;
}

==============


オブジェクトを動かす

🐼オブジェクトを動かす実装

次はジョイスティックの動きに合わせてオブジェクトを動かします。
シーンに動かしたいオブジェクトを入れます。
ジョイスティックは真ん中にあると邪魔なので移動させて、カメラもいい感じの位置にしました。

f:id:maplesyrup-cs6:20200111111610p:plain
ゲーム画面こんな感じ


進行方向をZ軸方向に向くようにします。モデルの進行方向がZ軸を向いてない場合は、空のゲームオブジェクトの子にして子オブジェクトのZ軸方向に向くようにします。

f:id:maplesyrup-cs6:20200111111701p:plain
進行方向(Z軸方向)を見るおまる(かわいい)


新しいC#スクリプトを作り、MovingObjという名前にして以下をスクリプトに貼り付けます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovingObj : MonoBehaviour
{
    [SerializeField]
    private JoyStick _joyStick;

    [SerializeField]
    private float speed;

    void Update()
    {
        if (!Input.GetMouseButton(0)) return;

        Vector3 pos = transform.position;

        Vector2 moveDist = _joyStick.Position;
        pos.x += moveDist.x * speed;
        pos.z += moveDist.y * speed;

        transform.position = pos;

        Vector3 lookDir = new Vector3(_joyStick.Direction.x, 0, _joyStick.Direction.y);
        transform.forward = lookDir;

    }
}


このスクリプトを動かしたいオブジェクトに付けて(空のゲームオブジェクトの子にオブジェクトを入れた場合は空のゲームオブジェクトの方につける)、
インスペクタから
JoyStickの所に最初に作ったジョイスティックのボタンをドラッグアンドドロップします。
Speedを設定します。ここでは0.1にしておきます。

f:id:maplesyrup-cs6:20200111123629p:plain
インスペクタから設定

これで再生すると

f:id:maplesyrup-cs6:20200111123800g:plain
動いた!


🐼オブジェクトを動かす解説

【動かす】

y軸は動かないように、x軸とz軸にジョイスティックのPositionの値の方向に進むように加算しています。speedを掛けることで速さを変えています。

Vector3 pos = transform.position;

Vector2 moveDist = _joyStick.Position;
pos.x += moveDist.x * speed;
pos.z += moveDist.y * speed;

transform.position = pos;
【進行方向を向かせる】

JoyStickのスクリプトのCulculateDist関数の中に、さりげなく
Direction = diff; と書いてありました。
現在のジョイスティックの位置からデフォルトの位置を引くことで、その方向が算出されるので、それをDirectionに入れてその値をMovingObjの方で使用しています。

JoyStick.cs

    public float CalculateDist(Vector2 jPos)
    {
        Vector2 diff = jPos - _defaultPos;
        Direction = diff;      //これ
        float dist = Mathf.Sqrt(Mathf.Pow(diff.x, 2) + Mathf.Pow(diff.y, 2));

        return dist;
    }


その方向を単純にxとzに入れてあげて、transform.forward(z軸方向)がそちらを向くようにしています。

Vector3 lookDir = new Vector3(_joyStick.Direction.x, 0, _joyStick.Direction.y);
transform.forward = lookDir;


以上です!!🐼