3Dアクションゲーム編
Lesson2 カメラを実装しよう
プレイヤーを追尾するカメラを実装します。また、カメラを考慮してプレイヤーの移動も変更し、ゲームの基礎となるシステムを完成させましょう。
2-1
2-1 追尾するカメラ
1
追尾処理と
回転処理
Unityにも「カメラ」という概念があり、カメラに映ったものが実際のゲーム画面に描画されます。テレビ番組のカメラマンをイメージしてもらうとわかりやすいかと思います。
現在、カメラは初期位置から全く動きません。プレイヤーへの追尾と回転ができるカメラを作りましょう。
新しいスクリプト「GameCamera」を追加してください。スクリプトの作り方を忘れた人は1-3を参考にしてください。

GameCameraスクリプトを以下のように入力してください。少し長いですが、これだけでカメラの処理は完成になります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameCamera : MonoBehaviour
{
public float RotSpeed = 0.5f;
public float RotUpLimit = 40.0f;
public float RotDownLimit = -20.0f;
public float CameraRange = 3.0f;
public float CameraY_Up = 1.5f;
private GameObject m_player;
private float m_nowX_Rot = 0.0f;
void Start()
{
// Playerタグがついたオブジェクトを探す
m_player = GameObject.FindGameObjectWithTag("Player");
// 初期X軸の回転量を保存
m_nowX_Rot = transform.localEulerAngles.x;
}
void Update()
{
// 上下
float Up_rot = 0.5f;
if (Input.GetKey(KeyCode.UpArrow))
{
Up_rot = RotSpeed;
}
else if (Input.GetKey(KeyCode.DownArrow))
{
Up_rot = -RotSpeed;
}
else
{
Up_rot = 0.0f;
}
// 上下角度制限
m_nowX_Rot += Up_rot;
if (m_nowX_Rot > RotUpLimit || m_nowX_Rot < RotDownLimit)
{
m_nowX_Rot = Mathf.Clamp(m_nowX_Rot, RotDownLimit, RotUpLimit);
Up_rot = 0.0f;
}
transform.RotateAround(m_player.transform.position, this.transform.right, Up_rot);
// 左右
float Left_rot;
if (Input.GetKey(KeyCode.LeftArrow))
{
Left_rot = RotSpeed;
}
else if (Input.GetKey(KeyCode.RightArrow))
{
Left_rot = -RotSpeed;
}
else
{
Left_rot = 0.0f;
}
transform.RotateAround(m_player.transform.position, Vector3.up, Left_rot);
// 座標計算
// カメラの前方向を使って移動量を計算
Vector3 cameraMove = transform.forward * -CameraRange;
// カメラを少し持ち上げる
cameraMove.y += CameraY_Up;
// 座標設定
transform.position = m_player.transform.position + cameraMove;
}
}
【プログラムの解説】
・Start関数にある GameObject.FindGameObjectWithTag("Player") は、引数に設定した名前のタグ(今回はPlayer)が設定されているオブジェクトを取得する関数です。ただし同じタグが設定されているオブジェクトが複数ある場合は、どのオブジェクトの情報を返すか不定なので注意しましょう。

・Mathf.Clamp関数は数値を範囲内に収めてくれる関数です。第一引数に対象となる値、第二引数に下限、第三引数に上限を入力します。戻り値に丸めた結果の値が返ってきます。

2
プレイヤータグを
設定
・transform.RotateAround関数は第一引数に入力した座標を中心に回転させる関数です。今回はプレイヤーを中心に第二引数(Y軸周り)に回転させています。
このスクリプトは「Player」タグがついたオブジェクトをターゲットにします。まだゲーム上にPlayerタグがついたオブジェクトがありませんので、このままではエラーが起きてしまいます。
カメラに注目してほしいのは当然ユニティちゃんなので、ユニティちゃんにPlayerタグを設定しましょう。PlayerタグはUnity側がデフォルトで用意してくれています。

3
スクリプトを
アタッチ
忘れないようにスクリプトのアタッチをしましょう。MainCameraにGameCameraスクリプトをドラッグ&ドロップしてください。

これでカメラがプレイヤーについてくるようになりました。
例のごとくpublicに設定した変数はインスペクターから変更できます。好みに調整しても構いません。
RotSpeed … カメラの回転速度
RotUpLimit … カメラの回転上限
RotDownLimit … カメラの回転下限
CameraRange … プレイヤーとカメラの距離
CameraY_Up … カメラの高さ
MainCameraのCameraコンポーネントからField of View(画角)やNear・Far(近平面・遠平面)も設定できます。何のこっちゃいという人はゲームプログラミングの教材で復習してみてください。

