【Unity】スマホ用のFPSコントローラ(PCデバッグ可能)。縦方向のカメラ回転制限を実装しました

2023年4月15日

どうも、だらはです。
今回はコピペで動作するスマホ用のFPSコントローラの作り方を紹介します。
PCでも動かせるのでデバッグもしやすくお勧めです。

スポンサーリンク

実装方法

早速ですが実装方法を記載していきます。

  1. [UI -> Panel]からPanelを作成してDragLeftDragRightと命名。DragHandlerスクリプトをアタッチする。
  2. [Create Empty]からGameObjectを作成してPlayerと命名。Playerスクリプトをアタッチする。
  3. 2のPlayerスクリプトに対して、MoveControllerDragLeftを、LookControllerDragRightを、Cameraカメラを、ObjPlayerModelプレイヤー用モデルを設定する。(図1参考)

以上です。

◆図1

◆DragHandler.csスクリプト

using System;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public Action<PointerEventData> OnBeginDragEvent;

    public Action<PointerEventData> OnDragEvent;

    public Action<PointerEventData> OnEndDragEvent;

    //自身が所属してるキャンバス
    private Canvas _belongedCanvas;

    public void OnBeginDrag(PointerEventData eventData)
    {
        OnBeginDragEvent?.Invoke(eventData);
    }

    public void OnDrag(PointerEventData eventData)
    {
        OnDragEvent?.Invoke(eventData);
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        OnEndDragEvent?.Invoke(eventData);
    }

    //スクリーン座標を自身が所属してるキャンバス上の座標に変換
    public Vector2 GetPositionOnCanvas(Vector2 pointerPos)
    {
        if (_belongedCanvas == null)
        {
            _belongedCanvas = GetBelongedCanvas(transform);
        }
        RectTransformUtility.ScreenPointToLocalPointInRectangle(_belongedCanvas.transform as RectTransform, pointerPos, _belongedCanvas.worldCamera, out Vector2 localPointerPos);
        return localPointerPos;
    }

    //所属するCanvasを取得
    private Canvas GetBelongedCanvas(Transform t)
    {
        if (t == null)
        {
            return null;
        }

        var canvas = t.GetComponent<Canvas>();
        if (canvas != null)
        {
            return canvas;
        }

        return GetBelongedCanvas(t.parent);
    }
}

◆Player.csスクリプト

using UnityEngine;
using UnityEngine.EventSystems;

public class Player : MonoBehaviour
{
    //移動操作を受け付けるタッチエリア
    [SerializeField]
    private DragHandler _moveController;

    //移動速度(m/秒)
    [SerializeField]
    private float _movePerSecond;

    //移動操作としてタッチ開始したスクリーン座標
    private Vector2 _movePointerPosBegin;
    private Vector3 _moveVector;

    [SerializeField]
    private Camera _camera;

    //カメラ操作を受け付けるタッチエリア
    [SerializeField]
    private DragHandler _lookController;

    //カメラ速度(°/px)
    [SerializeField]
    private float _angularPerPixel = 1f;

    //カメラ操作として前フレームにタッチしたキャンバス上の座標
    private Vector2 _lookPointerPosPre;

    //アニメーター制御用
    [SerializeField]
    GameObject objPlayerModel;
    Animator animPlayer;

    private void Awake()
    {
        _moveController.OnBeginDragEvent += OnBeginDragMove;
        _moveController.OnDragEvent += OnDragMove;
        _moveController.OnEndDragEvent += OnEndDragMove;
        _lookController.OnBeginDragEvent += OnBeginDragLook;
        _lookController.OnDragEvent += OnDragLook;
    }

    void Start()
    {
        animPlayer = objPlayerModel.GetComponent<Animator>();
    }

    private void Update()
    {
        UpdateMove(_moveVector);
    }

    // 移動操作
    #region Move

