【Unity】タワー系ゲームで全体を写すカメラワークを実装する

Unity
マイケル
マイケル
みなさんこんばんは!
マイケルです!
エレキベア
エレキベア
クマ〜〜〜〜〜
マイケル
マイケル
今回は複数のオブジェクト全体を写すカメラワークの方法について紹介するよ!
エレキベア
エレキベア
複数のオブジェクトクマ??
マイケル
マイケル
そう。具体的には今タワーゲームを作っているところなんだけど、
ゲーム終了後に積み上げたタワー全体を写す処理を実装したんだ!
ズームアウト
マイケル
マイケル
完成系としてはこんな感じだね
エレキベア
エレキベア
タワーゲームでよくある動きのやつクマね
マイケル
マイケル
その通り!
さっそく実装してみよう!
スポンサーリンク

参考元サイト

マイケル
マイケル
本題に入る前に、今回の実装にあたって
下記の記事を参考にさせていただきました!


Unityで複数のオブジェクトがカメラの描画範囲内に収まるように自動調整できるカメラワークを模索してみた。

エレキベア
エレキベア
分かりやすい記事クマ〜〜〜
マイケル
マイケル
こちらの記事ではタワーに関わらず、複数オブジェクトがある場合を考えて実装しています!
すごく勉強になったのでこちらも見てみてくださいね

カメラワークの考え方

マイケル
マイケル
基本的には、下記のように
三角関数を利用してカメラの距離を計算するといった考えで実装したよ!
CameraImage01
マイケル
マイケル
三角関数は下記の通り、tanθ = r / xで求められます!
CameraImage02
マイケル
マイケル
これを利用して、オブジェクトの中心部にポイントを置いて、
カメラの視野角(FOV角度)と中心から最も遠いオブジェクトの距離から
距離を算出する
といった考え方です!
エレキベア
エレキベア
三角関数なつかしいクマ〜〜〜
高校数学で出てくるやつクマね
マイケル
マイケル
ぶっちゃけうろ覚えだったぜ!

距離を算出するスクリプト

マイケル
マイケル
それでは実装に入ります!
マイケル
マイケル
まずは中心部からカメラまでの距離を算出するスクリプトを作りましょう!
CenterPointとするオブジェクトを作成して、下記スクリプトをアタッチします!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraPointController : MonoBehaviour
{
    public Camera usingCamera;
    private Vector3 pos = new Vector3(0, 0, 0);
    private Vector3 center = new Vector3(0, 0, 0);
    private float radius;         // タワー全体の半径
    private float margin = 10.0f; // 半径を少し余分にとるための値
    private float distance;       // タワー全体が写る距離

    void Start()
    {
        // カメラオブジェクトを取得
        CameraController cameraController = FindObjectOfType<CameraController>();
        usingCamera = cameraController.GetComponent<Camera>();
    }

    // マグネタワー全体が映るカメラ位置を返却する
    public Vector3 GetAllTowerLookPosition()
    {
        // マグネタワーのオブジェクトリストを取得
        List<Transform> magneTowerTransformList = new List<Transform>();
        MagnetController[] magneList = FindObjectsOfType<MagnetController>();
        foreach (MagnetController magne in magneList)
        {
            // リストに追加
            magneTowerTransformList.Add(magne.transform);
        }
        //オブジェクトのポジションの平均値を算出
        foreach (Transform trans in magneTowerTransformList)
        {     
            pos += trans.position;
        }
        center = pos / magneTowerTransformList.Count;
        // CenterPointのポジションをタワーの中心に配置
        transform.position = center;
        // 中心から最も遠いオブジェクトとの距離を算出
        foreach (Transform trans in magneTowerTransformList)
        {     
            radius = Mathf.Max(radius, Vector3.Distance(center, trans.position));
        }
        // カメラをCenterPointの高さの位置に移動する
        usingCamera.transform.position = new Vector3(usingCamera.transform.position.x, transform.position.y, 0);
        // タワー全体が写る距離を算出し、位置情報として返却する
        // 式: 半径 / tan(カメラの描画角度 / 2)
        distance = (radius + margin) / Mathf.Tan(usingCamera.fieldOfView * 0.5f * Mathf.Deg2Rad);
        return new Vector3(distance, transform.position.y, 0);
    }
}
マイケル
マイケル
細かく見ていきましょう!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraPointController : MonoBehaviour
{

・・・略・・・

    // マグネタワー全体が映るカメラ位置を返却する
    public Vector3 GetAllTowerLookPosition()
    {
        // マグネタワーのオブジェクトリストを取得
        List<Transform> magneTowerTransformList = new List<Transform>();
        MagnetController[] magneList = FindObjectsOfType<MagnetController>();
        foreach (MagnetController magne in magneList)
        {
            // リストに追加
            magneTowerTransformList.Add(magne.transform);
        }
        //オブジェクトのポジションの平均値を算出
        foreach (Transform trans in magneTowerTransformList)
        {     
            pos += trans.position;
        }
        center = pos / magneTowerTransformList.Count;
        // CenterPointのポジションをタワーの中心に配置
        transform.position = center;

・・・略・・・

    }
}
マイケル
マイケル
上記部分では、カメラに写したいオブジェクトのリストを取得して、
中心の位置にCenterPointオブジェクトを配置しています!
エレキベア
エレキベア
positionの合計値をオブジェクトの数で割って位置を割り出すクマね
マイケル
マイケル
取得するオブジェクトの部分は各自書き換えてくださいね!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraPointController : MonoBehaviour
{

・・・略・・・

private float margin = 10.0f; // 半径を少し余分にとるための値

・・・略・・・

    // マグネタワー全体が映るカメラ位置を返却する
    public Vector3 GetAllTowerLookPosition()

・・・略・・・

        // 中心から最も遠いオブジェクトとの距離を算出
        foreach (Transform trans in magneTowerTransformList)
        {     
            radius = Mathf.Max(radius, Vector3.Distance(center, trans.position));
        }
        // カメラをCenterPointの高さの位置に移動する
        usingCamera.transform.position = new Vector3(usingCamera.transform.position.x, transform.position.y, 0);
        // タワー全体が写る距離を算出し、位置情報として返却する
        // 式: 半径 / tan(カメラの描画角度 / 2)
        distance = (radius + margin) / Mathf.Tan(usingCamera.fieldOfView * 0.5f * Mathf.Deg2Rad);
        return new Vector3(distance, transform.position.y, 0);
    }
}
マイケル
マイケル
あとは最も遠いオブジェクトの距離を取得して半径rを取得
「usingCamera.fieldOfView * 0.5f」でθを取得して公式に当てはめるだけです!
エレキベア
エレキベア
marginの変数は何クマ?
マイケル
マイケル
これは半径を余分にとるための値で、
例えば下記の図を見たら分かる通り、単純にオブジェクトとの距離で半径を算出した場合には見えない部分が発生してしまうんだ!
CameraImage03
マイケル
マイケル
オブジェクトの位置は具体的にはオブジェクトの中心位置だから
オブジェクトの大きさまでは考慮されていない。