Unity Tips!


2-2
2-2 プレイヤーの移動を修正
1
カメラの回転を
使って移動する
これでカメラの処理は完成しました。
しかし、カメラを回転させるとユニティちゃんの移動がおかしくなってしまいます。ゲームプログラミングの授業でも起こりましたが、ユニティちゃんがカメラを考慮して移動していないのでこのような現象が起こってしまいます。よって解決方法も全く同じです。



ゲームプログラミングではパッドを使いましたが、キーボード操作でも考え方は変わりません。カメラの前方向と右方向を使ってプレイヤーの移動ベクトルを計算しましょう。
Playerスクリプトを開いて赤い部分のコードを記述してください。
※移動と回転の処理が変わっているので注意!!古い処理は消してから記述してください!
2
移動速度を調整
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float MoveSpeed = 0.01f;
public float JumpPower = 6.0f;
Rigidbody m_rigidBody;
Animator m_playerAnimator;
GameObject m_mainCamera;
// 接地判定用スクリプト
public GroundChecker Ground_Checker;
bool m_moveFlag, m_jumpFlag, m_airFlag;
void Start()
{
// 自分にアタッチされているRigidBodyを取得する
m_rigidBody = GetComponent<Rigidbody>();
// 自分にアタッチされているAnimatorを取得する
m_playerAnimator = GetComponent<Animator>();
// メインカメラのゲームオブジェクトを取得する
m_mainCamera = Camera.main.gameObject;
}
~中略~
// 左右移動
if (Input.GetKey(KeyCode.D))
{
move.x += MoveSpeed;
}
if (Input.GetKey(KeyCode.A))
{
move.x += -MoveSpeed;
}
// カメラを考慮した移動
Vector3 PlayerMove = Vector3.zero;
Vector3 forward = m_mainCamera.transform.forward;
Vector3 right = m_mainCamera.transform.right;
forward.y = 0.0f;
right.y = 0.0f;
right *= move.x;
forward *= move.z;
// 移動速度に上記で計算したベクトルを加算する
PlayerMove += right + forward;
// 移動させる
transform.position += PlayerMove * Time.deltaTime;
// 移動フラグの更新
if (PlayerMove.sqrMagnitude == 0.0f)
{
m_moveFlag = false;
}
else
{
m_moveFlag = true;
}
// 回転
if (PlayerMove.sqrMagnitude > 0.0f)
{
transform.rotation = Quaternion.LookRotation(PlayerMove.normalized);
}
// ジャンプ
【プログラムの解説】
・Camera.main.gameObject でメインカメラ(画面に表示しているカメラ)のゲームオブジェクトを取得できます。
・Time.deltaTimeは1つ前のフレームから今のフレームを実行するまでにかかった時間を返します。k2Engineと同じようにタイマーに使うこともできます。
Time.deltaTimeを使うことによって、もしゲームが重くなっても移動速度が一定になるようになります。
Time.deltaTimeを追加したことで移動速度が遅くなっています。MoveSpeedの値を調整してください。5あたりがオススメです。

これでカメラを考慮した移動が完成しました。ゲームをプレイして確認してみてください。
Time.deltaTimeは移動処理を安定して動作させるために欠かせないものですが、最初はいつ使えばいいかわからない人も多いと思います。
Update関数は毎フレーム実行される関数です。つまりFPSが60なら1秒間に60回、FPSが10なら1秒間に10回実行されます。
例えばUpdate関数にX座標を+1する処理を書いていた場合、FPS60では1秒間でX座標は+60されますが、FPS10の環境では1秒間でX+10になってしまいます。このようにFPSの違いによって、実行結果に差が出る場合にTime.deltaTimeを使ってください。

最初は使いどころをイメージしづらいかもしれませんが、上の図を思い出して「1秒間における実行回数が変わると結果が変わりそうだな」と思った時に使うようにしてみてください。
不安な時はテストプレイとして意図的にFPSを落とすなどして確認してみましょう(デバッグ用にFPSを意図的に落とす方法)
Unity Tips!


まとめ
・GameObject.FindGameObjectWithTag("Player") で「Player」というタグがついたオブジェクトを検索して、見つかったオブジェクトを取得することができる
同じタグがついたオブジェクトが複数ある場合、どれが返ってくるかは不確定なので注意
・Mathf.Clamp 関数は値を範囲内に収めてくれる関数
・Time.deltaTime は前フレームから現在のフレームまでの更新にどのくらいかかったかを返す
正しく使わないと環境によって移動速度が変化したり、タイマーの時間が変わったりしてしまう
評価テスト
【評価テスト2】