情報学的バナナの皮

だらだらと自作プログラムについての備忘録

Cinemachine無しで追従範囲の決まった2D見下ろしカメラを作る

大学のサークルで作っているUnityの見下ろし型2Dゲームで追従カメラを作る必要があったんですけど「Unity 追従 カメラ 範囲」とかで調べてもCinemachineを使う方法しか出てこず、Cinemachineを導入するとまた設定とかを弄らなきゃいけなくて面倒そうだったので使いたくありませんでした。
でもCinemachineの追従範囲の設定方法を見ていると、ポリゴンコライダーを使っているみたいで(これなら自作できそうじゃね?)と思ったので作ってみました。
せっかくなのでいつも通り備忘録を残しておこうかかと。

・ほしい機能
1.プレイヤーキャラに追従して動くこと
2.ステージマップだけを映して、マップ外は一切映さないこと
3.Cinemachineも物理演算も使わず極力軽いこと。
4.ステージマップを細分化したときにそれぞれのマップで追従すること(画面端に行くと次のマップに進むのと、今のマップ内ではなめらかにスクロールする処理の両立、メトロイドとかマリオUSAとかゼルダとかみたいなやつ)

・実装

1.プレイヤーキャラに追従して動くこと
これはただ単に最終的なカメラの座標(next_pos)にプレイヤーの座標を送るだけです。
xyzを追従するか否かをfollow_〇としてpublic boolでおいています。
next_pos = new Vector3(
 (follow_x ? follow_obj.transform.position.x : camobj.transform.position.x),
 (follow_y ? follow_obj.transform.position.y : camobj.transform.position.y),
 (follow_z ? follow_obj.transform.position.z : camobj.transform.position.z)
);

2.ステージマップだけを映して、マップ外は一切映さないこと
3.Cinemachineも物理演算も使わず極力軽いこと。
元々今回作っていたゲームの仕様がTileMapを使用していたのでTileMapコライダーの範囲を取ってきて、それをカメラの端と比較するという手法を取ることにしました。
Collider2D.boundsを使うと2Dコライダーの中心やら大きさやらを取ってくることができます。
その中心と大きさ情報を元に、new Rectで矩形を生成します。これは物理演算を使わず完全に計算ベースにするためです。

//ステージの映る範囲の矩形(col_rect)を生成
void rect_update() {
 stage_center = stage_col.bounds.center;
 stage_size = stage_col.bounds.extents;
 col_rect = new Rect( (stage_center.x - stage_size.x), (stage_center.y - stage_size.y),stage_size.x * 2, stage_size.y * 2);
}

これで映したいマップの範囲をデータ化できたので、もうコライダーの情報は必要ありません。
次にカメラに映る範囲とその中心の情報を取ってきます。
カメラの中心はカメラのオブジェクトの座標とイコールなので簡単です。
カメラの中心とカメラの右上の端点の差分を取れば、映る範囲の縦横のサイズがわかります。

//カメラの中央とカメラの端点の座標の距離を取る
Vector2 col_edge_def= cam.ScreenToWorldPoint(new Vector3(0.0f, 0.0f, 0.0f) )-camobj.transform.position;
col_edge_def = new Vector3(Mathf.Abs(col_edge_def.x), Mathf.Abs(col_edge_def.y), 0.0f);

スクリーン座標の(0.0f, 0.0f, 0.0f)は画面右上の端となるので、それをワールド座標に変換してカメラのオブジェクトの座標と差分を取り、xyをそれぞれ絶対値に変換します。

 

これで映したいマップ範囲と、カメラの映る範囲の両方がわかりました。

後は計算するだけです。

例えば右方向
if (col_rect.xMax < col_edge_pos[(int)camera_dir.Left_Up].x) { next_pos.x =next_pos.x-(col_edge_pos[(int)camera_dir.Left_Up].x - col_rect.xMax); }

col_rect.xMaxは矩形のx座標の右端、つまり映したいマップの範囲の右端です。

それをカメラの右端の座標と比較してもしカメラがマップ範囲を超えていたら、その際のx座標からはみ出した分の距離を減算してあげます。

あら簡単!これだけでもう完成です。同じことを4方向に適用し、最後にカメラのオブジェクト座標に代入してあげましょう。

 

4.ステージマップを細分化したときにそれぞれのマップで追従すること

これもそんなに難しくないですね。

自分はステージを細分化した分だけTileMapを分けてやり、それぞれにここまで作ったスクリプトをアタッチしました。

1つのSceneに5個のTileMapを置いて1個のステージとする、みたいな感じです。
そして自分の範囲内にプレイヤーがいるときだけカメラを操作して、範囲外になったら何もしないような処理を足してあげます。

ですが、プレイヤーの検知をOnCollisionStay2Dでやろうとすると、TileMapの仕様上壁などに当たっていないと検知されません。かといってTriggerでも検知されません。

じゃあどうするかというと、もう答えは出ています。だって極力物理演算は使わない方針ですから、Collisionも使わない方向で考えればいいんです。

矩形の操作の中にRect.Contains(Vector2)という、与えられた座標が矩形内に収まっているか否かをboolで返すメソッドがあります。

最初に作ったステージの範囲を表す矩形でこれをやってあげれば良さそうですね。

if (col_rect.Contains(player.transform.position)) {}

ということでこれでここまでの全処理をくくってあげれば完成です。

ああ、あと処理はLateUpdate()内ですることをお忘れなく。追従カメラの基本らしいですから。

・結果

 

youtu.be

こんな感じになります。

Cinemachineなしでだいぶ作ってしまってここから実装するのは嫌だ!とかできるだけ軽い処理にしたい!という人はぜひお試しあれ。