tsugaru’s blog

主に技術的なことをつぶやきます。プログラミング,ポケモンGO,音楽,アニメ等

unityにおけるTPS風移動、カメラ移動の実装(Caracter Controllerを使用)

Unityはまだ慣れていませんので、参考程度にしてください。

Character Controllerを用いて以下のように実装しました。 詳しくは以下のコード中に書いてあります。

using System.Collections;
using UnityEngine;

/*
 * 構成図
 * root [このスクリプト、CharacterControll、Animator]
 *  |- Camera
 *  |- Initial Camera Transform [からのオブジェクト]
 *  |- Rotate Body[主に カメラ基準にした時の回転方向。からのオブジェクト。しばしばモデルそのもののルート]
 *      |- モデルの構造体
 *      
 * マウスの左右でカメラ視点切り替え
 * 十字キーでカメラ視点方向を基準に8方向に動く
 * character controllerを用いるため、超えられる段差の調整はそちらで行う
 * character controllerを用いるため、rigidbodyなどは基本使わない
 * 体がカメラ視点方向を基準とするタイミングはなんらかの十字キーを押したタイミング
 * カメラはキャラクターの上下移動に対し、画角の基準になるまでは動かない
 * ジャンプ中も移動が可能
 * キックアクション中は地上にいる時移動が止まるが、ジャンプ中は移動できる(大技に改造できそう)
 * animationは各自自由に設定する
 * 
 * 未実装など
 * カメラの上下回転移動
 * カメラ基準にモデルが戻る時の回転のスムースと、モデルが180度方向が変わる時のスムース
 * カメラ移動のキャラクターに対するスムース
 */

public class PlayerController : MonoBehaviour
{
    CharacterController controller;
    Animator animator;
    [SerializeField] float walkSpeed=2;
    [SerializeField] float runSpeed = 5;
    [SerializeField] float accel = 5;
    [SerializeField] float blake = 10;
    [SerializeField] float maxAngle = 45;
    [SerializeField] Transform rotateBody;
    [SerializeField] float cameraRotateSpeed=10;
    [SerializeField] Transform playerCamera;
    [SerializeField] Transform initialCameraPos;
    [SerializeField] [Tooltip("ジャンプの初速度")]
    float jumpVelocity = 13.5f;
    float rotateAngle = 0;
    float verticalVelocity = 0;
    float oldCameraY;
    bool canMove = true;//地面に触れている時、falseなら入力を受け付けない

    [SerializeField,Header("Not Changeable.For Debug")]float speed=0;
    void Awake()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
        oldCameraY = playerCamera.localPosition.y;
    }
    IEnumerator Kick()
    {
        canMove = false;
        animator.SetTrigger("Kick");
        yield return new WaitForSeconds(0.5f);
        canMove = true;
    }

    void Update()
    {
        CameraMove();
        Move();
        CameraHeight();
        if (Input.GetKeyDown(KeyCode.Mouse0)&&canMove)
        {
            StartCoroutine(Kick());
        }   
    }
    void CameraMove()
    {
        float h = Input.GetAxis("Mouse X");
        playerCamera.RotateAround(transform.position, Vector3.up, h * cameraRotateSpeed);
        if (Input.GetKeyDown(KeyCode.Z))
        {
            //キャラクターの前の方向にカメラの方向を合わせる
            // todo
            //transform.localRotation=
        }
        oldCameraY=playerCamera.position.y;
    }

    void CameraHeight()
    {

        //上下でプレイヤーが画伯から出そうになるまでカメラは動かない
        Vector3 pos = playerCamera.position;
        Vector3 oldPos = pos;
        pos.y = oldCameraY;
        playerCamera.position = pos;

        Vector3 p = Camera.main.WorldToViewportPoint(transform.position);
        if ( p.y<0 || 0.25f <p.y)
        {
            // 画伯からはみ出た、もしくは画伯に入ろうとしているとき
            //カメラの高さはプレイヤーの動きに付随する
            playerCamera.position = oldPos;
        }
    }
    private void Move()
    {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        if (!canMove&&controller.isGrounded)
        {
            // ジャンプ中のキックは、移動しながらのアクションを認める
            x = 0;z = 0;
        }
        float magnitute = x * x + z * z;
 
        if (magnitute != 0)
        {
            //Debug.Log("move x:"+x+"z:"+z);
            //動いたら全体をカメラの方向に向け、カメラの位置をリセットする。
            Vector3 bodyAngle = transform.localEulerAngles;
            bodyAngle.y += playerCamera.localEulerAngles.y;
            transform.localEulerAngles = bodyAngle;

            // yは回転に関与していないのでリセットしない。
            Vector3 pos = playerCamera.position;
            pos.x = initialCameraPos.position.x;
            pos.z = initialCameraPos.position.z;
            playerCamera.position = pos;
            playerCamera.rotation = initialCameraPos.rotation;

            
            if (Input.GetKey(KeyCode.LeftShift))
            {
                // シフトキーを押して加速する時
                speed += accel * Time.deltaTime;
                speed = Mathf.Clamp(speed, 0, runSpeed);
            }
            else if (speed <= walkSpeed)
            {
                // 歩くスピードまで加速する時
                speed += accel * Time.deltaTime;
                speed = Mathf.Clamp(speed, 0, walkSpeed);
            }
            else
            {
                //歩くスピードを超えて走っていて減速する時
                speed -= blake * Time.deltaTime;
                speed = Mathf.Clamp(speed, 0, runSpeed);
            }
            rotateAngle = Mathf.Clamp(Mathf.Atan2(x, z), -maxAngle, maxAngle);           
        }
        else if(speed>0)
        {
            // 何も押していない時
            speed -= blake * Time.deltaTime;
            speed = Mathf.Clamp(speed, 0, runSpeed);
        }

        rotateBody.localEulerAngles = new Vector3(0, rotateAngle, 0) * Mathf.Rad2Deg;
        animator.SetFloat("Speed", speed);
        Vector3 move = transform.rotation * new Vector3(x, 0, z) * speed;


        if (controller.isGrounded)
        { 
            verticalVelocity = 0;
            if (Input.GetKeyDown(KeyCode.Space))
            {
                verticalVelocity += jumpVelocity;
                animator.SetTrigger("Jump");
            }
        }
        else
        {
            animator.SetFloat("Speed", 0);
            //地面についていないときは加速を続ける
            verticalVelocity += Physics.gravity.y*Time.deltaTime;
            verticalVelocity = Mathf.Clamp(verticalVelocity, 0, jumpVelocity);
        }
        
        move.y = verticalVelocity;
        controller.Move((move+ Physics.gravity)* Time.deltaTime);
    }
   
}

f:id:pika-bika:20210519142600p:plain
アニメーション
f:id:pika-bika:20210519142604p:plain
歩行などのブレンドツリー