前回の記事では背景となるGameObjectをTerrainを使用して配置しました。
前回の記事:
今回ではその背景に指定したルートをプレイヤーが移動するようにしましょう!
定めた場所を通りながら自動的にプレイヤーの向きや進行方向も変更していきます。
また、プレイヤーの子オブジェクトにカメラをアタッチして1人称視点ゲームに変化させます。
プレイヤーを移動させる
プレイヤー操作用のスクリプトを新しく追加します。スクリプトの名前は「Player」にしてください。
追加できたらシーンの「Player」にアタッチしてください。
次のサンプルコードは前の講座で配置したルート上のポイントの間でプレイヤーを移動させるものになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public Transform[] RoutePoints; [Range(0, 200)] public float Speed = 10f; bool _isHitRoutePoint; IEnumerator Move() { var prevPointPos = transform.position; foreach(var nextPoint in RoutePoints) { _isHitRoutePoint = false; //必ずfalseにする while (!_isHitRoutePoint) { //進行方向の計算 var vec = nextPoint.position - prevPointPos; vec.Normalize(); // プレイヤーの移動 transform.position += vec * Speed * Time.deltaTime; //次の処理は進行方向を向くように計算している transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(vec, Vector3.up), 0.5f); yield return null; } prevPointPos = nextPoint.position; } } private void OnTriggerEnter(Collider other) { if(other.gameObject.tag == "RoutePoint") { other.gameObject.SetActive(false); _isHitRoutePoint = true; } } void Start() { StartCoroutine(Move()); } } |
スクリプトが無事コンパイルできたら、次はシーンに配置したGameObjectに必要な設定を行なっていきましょう!
まず「Player」の設定をしましょう。
- PlayerコンポーネントのRoutePointsフィールドにBackGroundの子のPointオブジェクトを移動順にヒエラルキーからアタッチする
- 「Player」プレハブにRigidbodyをアタッチし、IsKinematicにチェックを入れる
- RigidbodyのUse Gravityのチェックを外す
- 「Player」プレハブにSphereColliderをアタッチして、Edit Colliderを選択し、Radiusを変更しモデル全体を覆うようにする(値は1記事目で作った機体による。個々人で調整)
この講座ではルート上のポイントと衝突したときにそのポイントに到達したことにしています。
そのため当たり判定を行う必要があるのですが、それにはRigidbodyコンポーネントがアタッチされていないといけません。
今回の場合では単純に当たり判定を行うことだけにRgidbodyコンポーネントを使用しているので、Is Kinematicにチェックを入れ、物理エンジンによる操作は行わないようにしています。
次にルート上のポイント全てに次の設定を行なってください
タグの設定はこの次の項目を参考にしてください。
プレイヤーはポイントに当たった時に進行ルートを変えるので、その辺りを意識して調節してください。
- タグを「RoutePoint」に設定
- Collider系のコンポーネントをアタッチし、IsTriggerにチェックする
- Colliderの大きさはルートに応じて適当なサイズにする
Collider系のコンポーネントには次のものがあるので、適宜使い分けてください。ただし3D用のColliderを使用してください。
- BoxCollider
- SphereCollider
- CapsuleCollider
- MeshCollider
などなど。
GameObjectのタグについて
UnityではGameObjectの判別のためにタグを指定することができます。
タグはInspectorの上にあるTagから設定できます。
タグは設定する際に既にあるものを使うor新しくタグを追加して使うことができます。
- InspectorのTagをクリックし、開いたメニューから「Add Tag」をクリック
- Inspectorの表示が切り替わるので、そこからTagsの欄に追加したいタグの名前を追加する
この記事ではルート上のポイントに「RoutePoint」というタグを指定しますので、上で説明した手順で「RoutePoint」タグを追加してから、GameObjectに設定してください。
スクリプト上からはUnityEngine.GameObject.tagからGameObjectに設定されているタグを取得できます。
1 |
gameObject.tag == “RoutePoint” |
カメラがプレイヤーに追従するようにする。
このまま再生するとプレイヤーだけが移動しカメラから映らなくなります。
そのためシーンにある「Main Camera」を「Player」の子GameObjectにします。子GameObjectに追加した「Main Camera」は「Player」プレハブには追加しないように注意してください。
また「Main Camera」には次の設定を行なってください。
- TransformのPositionを(0, 0, -20)にする
- TransformのRotationを(0, 0, 0)にする
- TransformのScaleを(1, 1, 1)にする
注:↑の値は人によって適性な値が異なります。作ったコースや機体の大きさなどに合わせて調整してください。
プレイヤーを上下左右に移動させる
一応ここまででプレイヤーをルート上のポイントの間を移動できるようになりました。
ただこのままだと直進するだけなので、ルートから外れない程度に上下左右に移動できるようにしてみましょう。
Playerコンポーネントに次のものを追加してください。
「Horizontal」と「Vertical」に指定されたキーやボタンを押すことで進行方向に合わせた上下左右に移動するようになっています。
数学的な知識を含んだ処理になっていますが、本筋からは外れるので詳しい解説は省略します(どこかでまた数学講座も作りたいところですね)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// Playerクラスの中 [Range(0, 50)] public float MoveSpeed = 10f; public float MoveRange = 40f; // Moveメソッドを次のものに上書きしてください IEnumerator Move() { var prevPointPos = transform.position; var basePosition = transform.position; var movedPos = Vector2.zero; foreach (var nextPoint in RoutePoints) { _isHitRoutePoint = false; //必ずfalseにする while (!_isHitRoutePoint) { //進行方向の計算 var vec = nextPoint.position - prevPointPos; vec.Normalize(); // プレイヤーの移動 basePosition += vec * Speed * Time.deltaTime; //上下左右に移動する処理 // 行列によるベクトルの変換に関係する知識を利用しています。 movedPos.x += Input.GetAxis("Horizontal") * MoveSpeed * Time.deltaTime; movedPos.y += Input.GetAxis("Vertical") * MoveSpeed * Time.deltaTime; movedPos = Vector2.ClampMagnitude(movedPos, MoveRange); var worldMovedPos = Matrix4x4.Rotate(transform.rotation).MultiplyVector(movedPos); //ルート上の位置に上下左右の移動量を加えている transform.position = basePosition + worldMovedPos; //次の処理では進行方向を向くように計算している transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(vec, Vector3.up), 0.5f); yield return null; } prevPointPos = nextPoint.position; } } |
注意点として、移動ルートの直線上から微妙に外れることがあるため、「RoutePoint」タグをつけたシーン上のGameObjectと当たらなくなる場合が出てくるようになります。
その際は次の修正を行なってください。
- 「RoutePoint」の当たり判定の領域を広げる。
- その際、進行ルートも微妙にずれたりするので、Transformの位置を微調節する
- 地形にめり込んだりする時は「Background」の方も修正してください。
修正はトライ&エラーで頑張りましょう。移動可能経路の抑えを増やすことでも調整可能です。
なお、プレイヤーはルートのポイントに当たった時に進行方向を変えることを念頭に置いておくと修正しやすいと思います。
まとめ
今回の記事でプレイヤーをルートに沿って移動させることができました。
3Dゲームでは数学の知識があるとより自由に安定した動作を作ることができるので、より凝ったものを作りたい方は3Dゲームの物理や数学を勉強してみるのもいいでしょう。
まとめると以下のようになります。
- GameObjectを識別する際はタグを利用できる。
- 3Dゲームではベクトルや行列の知識があるとより自由にGameObjectを制御することが出来る。
それでは次の記事に行ってみましょう。
参考用:スクリプトの完成図
ちなみに今回の記事で作成したスクリプトの完成図は次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public Transform[] RoutePoints; [Range(0, 200)] public float Speed = 10f; [Range(0, 50)] public float MoveSpeed = 10f; public float MoveRange = 40f; bool _isHitRoutePoint; IEnumerator Move() { var prevPointPos = transform.position; var basePosition = transform.position; var movedPos = Vector2.zero; foreach (var nextPoint in RoutePoints) { _isHitRoutePoint = false; //必ずfalseにする while (!_isHitRoutePoint) { //進行方向の計算 var vec = nextPoint.position - prevPointPos; vec.Normalize(); // プレイヤーの移動 basePosition += vec * Speed * Time.deltaTime; //上下左右に移動する処理 // 行列によるベクトルの変換に関係する知識を利用しています。 movedPos.x += Input.GetAxis("Horizontal") * MoveSpeed * Time.deltaTime; movedPos.y += Input.GetAxis("Vertical") * MoveSpeed * Time.deltaTime; movedPos = Vector2.ClampMagnitude(movedPos, MoveRange); var worldMovedPos = Matrix4x4.Rotate(transform.rotation).MultiplyVector(movedPos); //ルート上の位置に上下左右の移動量を加えている transform.position = basePosition + worldMovedPos; //次の処理は進行方向を向くように計算している transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(vec, Vector3.up), 0.5f); yield return null; } prevPointPos = nextPoint.position; } } private void OnTriggerEnter(Collider other) { if(other.gameObject.tag == "RoutePoint") { other.gameObject.SetActive(false); _isHitRoutePoint = true; } } void Start() { StartCoroutine(Move()); } } |
コメント