やりたいこと
- スプライトの領域内でカメラをドラッグで移動させたい。
- ズーム機能をつけたい。
ちょっと見難いけど、こんな感じ。
ドラッグ処理
1:カメラにコライダーを追加する
Rayを飛ばして当たり判定をとりたいので、カメラにコライダーを追加する。
このコライダーがドラッグ領域になる。
今回は、カメラ領域からUI部分を除いた部分をドラッグ領域とした。
また、他のオブジェクトのコライダーを優先的に感知したいので、一番最下層にくるように配置。
カメラがX90度回転している理由
キャラクターをNavMeshを使って移動させているので、XZ方向に動くキャラクターを上から撮るためにカメラを回転させている。
2:スプライトを配置
ドラッグの領域となるスプライトを配置する。
これは、スクリプトからサイズを取得するだけなので、特にコライダーなどは必要なし。
座標も好きなところに配置でOK。
3:Rayを飛ばすクラスを作る
方法はいろいろあると思うが、今回は他でもRay飛ばして感知したいコライダーがあるので、使い回しできるようにRay用のクラスも作成して、ステージに配置。
using UnityEngine; public class RayHitMouse : MonoBehaviour { [HideInInspector] public GameObject hit; public GameObject Ray() { RaycastHit hit_info = new RaycastHit(); var ray = Camera.main.ScreenPointToRay(Input.mousePosition); float max_distance = 15f; var layer = LayerMask.NameToLayer("SpriteClick"); var layer2 = LayerMask.NameToLayer("UI"); int layer_mask = 1 << layer2 | 1 << layer; var is_hit = Physics.Raycast(ray, out hit_info, max_distance, layer_mask); // Debug.DrawRay(ray.origin, ray.direction * max_distance, Color.red, 5, false); if (is_hit) { hit = hit_info.transform.gameObject; } else { hit = null; } return hit; } }
SpriteClickとUIレイヤーのみ感知するようにしてある。
レイヤーを作成し、カメラのレイヤーをSpriteClickに変更する。
※Rayを飛ばす距離を15fと短めにしてるので足りなかったら適宜変更すること。
4:ヒット判定とドラッグ判定
カメラをドラッグするクラスを作成し、カメラにアタッチしておく。
Input.GetMouseButtonDown(0)でヒット判定し、Input.GetMouseButton(0)でドラッグ判定をする。
using UnityEngine; public class CameraDrag : MonoBehaviour { RayHitMouse rayHitMouse; bool is_down; Vector3 mouseStartPos; void Awake() { rayHitMouse = GameObject.Find("RayHitMouse").GetComponent<RayHitMouse>(); } void Update() { if (Input.GetMouseButtonDown(0)) { OnTouchDown(); } if (Input.GetMouseButton(0)) { if (is_down && mouseStartPos != Input.mousePosition) { OnTouchMove(); } } if (Input.GetMouseButtonUp(0)) { is_down = false; } } public void OnTouchDown() { var hit = rayHitMouse.Ray(); if (hit == gameObject) { is_down = true; mouseStartPos = Camera.main.ScreenToWorldPoint(Input.mousePosition); } } public void OnTouchMove() { { }
ドラッグ判定が2重チェックな理由
if (is_down && mouseStartPos != Input.mousePosition)
この部分についての説明。
マウスの移動があったかどうかは、Down時のマウスの位置と現在のマウスの位置の差で判定できる。
mouseStartPos != Input.mousePosition
←この部分。
is_down
の役割は、カメラにヒットしているかどうかを判定するためで、これをつけておかないと、マウスをドラッグするたびに感知してしまうことになる。
5:ドラッグ処理を追加する
OnTuchMoveメソッドにドラッグ処理を追加していく。
処理内容はシンプルで、マウスの移動量をカメラ座標に追加していくだけ。
public void OnTouchMove() { //ドラッグ処理 var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); var moving_distance = mouseStartPos - mousePos; transform.position += moving_distance; }
ここまでで、ひとまずカメラをドラッグすることができる。
ドラッグ領域の設定
1:限界点を求める
カメラを端に寄せた時の座標を求める方法を考える。
スプライトとカメラの最初の座標が(0.0)のとき、(スプライトの横幅 - コライダーの横幅)/2となる。
スプライトが(0,0)以外の場合、スプライトの座標分をオフセットする。
このままでは、カメラの中心点とコライダーの中心点が異なる場合、ずれてしまう。
なので、コライダーのCenter座標もオフセットする。
Vector2 contentSize; Vector2 contentOffset; BoxCollider cameraCollider; void Awake() { //つづけて追加 var content = GameObject.Find("MapSprite").gameObject; var sr = content.GetComponent<SpriteRenderer>(); contentSize = new Vector2(sr.bounds.size.x, sr.bounds.size.z); contentOffset = new Vector2(content.transform.position.x, content.transform.position.z); cameraCollider = GetComponent<BoxCollider>(); } public void OnTouchMove() { //つづけて追加 //コライダーオフセット var colliderOffset = new Vector2(cameraCollider.center.x, cameraCollider.center.y); //限界値の算出 var limitR = (contentSize.x - cameraCollider.size.x) / 2 - colliderOffset.x + contentOffset.x; var limitL = (contentSize.x - cameraCollider.size.x) / -2 - colliderOffset.x + contentOffset.x; var limitT = (contentSize.y - cameraCollider.size.y) / 2 - colliderOffset.y + contentOffset.y; var limitB = (contentSize.y - cameraCollider.size.y) / -2 - colliderOffset.y + contentOffset.y; }
2:カメラ座標に設定する
スプライトのサイズがコライダーのサイズよりも小さい場合はドラッグする必要がないので、最初の座標で固定する。
限界点を超えたら、限界点で固定。
Vector3 defCameraPosition; void Awake() { //つづけて追加 defCameraPosition = Camera.main.transform.position; } public void OnTouchMove() { //つづけて追加 //限界まで移動した場合の処理 var posi = transform.position; if (contentSize.x > cameraCollider.size.x) { if (transform.position.x > limitR) { posi.x = limitR; } else if (transform.position.x < limitL) { posi.x = limitL; } } else { posi.x = defCameraPosition.x; } if (contentSize.y > cameraCollider.size.y) { if (transform.position.z > limitT) { posi.z = limitT; } else if (transform.position.z < limitB) { posi.z = limitB; } } else { posi.z = defCameraPosition.z; } transform.position = posi; }
ズームに対応させる
1:ズームボタンを作る
ズームの仕組みは単純で、カメラのorthographicSizeを変更するだけ。
using UnityEngine; public class StageZoom : MonoBehaviour { float def; float[] zoom = { 1.0f, 0.75f, 0.5f }; int counter; void Awake() { def = Camera.main.orthographicSize; } public void OnZoom() { counter++; if (counter == zoom.Length) { counter = 0; } Camera.main.orthographicSize = def * zoom[counter]; //位置調整 Camera.main.GetComponent<CameraDrag>().OnTouchMove(); } }
この最後の位置調整で、先に作成したドラッグのメソッドを呼び出してある。
このボタンをCanvasに作成した場合UIレイヤーになるので、Rayで感知した場合はコライダーをつけておく。
そうするとボタンを押してる間はドラッグされないようになる。
2:倍率に合わせてコライダーもリサイズする
OnTouchMoveメソッドが呼び出されるので、そこに追加していく。
カメラをズームすると、カメラの領域が狭くなるのに対し、コライダーサイズはそのままなので、最初のコライダーサイズに倍率を掛けて調整する。
また、コライダーの位置もカメラの領域に合わせて調整する必要がある。ただし、Y軸(高さ)はそのままにしておきたいのでY軸だけは初期値に固定する。
ここで注意が必要なのは、コライダーのcenterはカメラに対してのローカル座標になるので、固定するのはz座標になる。
これをコライダーのオフセットの記述より前に追加する。
Vector2 defColliderSize; Vector3 defColliderCenter; float defCameraOrthographicSize; void Awake() { defCameraOrthographicSize = Camera.main.orthographicSize; defColliderSize = cameraCollider.size; defColliderCenter = cameraCollider.center; } public void OnTouchMove() { //ズーム率 var zoom = Camera.main.orthographicSize / defCameraOrthographicSize; //コライダーをズームに合わせる cameraCollider.size = defColliderSize * zoom; //z軸は固定(コライダーはカメラに対してのローカル座標) cameraCollider.center = new Vector3(defColliderCenter.x * zoom, defColliderCenter.y * zoom, defColliderCenter.z); }
3:ズームボタンから呼び出した時はドラッグしないようにする
ドラッグ処理のときにDown時にmouseStartPosにマウス座標を代入してあるが、ズームボタンから直接呼び出す場合は、移動する必要がないのでis_down=trueのときしかドラッグ処理をしないようにしておく。
public void OnTouchMove() { if (is_down) { //ドラッグ処理 var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); var moving_distance = mouseStartPos - mousePos; transform.position += moving_distance; } }
CameraDrag の完成スクリプト
using UnityEngine; public class CameraDrag : MonoBehaviour { RayHitMouse rayHitMouse; bool is_down; Vector3 mouseStartPos; Vector2 contentSize; Vector2 contentOffset; BoxCollider cameraCollider; Vector3 defCameraPosition; Vector2 defColliderSize; Vector3 defColliderCenter; float defCameraOrthographicSize; void Awake() { rayHitMouse = GameObject.Find("RayHitMouse").GetComponent<RayHitMouse>(); var content = GameObject.Find("MapSprite").gameObject; var sr = content.GetComponent<SpriteRenderer>(); contentSize = new Vector2(sr.bounds.size.x, sr.bounds.size.z); contentOffset = new Vector2(content.transform.position.x, content.transform.position.z); cameraCollider = GetComponent<BoxCollider>(); defCameraPosition = Camera.main.transform.position; defColliderSize = cameraCollider.size; defColliderCenter = cameraCollider.center; defCameraOrthographicSize = Camera.main.orthographicSize; } void Update() { if (Input.GetMouseButtonDown(0)) { OnTouchDown(); } if (Input.GetMouseButton(0)) { if (is_down && mouseStartPos != Input.mousePosition) { OnTouchMove(); } } if (Input.GetMouseButtonUp(0)) { is_down = false; } } public void OnTouchDown() { var hit = rayHitMouse.Ray(); if (hit == gameObject) { is_down = true; mouseStartPos = Camera.main.ScreenToWorldPoint(Input.mousePosition); } } public void OnTouchMove() { //ドラッグ処理 if (is_down) { //マウスの移動量を加算 var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); var moving_distance = mouseStartPos - mousePos; transform.position += moving_distance; } //ズーム対応 var zoom = Camera.main.orthographicSize / defCameraOrthographicSize; cameraCollider.size = defColliderSize * zoom; cameraCollider.center = new Vector3(defColliderCenter.x * zoom, defColliderCenter.y * zoom, defColliderCenter.z); //コライダーのオフセット var colliderOffset = new Vector2(cameraCollider.center.x, cameraCollider.center.y); //限界値の算出 var limitR = (contentSize.x - cameraCollider.size.x) / 2 - colliderOffset.x + contentOffset.x; var limitL = (contentSize.x - cameraCollider.size.x) / -2 - colliderOffset.x + contentOffset.x; var limitT = (contentSize.y - cameraCollider.size.y) / 2 - colliderOffset.y + contentOffset.y; var limitB = (contentSize.y - cameraCollider.size.y) / -2 - colliderOffset.y + contentOffset.y; //限界まで移動した場合の処理 var posi = transform.position; if (contentSize.x > cameraCollider.size.x) { if (transform.position.x > limitR) { posi.x = limitR; } else if (transform.position.x < limitL) { posi.x = limitL; } } else { posi.x = defCameraPosition.x; } if (contentSize.y > cameraCollider.size.y) { if (transform.position.z > limitT) { posi.z = limitT; } else if (transform.position.z < limitB) { posi.z = limitB; } } else { posi.z = defCameraPosition.z; } transform.position = posi; } }
以上。
合ってるかわかんないけど、すごく長くなっちゃった。