【Unity】2Dシューティングを作ってみる話 #2
記事をご覧いただき、誠にありがとうございます。
移動制限がかかっているのでカメラの画角から出ることはありませんが、これでは画角外に出て無敵状態になっているのと殆ど変わりありません。
試しにmarginX・Yを1にしてゲームを実行してみてください。
これでプレイヤーがほぼ無敵状態というのが解消されました。
投稿主の無能です。
今回は、前回動くようになったプレイヤーに、カメラの画角外へ出ないように制限を付けたいと思います。
では早速やっていきましょう。
まずおさえておくべき座標の概念
カメラの画角から出ないように、とは言いましたが、座標と言うものが分からないと、今どの位置にいてどんな座標にあたるのか、という事が分からないままになってしまいます。
ですので、まずはUnityの世界の座標と言う概念を取り入れて理解しましょう。
(と言っても、無能もごく一部の紹介になるのですが…)
ワールド座標の大まかなイメージ
まず、前提として理解しておきたいのは、ゲームオブジェクトに与えられる座標と言うのは「ワールド座標」というものになります。
簡単に言い換えてしまうと、現実世界のGPSのようなものですね。
この座標の情報がゲームオブジェクトのTransoformのpositionに当たります。
ここまでは理解出来そうですね。
当然大きさも関わるので、ドット絵なんかだと1ドットを表す解像度なんかも関わってきます。
Unityではピクセルという単位で表されます。
そして画面の大きさですが、表す解像度に因って左右されます。
つまり、綺麗な(ドット)絵を表示したいとなれば、解像度を上げて表示できるもの(ここではドットやピクセル)の数を上げてやれば、より精細な(ドット)絵が表示できるようになる、と言うわけです。
なので、最小値となる画面の左下の値は、x・y共に0になります。
対して最大値になるカメラの画角の右上は、どうしても解像度に因る値になる、という事が特徴になり、解像度が決まっていないと最大値が分からないという事になります。
ビューポート座標の大まかなイメージ
対して、Unityの座標の表し方には「ビューポート座標」という表し方もあります。
このビューポートと言うのは、まず範囲が決まっているのが大きな特徴と言えます。
最小値となる左下は、ワールド座標と変わらずx・y共に0となります。
しかし大きな違いとして、最大値の右上はx・y共に1と決まっています。
この違いは非常に大きくて、例え解像度が違っていたとしても、最大値の右上は常にx・y共に1であるという事です。
結構大きな違いがありますね。
ビューポート座標とワールド座標の違い(イメージ)
座標の取得の仕方が違うのであれば…
ここまで座標の表し方の違いについてザックリと説明してきました。
大きく分けると以下のようになります。
- ワールド座標:現実世界のGPSのようなもの
- 最小値(左下)はx・y共に0である
- 最大値(右上)は解像度に因るため、解像度が決まっていないと分からない(解像度よって変化する)
- ビューポート座標:カメラが映している世界の座標
- 最小値(左下)はx・y共に0である
- 最大値(右上)は解像度に因らずx・y共に1である
こんな感じの捉え方で大丈夫だと思います。
ですから、ワールド座標ではなくビューポート座標でプレイヤーの現在地を取得して、ビューポート座標で取得した値がワールド座標の何処になるのかが分かれば良い、というわけです。
そして制限をかけてやれば、目的の移動制限が実現出来そうですね。
移動制限をスクリプトに追記する
方針が何となく分かったので、その内容をスクリプトで表現してやります。
このように追記しました。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController : MonoBehaviour
{
[Header("Playerの動く速さ")]
[SerializeField] float moveSpeed = 1f;
[Header("移動制限の余白")]
[SerializeField] float marginX = 1f, marginY = 1f;
Rigidbody2D playerRB; // プレイヤーのRigidbody
Vector2 moveDirection, min, max; // プレイヤーのベクトル,画面サイズの最小ベクトル/最大ベクトル
Vector2 playerPos; // プレイヤーの移動制限用
void Start()
{
// プレイヤーのRigidbodyを取得
playerRB = GetComponent<Rigidbody2D>();
}
void Update()
{
// 入力受付
InputProcess();
}
void FixedUpdate()
{
// プレイヤーを動かす
PlayerMove();
}
// 入力受付
void InputProcess()
{
// 入力を正規化して受け取る
float x = Input.GetAxisRaw("Horizontal");
float y = Input.GetAxisRaw("Vertical");
// 入力を正規化する
moveDirection = new Vector2(x, y).normalized;
}
// プレイヤーを動かす
void PlayerMove()
{
// 移動制限
MoveClamp();
// プレイヤーを動かす
playerRB.velocity = moveDirection * moveSpeed;
}
// プレイヤーの移動制限
void MoveClamp()
{
// 最後の移動地点を取得
playerPos = transform.position;
// ビューポート座標からワールド座標を取得
min = Camera.main.ViewportToWorldPoint(Vector2.zero);
max = Camera.main.ViewportToWorldPoint(Vector2.one);
// 移動を制限する
playerPos.x = Mathf.Clamp(playerPos.x, min.x + marginX, max.x - marginX);
playerPos.y = Mathf.Clamp(playerPos.y, min.y + marginY, max.y - marginY);
// プレイヤーの位置を取得した最後の地点にする
transform.position = playerPos;
}
}では追記部分を説明していきます。
移動制限のMoveClamp関数
移動制限を追記するにあたって、MoveClamp関数を作成しました。
MoveClamp関数の最初に、playerPosにプレイヤーが制限に到達する直前の位置を取得しておきます。
こうすることで、制限に到達しても到達直前の位置に戻せるようになります。
次にmin・maxでそれぞれビューポート座標の最小値・最大値を取得して、Camera.mainでメインカメラ、ViewportToWorldPointでビューポート座標からワールド座標に置き換えがされます。
その基準となるものは、minの場合は最小値となるVector2.zero、maxの場合は最大値となるVector2.oneを対象にしています。
そしてMathf.Clamp関数で最小値のmin、最大値のmaxから余白分を足し引きした値に限定されます。
最後に自身の位置を、最初に取得した位置にしています。
ここまでがMoveClamp関数の流れになっています。
では一つずつ見ていきましょう。
ViewportToWorldPoint関数
ViewportToWorldPoint関数の使い方として、引数にベクトルが必要になります。
使い方は公式ドキュメントを参考にしてください。
Camera.ViewportToWorldPoint - Unity Documentation
このViewportToWorldPointですが、2Dの場合は縦横だけで済みますが、3Dになると少々勝手が違います。
それは何故かと言うと、奥行きのあるz軸が加わるからです。
説明は割愛しますが、z軸が加わることで想定していた動作と実際の挙動が変わってきます。
簡単に言うと、画面とカメラがどのくらい離れているか、ということも計算してあげる必要があります。
このz軸の計算が無いと、例えばマウスのクリック判定が想定外になっているとか、見た感じと実際の挙動に差異が生じたりします。
ですので3Dで使う場合は、z軸を考慮する必要があると頭の片隅に置いておいてください。
Mathf.Clamp関数
Mathf.Clamp関数ですが、こちらも使い方を確認してからの方が理解しやすいかと思います。
工具のクランプそのままのようなイメージですね。
Mathf.Clamp - Unity Documentation
使い方は引数に制限する対象の値と、最小・最大値を設定してあげることで、対象の値を最小・最大値の間に計算してくれるようになります。
引数は(制限の対象となる値, 最小値, 最大値)となります。
前述のMoveClamp関数では、取得したplayerPosのx・yの値を最小値のminのx・yと最大値のx・yの間になるようにしています。
本来であればこれだけでカメラの画角外へ出ることは無いのですが、marginのXとYという値で余白を取るようにしています。
試しにmarginのXとYを0、つまり余白無しでゲームを実行してみます。
marginのX・Yをそれぞれ0(余白無し)にする
カメラの画角外へは出ないが、プレイヤーの姿はほぼ隠れている
この状態が画面の四隅で起こってしまいます。
どうしてこんな状態になるのかという理由ですが、まずMathf.Clampは正常に動作しています。
問題なのは、オブジェクトの中心が何処に在るか、ということです。
実際に上記の余白無しの状態だと、これ以上は左下に向かって動くことが出来ません。
という事は、オブジェクトの中心は文字通りオブジェクトのド真ん中にあるという事になります。
ですのでmarginという余白を設定することで、オブジェクト自体が隠れてしまうことを補っている、というわけですね。
余白(marginX・Y)のイメージ
marginX・Yをそれぞれ1にする
余白の働きでプレイヤーが隠れなくなった
余白はそれぞれ自由に設定してください。
プレイヤーが想定の動きをしてくれると、何だか気持ちいいですね。
まとめ
今回は
- スクリプトにプレイヤーの移動を制限する処理を追記した
という事をやりました。
次回は弾を発射してみたいと思います。
では、また次回!






コメント
コメントを投稿