環境:Unity 2019.2.13f1
キャラクターをアニメーションさせながら移動させ、カメラも追従するようにします。キャラクターの移動方向はカメラ視点が変わっても入力方向と一致するようにします。
準備
ボーンの入ったプレイヤーモデルとアニメーションのFBXを用意。今回は待機、歩き、走りモーションの3つをブレンドして使うことにしました。
プレイヤーの構造
Create EmptyでPlayerというオブジェクトをつくり、そこにプレイヤーモデルをぶらさげる。Playerにプレイヤーを動かすスクリプトをアタッチし、モデルにアニメーターコントロールをアタッチする構造になる。
TagにPlayerを設定しておく。後々スクリプトで参照する。
アセット内にAnimator Controllerをつくる。続いてCreate State > From New Blend Treeでブレンドツリーをつくる。
ブレンドツリーを開き、アニメーションを追加していく。事前にモーションアセット内でアニメーションの名前を変更しておくとわかりやすくなる。ここではidleAnim、walkAnim、runAnimという名前にした。切り替えるアニメーションのタイミングを数値で設定する。歩くアニメーションを早めのタイミングで切り替えたかったので0.15に設定。
アニメーションが原点から移動する場合はLoop Timeにチェックし、Loop Poseにもチェックを入れる。Applyを押して変更を決定することを忘れずに。
続いてスクリプト。カメラもプレイヤーも互いのスクリプトを参照し合う仕組みです。
カメラのスクリプト
ゲームパッドの右スティックでカメラをプレイヤーのまわりを回転するようにする。
ゲームパッドの右スティックを有効にする
メニューのEdit > Project Settings > Input
Sizeを2足して20にする。Horizontal2とVertical2という項目名にして画像のように数字を設定する。
コードは以下。CameraControllerという名前で作成。プレイヤーの座標を参考にしてオフセットする。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
// 外部オブジェクトの参照
GameObject player;
public Vector3 cameraRotation = new Vector3();
Vector3 currentCamRotation = new Vector3();
public float dist = 4.0f;
Vector3 currentLookAtPos = new Vector3();
// Start is called before the first frame update
void Start()
{
player = GameObject.FindGameObjectWithTag("Player");
}
// Update is called once per frame
void Update()
{
// ゲームパッド右スティックからの値を加算する
cameraRotation += new Vector3(-Input.GetAxis("Vertical2"), -Input.GetAxis("Horizontal2"), 0) * 2.0f * Time.deltaTime;
// X軸回転の制限
cameraRotation.x = Mathf.Clamp(cameraRotation.x, 15 * Mathf.Deg2Rad, 60 * Mathf.Deg2Rad);
// 遅延用の角度との差分をとる
Vector3 diff = cameraRotation - currentCamRotation;
currentCamRotation += WrapAngle(diff) * 0.2f;
// 角度からベクトルを計算する
Vector3 craneVec = new Vector3
(
Mathf.Cos(currentCamRotation.x) * Mathf.Cos(currentCamRotation.y),
Mathf.Sin(currentCamRotation.x),
Mathf.Cos(currentCamRotation.x) * Mathf.Sin(currentCamRotation.y)
);
// 注視点の座標
Vector3 lookAtPos = player.transform.position + new Vector3(0, 1, 0);
currentLookAtPos += (lookAtPos - currentLookAtPos) * 0.2f;
// カメラの座標を更新する
this.transform.position = currentLookAtPos + craneVec * dist;
// プレイヤーの座標にカメラを向ける(これは最後にする)
this.transform.LookAt(currentLookAtPos);
}
// 角度を0~360°に収める関数
Vector3 WrapAngle(Vector3 vector)
{
vector.x %= Mathf.PI * 2;
vector.y %= Mathf.PI * 2;
vector.z %= Mathf.PI * 2;
return vector;
}
}
プレイヤーのスクリプト
PlayerControllerという名前で作成。カメラの回転値を参照して進む方向を計算する。またAnimator Controllerに速度の値を渡す。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 2.0f;
public float rotationSpeed = 360.0f;
GameObject camera; // 外部のオブジェクトを参照する
Animator animator;
// Start is called before the first frame update
void Start()
{
camera = GameObject.FindGameObjectWithTag("MainCamera");
animator = this.GetComponentInChildren<Animator>();
}
// Update is called once per frame
void Update()
{
// ゲームパッドの左スティックのベクトルを取得する
Vector3 direction = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// 他のオブジェクトのスクリプトを読み込む(スクリプトはクラス扱い)
CameraController cameraScript = camera.GetComponent<CameraController>();
// カメラのY軸角度から回転行列を生成する
Quaternion rot = Quaternion.Euler(0, cameraScript.cameraRotation.y * Mathf.Rad2Deg + 90, 0);
// 逆行列を生成する
Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, rot, Vector3.one);
Matrix4x4 inv = m.inverse;
// 回転行列をかけたベクトルに変換する
direction = inv.MultiplyVector(direction);
if (direction.magnitude > 0.001f)
{
// Slerpは球面線形補間、Vector3.Angleは二点間の角度(degree)を返す
Vector3 forward = Vector3.Slerp(this.transform.forward, direction, rotationSpeed * Time.deltaTime / Vector3.Angle(this.transform.forward, direction));
// ベクトル方向へ向かせる
transform.LookAt(this.transform.position + forward);
}
// 座標移動させる
transform.position += direction * moveSpeed * Time.deltaTime;
// アニメーターコントローラへ値を渡す
animator.SetFloat("Blend", direction.magnitude);
}
}