したかみ ぶろぐ

Unity成分多め

振り向きながら動く移動を考える

はじめに

一年生向けのUnity勉強会での話です。移動スクリプトの内容をしている際に今なら凝ったプログラムが作れるんじゃね?って思ったのが今回のきっかけです。

簡単な移動スクリプト

一部勉強会の内容をお借りしました。簡単なプログラムになります。

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

public class UnityChan_Run : MonoBehaviour {

    private Animator animator;
    [SerializeField]
    private float walkSpeed;

    private int run_id;

    // Use this for initialization
    void Start () {
        animator = GetComponent<Animator>();
        run_id = Animator.StringToHash("Run");
    }
    
    // Update is called once per frame
    void Update () {

        if (Input.GetKey(KeyCode.W)) {
            transform.eulerAngles = new Vector3(0, 0, 0);
            transform.position += transform.forward * walkSpeed * Time.deltaTime;
            animator.SetBool(run_id, true);
        }
        else if (Input.GetKey(KeyCode.D)) {
            transform.eulerAngles = new Vector3(0, 90, 0);
            transform.position += transform.forward * walkSpeed * Time.deltaTime;
            animator.SetBool(run_id, true);
        }
        else if (Input.GetKey(KeyCode.S)) {
            transform.eulerAngles = new Vector3(0, 180, 0);
            transform.position += transform.forward * walkSpeed * Time.deltaTime;
            animator.SetBool(run_id, true);
        }
        else if (Input.GetKey(KeyCode.A)) {
            transform.eulerAngles = new Vector3(0, -90, 0);
            transform.position += transform.forward * walkSpeed * Time.deltaTime;
            animator.SetBool(run_id, true);
        }
        else {
            animator.SetBool(run_id, false);
        }
    }
}

結果

f:id:vxd-naoshi-19961205-maro:20190618021201g:plain


少し解説するとW,A,S,Dを押すことで走るアニメーションが再生され、指定された向きを向いてから前方向に移動します。

gif動画を見て頂けるとわかると思いますが、違う向きに移動する際カクカクになっています。



ちょっと複雑な移動スクリプト

先程のカクカクな移動を改善しようと思います。

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

public class UnityChan_Run : MonoBehaviour {

    private Animator animator;
    [SerializeField]
    private float walkSpeed;
    [SerializeField, Range(0.1f, 50f)]
    private float angleSpeed;
    [SerializeField]
    private float power;
    private int run_id;

    private readonly float epsilon = 0.00001f;

    // Use this for initialization
    void Start () {
        animator = GetComponent<Animator>();
        run_id = Animator.StringToHash("Run");
    }
    
    // Update is called once per frame
    void Update () {

        Vector3 walkDirection = Vector3.zero;

        if (Input.GetKey(KeyCode.W))
            walkDirection.z += 1;
        if (Input.GetKey(KeyCode.S))
            walkDirection.z -= 1;
        if (Input.GetKey(KeyCode.D))
            walkDirection.x += 1;
        if (Input.GetKey(KeyCode.A))
            walkDirection.x -= 1;


        if (walkDirection.sqrMagnitude > epsilon) {
            walkDirection = walkDirection.normalized;
            animator.SetBool(run_id, true);
            Quaternion q = Quaternion.LookRotation(walkDirection.normalized, Vector3.up);
            transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * angleSpeed);
            float speed = walkSpeed * Mathf.Pow(Mathf.Max(0, Vector3.Dot(transform.forward, walkDirection)), power);
            transform.position += Time.deltaTime * transform.forward * speed;
        }
        else {
            animator.SetBool(run_id, false);
        }
        
    }
}

結果

f:id:vxd-naoshi-19961205-maro:20190618021645g:plain

このプログラムで振り向きながら歩くことが出来ました。後輩から「ぬるぬる動いてる!」って感想を頂いて達成感ありました。


解説

始めに移動する方向について解説します

Vector3 walkDirection = Vector3.zero;

if (Input.GetKey(KeyCode.W))
    walkDirection.z += 1;
if (Input.GetKey(KeyCode.S))
    walkDirection.z -= 1;
if (Input.GetKey(KeyCode.D))
    walkDirection.x += 1;
if (Input.GetKey(KeyCode.A))
    walkDirection.x -= 1;

ここではボタンが押された方向を加算しています。こうすることによって例えば上方向と右方向押されたときは右上に移動、左方向と右方向押されたときは移動しないということが表現できます。

移動するときはベクトルの大きさが0以上になり、移動しないときは0となります。


次に回転について解説します。

f:id:vxd-naoshi-19961205-maro:20190618031100p:plain

Quaternion q = Quaternion.LookRotation(walkDirection.normalized, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * angleSpeed);

Quaternion.LookRotation関数でベクトル方向の回転を取得することが出来ます。この回転とSlerpを使って少しずつ指定された方向を向くようになっています。

angleSpeed変数をいじることで回転スピードを調整できます。gif動画ではangleSpeed = 5.6に設定されています。



次に移動についてです。

float speed = walkSpeed * Mathf.Pow(Mathf.Max(0, Vector3.Dot(transform.forward, walkDirection)), power);
transform.position += Time.deltaTime * transform.forward * speed;

複雑になっているので頑張って少しずつ解説します。

f:id:vxd-naoshi-19961205-maro:20190618031527p:plain Vecotor3.Dot(transform.forward, walkDirection)でどれくらい移動方向を向いているか計算しています。移動方向を向けば向くほど1に近づきます。


f:id:vxd-naoshi-19961205-maro:20190618031654p:plain

Mathf.Max(0, Vector3.Dot(transform.forward, walkDirection))では値域を0~1に変更しています。こうしないと内積が負の時、後ろに走り出してしまいます!


Mathf.Pow(Mathf.Max(0, Vector3.Dot(transform.forward, walkDirection)), power)で先の式の値が0に近ければさらに0に近づくようになります。こうすることにより移動方向を向いていないときはほとんど移動しないようになります。

power変数をいじることでさらに移動できないようにしたり、少しは移動できるように変更できます。gif動画ではpower = 3と設定されています。


この箇所以外は見慣れた移動プログラムになると思います。gif動画ではwalkSpeed = 4.5です。



まとめ

普段VRコンテンツを開発してきたので移動プログラムを考えたことがほとんどなかったので楽しかったです。今どきのゲームはほとんど綺麗な歩行を行うので少しでもそれに近づけるために少し試行錯誤してみました。さらにクオリティを上げるためにはゆっくり歩くアニメーション、振り向くアニメーション、ブレーキをかけるアニメーション等を組み合わせる必要があると思います。

ちなみに私が一番きれいに歩くなって感じたゲームはGravity Dazeです。


PS4®『GRAVITY DAZE 2』プレイ映像 -街で遊んでみた-



ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています