【Unity】カメラの移動を考える

長い間、冬眠ならぬ初夏眠しておりました。
2カ月も放置する事になるとは…
(..*) オハズカシイ・・

休んでる間に沢山の方に見に来ていただいて、
これではイカンなと思いまして、PCを立ち上げた次第です。

暫くPCから離れていて何も出来てないので、
ブログの内容がショボイですが、よろしくお願い致します。_(._.)_ ユルシテ


さて、更新と言う事なんですが…

アクションの続きを書きたい所なんですが、
作成や検証ができてないので、続きを書く事ができません。

そこで、今まで触れてこなかった、
カメラワークを記事にしてみようと思います。


アクションでは、キャラの移動に合わせて、
カメラや背景を動かす必要があります。

シューティング系なら背景移動がメインなので、固定もできるのですが、
アクションの場合は、そうもいきませんよね~ (-_-)ゞ゛ウーム
なので、少し検証をしてみようと思います。

まずカメラの移動を考える際、二つのやり方があります。

・スクリプトを組まずに移動させる
・スクリプトで移動させる


スクリプトを組まずに移動させる
これは、一番簡単で処理が楽になるパターンです。

キャラの中にメインカメラを入れてしまう方法です。
カメラが子になるので、必然的に追従してくれます。

操作で移動させるような、例えば、ドラクエのマップ移動
これならスクリプトを組まなくても処理できます。

しかし…
アクションでは、移動の際に見えてしまうと困る部分もあります。
床や天井の裏側ですね~

