ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20200808_01

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

      Unityゲーム数学マグネタワー
      2020-08-08

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

      参考元サイト

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


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

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

      カメラワークの考え方

      マイケル
      マイケル
      基本的には、下記のように
      三角関数を利用してカメラの距離を計算するといった考えで実装したよ!
      マイケル
      マイケル
      三角関数は下記の通り、tanθ = r / xで求められます!
      マイケル
      マイケル
      これを利用して、オブジェクトの中心部にポイントを置いて、
      カメラの視野角(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の変数は何クマ?
      マイケル
      マイケル
      これは半径を余分にとるための値で、
      例えば下記の図を見たら分かる通り、単純にオブジェクトとの距離で半径を算出した場合には見えない部分が発生してしまうんだ!
      マイケル
      マイケル
      オブジェクトの位置は具体的にはオブジェクトの中心位置だから
      オブジェクトの大きさまでは考慮されていない。

      そのため、

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

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

      エレキベア
      エレキベア
      位置だけで算出してるから起こる症状クマね・・・
      マイケル
      マイケル
      ①の方法は少し計算が複雑になるから、
      今回は②の方法で実装したんだ!
      マイケル
      マイケル
      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()メソッドを使用して
      ゆっくりと移動するよう実装しました!
      エレキベア
      エレキベア
      これはいろんな場面で使えそうクマね
      マイケル
      マイケル
      これで全体を写すカメラワークの実装は完了です!

      おわりに

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

      Unityゲーム数学マグネタワー
      2020-08-08

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【ゲーム数学】第九回 p5.jsで学ぶゲーム数学「フーリエ解析」
      2024-05-12
      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
      2024-04-14
      【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-30
      【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
      2024-03-30
      【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-28
      【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
      2024-03-28