    //ドラッグ操作開始(移動用)
    private void OnBeginDragMove(PointerEventData eventData)
    {
        //タッチ開始位置を保持
        _movePointerPosBegin = eventData.position;
    }

    //ドラッグ操作中(移動用)
    private void OnDragMove(PointerEventData eventData)
    {
        //タッチ開始位置からのスワイプ量を移動ベクトルにする
        var vector = eventData.position - _movePointerPosBegin;
        _moveVector = new Vector3(vector.x, 0f, vector.y);
    }

    private void UpdateMove(Vector3 vector)
    {
        //現在向きを基準に、入力されたベクトルに向かって移動
        Vector3 nextPos = transform.rotation * vector.normalized * _movePerSecond * Time.deltaTime;
        float tagP = Mathf.Pow(transform.position.x + nextPos.x, 2) + Mathf.Pow(transform.position.y + nextPos.y, 2) + Mathf.Pow(transform.position.z + nextPos.z, 2);
        float nowP = Mathf.Pow(transform.position.x, 2) + Mathf.Pow(transform.position.y, 2) + Mathf.Pow(transform.position.z, 2);

        //アニメーションにSpeedを渡す(平方根の計算が重いかも)
        animPlayer.SetFloat("Speed", Mathf.Sqrt((Mathf.Abs(tagP - nowP))));
        transform.position += nextPos;
    }

    //ドラッグ操作終了(移動用)
    private void OnEndDragMove(PointerEventData eventData)
    {
        //移動ベクトルを解消
        _moveVector = Vector3.zero;
    }
    #endregion



    //カメラ操作
    #region Look

    private void OnBeginDragLook(PointerEventData eventData)
    {
        _lookPointerPosPre = _lookController.GetPositionOnCanvas(eventData.position);
    }

    private void OnDragLook(PointerEventData eventData)
    {
        var pointerPosOnCanvas = _lookController.GetPositionOnCanvas(eventData.position);
        //キャンバス上で前フレームから何px操作したかを計算
        var vector = pointerPosOnCanvas - _lookPointerPosPre;
        //操作量に応じてカメラを回転
        LookRotate(new Vector2(-vector.y, vector.x));
        _lookPointerPosPre = pointerPosOnCanvas;
    }

    private void LookRotate(Vector2 angles)
    {
        Vector2 deltaAngles = angles * _angularPerPixel;
        transform.eulerAngles += new Vector3(0f, deltaAngles.y);

        //縦方向の回転角度制限
        Vector3 mXAxiz = _camera.transform.localEulerAngles;
        var x = mXAxiz.x + deltaAngles.x;

        if (x >= 180)
        {
            x = x - 360;
        }

        if (x >= -60 && x <= 60)
        {
            mXAxiz.x = x;
            _camera.transform.localEulerAngles = mXAxiz;
        }
    }

    #endregion
}

補足

先ず、今回の方法は以下のHPで紹介されているスクリプトに対して、以下の機能を追加しました。

  • アニメーター制御機能。(移動すると歩くようにアニメーションさせるため)
  • 縦方向のカメラ回転制限。(制限しないとストレスフルのため)

◆参考とさせて頂きました神HP

もしプレイヤー用モデルを使わない場合はスクリプトのアニメーターに関する記述をコメントアウトしてください。
縦方向のカメラ回転制限は60度としています。適宜、値を変更して頂けたらと思います。

最後に

いかがでしたでしょうか。
今回紹介したスクリプトを使うことで、アセットを使うことなくコピペでFPSコントローラが作れます。
今後は、コントローラのジョイスティックなどを作れたら作っていきたいと思います!

以上、だらはでした。

追記(2022.3.31)

画面にジョイスティックを追加する方法についてまとめました。
超簡単に実装可能なので、参考にして頂けたら嬉しいです:)

◆スマホ用のFPSコントローラとジョイスティックを連携する方法

追記(2023.4.15)

スワイプ量に比例して移動量を調整できるよう機能追加しました。

スポンサーリンク

応用

Posted by daraha_gm