今回はUnityの3Dアクションゲーム等でキーボード等で移動しているキャラクターが車や飛行機の近くに寄って、特定のボタンを押した時に移動手段である車等に乗って移動する機能を作成していきます。
通常の移動をしているキャラクターを乗り物に乗せて動かすなんて難しそう!と思われるかもしれませんが、車でキャラクターを移動させるだけに限って言えば簡単です。
今回は大雑把な機能の作成をし、細かい部分は記事内で紹介する他の記事を参照してみてください。
今回の機能を作成すると、
上のような感じのものが出来上がります。
車に乗り込むアニメーション等は割愛し、キャラクターが車と移動出来るという機能に特化します。
その為、上のような車の上にキャラクターが乗っている状態になっています。(-ω-)/
キャラクターを乗り物に乗せて動かす機能の概要
まずはキャラクターを乗り物に乗せ、その乗り物と一緒にキャラクターを動かす為にはどうするか?を考えていきます。
車に乗せる
車にキャラクターを乗せるには車の子要素にキャラクターを検知する範囲を用意し、その範囲内にキャラクターがいる時に特定のボタンを押したらキャラクターを車の子要素に設定します。
実はキャラクターを車の子要素にするだけで車の動きに合わせてキャラクターも移動します。
キャラクターが車と相対的に動くわけですね。
車に乗り込む時にやらなければいけない事
特定のボタンを押したらキャラクターを車の子要素にすると書きましたが、その前にいくつかやる事があります。
車に乗り込むアニメーションと車の位置を合わせる
例えば車に乗り込むアニメーションをして車の中に入る場合は、キャラクターに車に乗り込むアニメーションをさせ運転席に座ってハンドルを持つようにする必要があります。
(キャラクターのアニメーションはBlender等で実際の車のモデルに合わせて作成すると良さげですね)。
ここで必要なのがキャラクターのアニメーション動作と運転席に座る位置とを合わせることです。
これにはターゲットマッチングを使用すると出来ます。
今回はそこら辺の細かい部分は作りませんが、以前ターゲットマッチングを使用して作成した機能があるので、興味がある方は
を参照してみてください。
キャラクターのコライダが車のコライダと衝突する
車に乗る前のキャラクターはキーボード等の操作で動かす為、コライダを使って地面などとの衝突判定を行っています。
車に乗り込む時にキャラクターのコライダがあると車のコライダと衝突してしまう為、車に乗り込む時にはキャラクターのコライダをオフにします。
車を降りる時にする事
最後に車に乗ったら車を降りるという機能も作成しなければいけません。
そこで車のスピードが一定の速度以下になったら車を降りれるようにし、またキャラクターのコライダを有効にし普通に移動出来るようにします。
キャラクターが車を降りた時に車の移動機能は無効にします。
今回の場合はスタンダードアセットの車を動かす機能を使いますのでそちらの移動機能を無効にします。
機能の作成
キャラクターを車に乗せる為の概要がわかったので、実際に機能を作成していきましょう。
スタンダードアセットの車を配置
まずはスタンダードアセットにあるCarを地面の上に配置します(地面はTerrainやPlane等で作っておきます)。
StandardAssets/Vehicles/Car/PrefabsにあるCarをヒエラルキー上にドラッグ&ドロップします。
Carには既に車のモデルと車を動かす機能が取り付けてあるので後はキャラクターが車に乗れる範囲とその際のスクリプトを作成するだけです。
スタンダードアセットの車については
を参照してください。
自作の車モデルを使う場合は
を参照してください。
車は最初はキーボード操作等で動いては困るのでCarUserControlスクリプトを無効化しておきます。
キャラクターが車に乗り込む事が出来る範囲の作成
Carの子要素に空のゲームオブジェクトを作成し、名前をCarStandとします。
CarStandにはSphererColliderを取り付けIs Triggerにチェックを入れて物理的に当たらないようにします。
またActivateCarScriptという名前の新しいスクリプトを作成し取り付けます。
実際のキャラクターが車に乗り込む事が出来る範囲は、
上のようになります。
車に乗り込む為のスクリプトActivateCarScriptの記述
ActivateCarScriptは車に乗り込む事が出来る範囲内にキャラクターがいる時に指定のキーを押したら車に乗り込む為の処理をするスクリプトです。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityStandardAssets.Vehicles.Car; public class ActivateCarScript : MonoBehaviour { // 車を操作しているかどうか private bool isActiveCar; // 車を操作するスクリプト [SerializeField] private CarUserControl carUserControl; // 車を乗る事が出来るエリア [SerializeField] private SphereCollider carStandCollider; // 車のRigidbody [SerializeField] private Rigidbody rigid; private void Start() { carUserControl = GetComponentInParent<CarUserControl>(); } public void OnTriggerStay(Collider other) { // プレイヤーが範囲内にいて、マウスの左クリックをした時 if (other.tag == "Player" && !isActiveCar && Input.GetButton("Fire1")) { RideInCar(other); } } private void RideInCar(Collider other) { isActiveCar = true; // キャラクターの状態を車に乗った状態にする other.GetComponent<VehicleChara>().SetState(VehicleChara.State.rideCar, transform.root); // CarUserControlスクリプトを有効にする carUserControl.enabled = true; carStandCollider.enabled = false; rigid.isKinematic = false; } // 車を降りる処理 public void GetOffCar() { carUserControl.enabled = false; carStandCollider.enabled = true; rigid.isKinematic = true; isActiveCar = false; } } |
フィールドでは車にキャラクターが乗る時と降りる時で使用するコンポーネントを設定出来るようにしておきます。
isActiveCarはキャラクターが車に乗っているかどうかのフラグです。
carUserControlは車をキーボード等で操作する時の処理をしているのでそれを有効・無効を切り替えて操作の切り替えをします。
rigidは車を動かす時にRigidbodyを使用しているのでキャラクターが車を降りる時にRigidbodyのIsKinematicを有効にし、物理的に作用しないようにします。
OnTriggerStayメソッドでは車に乗り込む範囲内にキャラクターがいる時で車に乗り込んでいない時にマウスの左クリックをしたら車に乗り込む処理であるRideInCarメソッドを呼び出します。
実際には車の中には入らないのでRideOnCarにした方が良さげですが・・・・(^_^;)
今回はマウスの左クリック(厳密にはFire1に割り当てられたボタン)でキャラクターが車に乗り込みますが、最初はSpaceキー(Jumpキー)で行うようにしていました。
しかしSpaceキーは車のハンドブレーキのキーと被ってしまっていて車に乗り込んだ後に前に進めないという不具合に見舞われたため変更しました・・・(^_^;)
RideInCarメソッドではキャラクターの操作スクリプトであるVehicleCharaのSetStateメソッドを呼び出し、キャラクターの状態の変更とCarのTransform情報を渡しています。
車に乗り込む時に車の操作スクリプトであるcarUserControlと車に乗り込む範囲のcarStandColliderを無効にしています。
またRigidbodyのIsKinematicも無効にし、Rigidbodyの物理的な作用を働かせます。
GetOffCarメソッドはキャラクターが車から降りる時に呼び出される処理です。
車のコントロールをオフ、乗り込む範囲のオン、IsKinematicのオン、isActiveCarのオフをしています。
スクリプトの作成が終わったらCarStandのインスペクタでActivateCarScriptにCarUserControlスクリプト、CarStandのコライダ、CarのRigidbodyを設定します。
これで車の設置が完了しました。
キャラクターの設定
次はキャラクターを作成していきます。
キャラクターのモデルにはスタンダードアセットのEthanを使用し、CharacterControllerコンポーネントの取り付け、AnimatorControllerの作成をしておきます。
EthanのインスペクタでTagにPlayerを設定しておきます。
AnimatorControllerではIdleとWalk状態を作成し、アニメーションパラメータのWalkSpeedで状態が遷移するようにしておきます。
細かい部分は
の記事等を参照して作成してください。
キャラクター操作スクリプトの作成
キャラクターであるEthanにVehicleCharaという名前の新しいスクリプトを作成し取り付けます。
スクリプトが長いので分割して解説します。
フィールド宣言部とStartメソッド
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityStandardAssets.Vehicles.Car; public class VehicleChara : MonoBehaviour { public enum State { normal, rideCar }; private CharacterController characterController; private Animator animator; private Vector3 velocity = Vector3.zero; [SerializeField] private float walkSpeed = 1.5f; private State state; // キャラクターが車に乗った時の位置 [SerializeField] private Vector3 ridePosition = new Vector3(0f, 1.2f, 0f); // Use this for initialization void Start () { characterController = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); state = State.normal; } } |
スクリプト内で使用するCarControllerスクリプトはUnityStandardAssets.Vehicles.Carパッケージで定義されているのでusingディレクティブを記述しておきます。
キャラクターの状態であるStateにはnormal、rideCarの二つの状態を作成します。
ridePositionは車に乗った時の車からの相対位置を指定します。
今回は車の上に乗るような位置を設定しています。
Updateメソッド
Updateメソッドではキャラクターの状態に応じて処理を分岐しています。
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 | void Update () { if (state == State.normal) { if (characterController.isGrounded) { velocity = Vector3.zero; var input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); if (input.magnitude > 0f) { animator.SetFloat("WalkSpeed", input.magnitude); transform.LookAt(transform.position + input.normalized); velocity = input.normalized * walkSpeed; } else { animator.SetFloat("WalkSpeed", 0f); } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } else if(state == State.rideCar) { if(Input.GetButtonDown("Fire1")) { // 車のスピードが一定以下になったら車を降りれるようにする if (transform.parent.GetComponentInChildren<CarController>().CurrentSpeed < 0.5f) { SetState(State.normal); } else { // スピードが速すぎる時はコンソールに速度表示 Debug.Log(transform.parent.GetComponentInChildren<CarController>().CurrentSpeed); } } } } |
キャラクターがState.normal(通常)の状態の時はただの移動処理をしているだけなので割愛します。
State.rideCar状態の時でマウスの左クリックを押した時はキャラクターの親の要素から子要素に向けてCarControllerスクリプトを検索し、CurrentSpeedプロパティで車の速度を求めています。
0.5より下の速度だったらキャラクターの状態をState.normalにし車から降りる処理をします。
CarControllerスクリプトをキャラクターの親の要素から検索しているのは、キャラクターが車に乗っている時はキャラクターを車の子要素にしている為、いったん親のCarに戻ってから子要素のCarControllerスクリプトを検索しています。
車の速度が0.5以上であったら確認の為にコンソールに車の速度を表示しています。
SetStateメソッド
SetStateメソッドではキャラクターの状態変更時の処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public void SetState(State tmpState, Transform vehicleTra = null) { state = tmpState; if(state == State.rideCar) { GetComponent<CharacterController>().enabled = false; animator.SetFloat("WalkSpeed", 0f); transform.SetParent(vehicleTra); transform.rotation = Quaternion.LookRotation(vehicleTra.forward); transform.localPosition = ridePosition; } else if(state == State.normal) { transform.parent.GetComponentInChildren<ActivateCarScript>().GetOffCar(); transform.position = transform.parent.Find("CarStand").position; transform.SetParent(null); GetComponent<CharacterController>().enabled = true; } // 乗るのと降りるキーを同じにした為、入力をリセットする必要がある Input.ResetInputAxes(); } |
State.rideCarに状態を変更した時は車に乗っている状態にするのでキャラクターのコライダの無効化、アニメーションパラメータのWalkSpeedを0にしIdle状態へ遷移させます。
また、引数で受け取ったCarをキャラクターの親に設定する為、SetParentメソッドで親を設定します。
キャラクターの向きはCarが向いている方向の角度をQuaternion.LookRotationで求め、キャラクターの角度をその角度に指定します。
キャラクターは既にCarの子要素になっているのでCarの位置から相対的にridePosition分を移動しています。
State.normal状態の時は車から降りる状態なので親からActivateCarScriptを検索しGetOffCarメソッドを呼び出して車を降りる時の処理をします。
キャラクターの位置はCarStandの位置にし、SetParentメソッドにnullを渡し車との親子関係を解除しています。
車を降りたら通常通りキャラクターを動かせないと困るのでCharacterControllerを有効にします。
車を乗る時と降りる時に押すべきボタンをFire1で統一している為、このままだと車に乗った瞬間に車を降りる処理が実行され車に永遠に乗れません・・・・(^_^;)
そこでInput.ResetInputAxes()メソッドを実行し、1回のUpdate内の処理でのキー操作をリセットし、連続して乗る→降りるという処理を1回のUpdateで実行しないようにします。
これでキャラクターが車に乗る機能が完成しました!
終わりに
実はキャラクターが車から降りる前にブレーキ(進行方向と逆のキーボードの↓のキー等)を押してブレーキランプを点灯している時に車から降りるとブレーキランプが点灯したままになります。
これは面倒くさいので対処しませんでした。(-_-)
Carの子要素のSkyCarの子要素のSkyCarBrakeLightsGlowのMeshRendererコンポーネントのオン・オフでこのブレーキランプの点灯を操作しています。
そこら辺を車を降りた時に必ずオフにするような処理を作ればブレーキランプ問題も解決しそうです。
ブレーキランプを点灯しているスクリプトはSkyCarBrakeLightsGlowに設定してあるBrakeLightスクリプトで行っており、車のブレーキの入力値が0以上の時に常にMeshRendererを有効にしています。
ここら辺も少し処理を変えないとうまくいかないようですね。(^_^;)
あとは最初に車のRigidbodyのIsKinematicのチェックを外しておくとキャラクターとの接触で車が動いてしまう事があります。
今回の機能は
の機能を簡略化したものなので、細かい部分は「一人用フリーフォール」の記事を参照してみてください。
でも、「乗り物にキャラクターを乗せて動かす」という機能が結構簡単に作れるのがわかりましたね。(^^)/