そのため、

① オブジェクトの大きさを考慮して半径を算出する。
② 少し余分に半径を設定する。

どちらかの対処が必要なんだ!

エレキベア
エレキベア
位置だけで算出してるから起こる症状クマね・・・
マイケル
マイケル
①の方法は少し計算が複雑になるから、
今回は②の方法で実装したんだ!
CameraImage04
マイケル
マイケル
marginの値を決める時はこのようなことを踏まえた上で設定するようにしてね!
エレキベア
エレキベア
勉強になったクマ〜〜〜

カメラを移動するスクリプト

マイケル
マイケル
あとは算出した距離の位置にカメラを移動させるのみ!
移動スクリプトは下記になります!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    /** 設定値 */
    private float lookSpeed = 0.5f;  // カメラ移動速度
    private Vector3 velocityZero = Vector3.zero;

    /** コンポーネント */
    private CameraPointController cameraPointController;

    /** 変数 */
    private GameObject lookTarget;   // カメラ視点対象
    private Vector3 lookPosition;    // カメラ視点の位置
    private bool changeLook = false; // カメラ視点変更フラグ

    void Start()
    {
        cameraPointController = FindObjectOfType<CameraPointController>();
    }

    void Update()
    {
        // カメラ視点位置に到着した場合
        if (transform.position == lookPosition)
            changeLook = false;
        // カメラ視点位置へ一定速度で移動する
        if (changeLook)
            transform.position = Vector3.SmoothDamp(transform.position, lookPosition, ref velocityZero, lookSpeed);
    }

    // タワー全体の視点対象設定処理
    public void LookTowerTarget()
    {
        // CameraPointよりカメラ位置を算出して取得
        lookPosition = cameraPointController.GetAllTowerLookPosition();
        transform.LookAt(cameraPointController.transform);
        changeLook = true;
    }
}
マイケル
マイケル
今回はVector3.SmoothDamp()メソッドを使用して
ゆっくりと移動するよう実装しました!
エレキベア
エレキベア
これはいろんな場面で使えそうクマね
マイケル
マイケル
これで全体を写すカメラワークの実装は完了です!

おわりに

マイケル
マイケル
というわけで今回はカメラワークの実装でした!
どうだったかな?
エレキベア
エレキベア
三角関数覚えてなくて焦ったクマ
マイケル
マイケル
俺も勉強嫌いだったからうろ覚えだったよ・・・。
学生の頃もこういう活用法とか知ってたらモチベが上がってたかもしれないね
エレキベア
エレキベア
何にせよ楽しかったクマ〜〜〜
マイケル
マイケル
楽しむことが一番だ!!
それでは今日はこの辺で!
アデュー!!
エレキベア
エレキベア
クマ〜〜〜〜〜〜

コメント