また、小刻みに動くとカメラも連動するので、
正直、酔います(;^_^A アセアセ・・・

そこでカメラをコントロールする為のスクリプトを考えてみようと思います。
当然、ステージが必要になるので、
カメラワーク①.jpg
以前に作ったボールアクションのテストステージを流用して、
スタートからゴールまでをカメラで追いかけてみたいと思います。

ボールは、右方向に強制スクロールさせています。
CameraMove

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

public class CameraMove : MonoBehaviour
{
[Header("球本体")]
public GameObject ball;

private float ballX = 0.0f; //球のX座標取得
private float cameraY = 0.0f; //カメラのY座標取得
private float cameraZ = 0.0f; //カメラのZ座標取得
private float offsetX = 0.0f; //キャラとカメラのX座標オフセット


// Start is called before the first frame update
void Start()
{
//カメラとボールのX軸差をオフセットとして取り込む
offsetX = transform.position.x - ball.transform.position.x;

//Y・Z軸は操作しないので初期位置を取得しておく
cameraY = transform.position.y;
cameraZ = transform.position.z;

//初期のボールX軸を取得する
ballX = ball.transform.position.x;
}

// Update is called once per frame
void Update()
{
//ボールが右移動してる時だけボールX座標を取得する
if (ballX < ball.transform.position.x)
ballX = ball.transform.position.x;

//ボールとのオフセットを計算して座標移動させる
transform.position = new Vector3(ballX + offsetX, cameraY, cameraZ);
}
}

このスクリプトをカメラにアタッチして、
publicな変数ballにボールオブジェクトをアタッチします。

スクリプト内に説明を書いておいたので、分かると思いますが、
ボールが右移動した時だけカメラも移動させるようにしています。

これで壁ジャンプの小刻みな左移動でもカメラが動く事がなくります。

Y・Z軸を初期座標を固定にしましたが、
Y軸はジャンプに影響されないようにする為です。

Z軸なんですが、2Dとは言えZ軸が安定しないと
カメラに表示されない事が起こります。

Vector3でもX・Y軸のみの入力で移動させる事ができるのですが、
オブジェクトを追加したり、カメラを追加したりすると、
なぜかZ軸に0が代入されてカメラが表示しないなんて事が起こったりします。

なので、Z軸の初期位置も保険として取得しておくと
回避できるかと思います。


X軸のオフセットを設定しましたが、
初期配置をする場合、シーンViewで見た目で配置すると思いますが、
ボールやカメラを動かすと座標計算だと配置を変更するたびに、
スクリプトの修正が必要になります。

初期位置からオフセット計算をしておけば、
配置変更しても、そのまま連動させる事ができるので、
修正が少なくて済むかと思います。


カメラ移動のスクリプトができたので、
ボールをゴールまで進めてみようと思います。
カメラワーク②.jpg
ボールと連動してるので、カメラが右に進み過ぎています。

少しカッコわるいでっす。(T-T) グスッ

スクリプトを修正して、ゴールがカメラの右端で止まるように
設定してみます。
CameraMove

//カメラのX軸下限・上限
private const float minX = -50f;
private const float maxX = 16f;

void Update()
{
//ボールが右移動してる時だけボールX座標を取得する
if (ballX < ball.transform.position.x)
ballX = ball.transform.position.x;

//カメラの移動範囲を設定する
float cameraX = Mathf.Clamp(ballX + offsetX, minX, maxX);

//ボールとのオフセットを計算して座標移動させる
transform.position = new Vector3(cameraX, cameraY, cameraZ);
}

Mathf.Clampを使って、カメラ位置の範囲を指定します。
これでボールに着いていく上限も決める事ができます。

実際の動きは、こんな感じになります。

Mathf.Clamp以外の方法もあります。

・if文でカメラの上限まで来たら、transformの代入を飛ばす。
・通り道に透明のフラグオブジェクトを用意してボールが通過したらカメラを止める

この辺りは、状況や使い易い方法を選択すればOKかと思います。

今回は、ケロの作っている横スクロールに対応したカメラの移動方法なので、
参考程度にして頂ければ幸いかと思います。

あまり大した内容では無かったので、
もう一つくらいカメラ移動処理を作ってみたいと思います。

キャラが画面外に移動する際、進行方向の部屋や上のフロアに
カメラがスクロールする、昔のゲームではお馴染みの処理なんですが…

今回は、縦スクロールを作ってみようと思います。

まず、移動範囲を2画面分用意します。
各画面に足場の床を設置していき、最上段にゴールを作ります。
カメラワーク③.jpg
ちょっと分かりにくいですが、左のシーンViewの白枠がカメラの描画範囲です。
ゲーム面は、2画面で構成しています。

ボールを操作して上にジャンプアップして行って、
下側の描画範囲から上に移動するとカメラも追従してスクロールします。

当然、上側の描画範囲から下に落ちたら
カメラも下側にスクロールするようにします。

ボールがどちらの画面に居るか判断する基準として、
Y座標から読み取るのが通常なんですが、
今回は、Colliderのトリガーを使ってみたいと思います。

まず、空のオブジェクトを追加して、Area0としておきます。
カメラワーク④.jpg
このArea0オブジェクトにBoxCollider2Dをアタッチします。

設定が重要になるので、ポイントとして
・カメラの座標とArea0の座標を同じにする(X・Y軸のみ)
・Colliderのトリガーにするをチェックする
・カメラの描画範囲より、やや小さめにColliderサイズを調整する

このArea0にボールが接触したら、カメラをスクロールさせたいので、
トリガーにするのチェックを入れておきます。

接触した際、Area0の座標をカメラの移動先に指定するので、
座標がズレていると変な位置にカメラが動くので注意でっす。

カメラの描画範囲よりColliderサイズを小さくしたのは、
上に用意するArea1のColliderと干渉しないようにする為です。

続いて、Area0を複製してArea1を作ります。
このArea1は、上画面用になります。
カメラワーク⑤.jpg
Area1を上画面に移動させます。

カメラの描画範囲のY軸がスケール10あるので、
Area0からY方向に10スケール上に設置します。

これで、トリガーが用意できたので、スクリプトを組んでみます。
CameraController

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

public class CameraController : MonoBehaviour
{
private Vector3 areaPos; //Area座標取得用

//カメラのX・Z座標取得用
private float posX = 0f;
private float posZ = 0f;

//カメラの移動速度
private const float moveSpeed = 10f;

// Start is called before the first frame update
void Start()
{
//X・Z座標は初期位置で固定する
posX = transform.position.x;
posZ = transform.position.z;
}

// Update is called once per frame
void Update()
{
if (transform.position.y != areaPos.y)
{
transform.position =
Vector3.MoveTowards(transform.position, areaPos, Time.deltaTime * moveSpeed);
}
}

//ボールがAreaに接触したら座標を取得する
public void AreaChange(Vector3 pos)
{
areaPos.x = posX;
areaPos.y = pos.y;
areaPos.z = posZ;
}
}


説明しないと分かりにくいですね(;^_^A アセアセ・・・

まず、ボールがどちらかのAreaに接触したらAreaの座標を読み取ります。

読み取った座標をAreaChangeメソッドに送ります。
座標が来たら、移動先の座標を設定するのですが、
今回は、X・Z軸を固定したいので、初期座標から読み取ったX・Zの値を代入します。

Y軸の値だけ送ればいいとなりますが、横スクロールを作るときにも修正して使えるので、
この形にしました。


UpdateでカメラのY軸とAreaのY軸を比較して、ズレていれば
Vector3.MoveTowardsを使ってカメラを移動させます。

Vector3.MoveTowards(現在位置, 移動先, 移動速度)で、
オブジェクトのスムーズな移動を行う事ができます。

速度を、Time.deltaTime * moveSpeedとしていますが、
Time.deltaTimeはフレーム間の秒数です。

これに距離を掛ける事で1秒間の移動速度が設定できます。
今回は、10を掛けてるので、10スケール/1秒となります。

このスクリプトをメインカメラにアタッチします。

続いて、ボールのスクリプトを組みます。
BallController

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

public class BallController : MonoBehaviour
{
[Header("メインカメラ本体")]
public GameObject mainCamera;

private Rigidbody2D ballRB; //ボールのRigidbody
private CameraController cameraController; //CameraControllerスクリプト

//ボール操作系の変数
private bool jump;
private float horizontal;
private float jumpPow = 400f;

// Start is called before the first frame update
void Start()
{
ballRB = GetComponent<Rigidbody2D>();
cameraController = mainCamera.GetComponent<CameraController>();
}

// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
jump = true;

if (Input.GetKey(KeyCode.RightArrow))
horizontal = 5f;
else if (Input.GetKey(KeyCode.LeftArrow))
horizontal = -5f;

if (Input.GetKeyUp(KeyCode.RightArrow))
horizontal = 0f;

if (Input.GetKeyUp(KeyCode.LeftArrow))
horizontal = 0f;
}

private void FixedUpdate()
{
ballRB.velocity = new Vector2(horizontal, ballRB.velocity.y);

if (jump)
{
ballRB.AddForce(Vector2.up * jumpPow);
jump = false;
}
}

//Areaのトリガーに接触したら
private void OnTriggerEnter2D(Collider2D collision)
{
cameraController.AreaChange(collision.transform.position);
}
}

ボールの操作部分の説明は省きます。

OnTriggerEnter2DでAreaに接触したらCameraControllerのAreaChangeメソッドに
接触Areaの座標を送ります。

ボールに関しては、これだけでっす。

このスクリプトをボールにアタッチして、
publicな変数mainCameraにメインカメラをアタッチすればPLAYでっす。

上手くカメラがスクロールしてくれました。

昔のファミコン時代には、よく使われていたように思います。
例えば、ドンキーコングやアイスクライマーなんかだったと思うんですが…

ゼルダなんかだとダンジョン系で横スクロールも処理が必要になるので、
X軸も読み込めば対応できるかと思います。

カメラの移動って、沢山の処理方法があるので、
何かの参考になれば幸いかと思います。

少し長くなったので、今回はこの辺りで終わります。

それでは(o・・o)/~マタネェ




この記事へのコメント