Unityで3Dの車の動きを作っているけど、どうやってドリフトを実現すればよいですか?というご質問を頂きました。「WheelCollider」というものを使えば、リクエストのドリフトに近い動きができるようです。 「WheelCollider」の使い方、調べましたがイマイチわかりにくかったので、実験を兼ねて試作品を作ってみました。
完成イメージは以下のようなもの。
1,自動車のモデル作成
WheelCollider を活用するには、その配置についてもルールがあるようです。そこを踏まえて、まずは車のモデルを作成します。
地面の作成
地面も併せて作っておきます。地面となる「Plane」と自動車の本体となる「Cube」を用意します。 Plane は次のように加工します。
transform Position: x:10、 y:1、z:10 (一時的に車が移動できる程度の大きさにします) Material:地面に一旦緑色のマテリアルを指定しておきます。(後で移動がわかるように柄付きの画像を追加します)
Body(本体)の作成
自動車の本体となる「Cube」は以下のように変更します。
name:Car position:x : 0、y:0.8、z:0(この後タイヤ部分を作成するので上部に上げておく) MeshRenderer:無効化(本体の見た目は透明にしておきます) Rigidbody Mass:1000(車体の重さを一旦、1,000㎏ 程度にしておきます)
作成した Car の子要素として「Capsule」を追加し、以下のように調整します。 Name:Body Transform Rotation:x:90、y:0、z:0(Capsuleを横向きいします) Transform Scsle:x:1.5、y:2、z:1(Capsuleの大きさを変更) CupsuleCollider Radius:0.4(大きさ変更によりColliderがはみ出したので調整します) Material:任意の色のマテリアルをセット(サンプルでは青色、metallicを強めにして金属の素材感を演出)
車の前後がわかるように、目印を付けておきます。上で作成した「Body」の配下に「Sphere」を作成、コックピットを作ります。「Sphere」は以下のように修正します。 Name:Cockpit Transform Position:x:0、y:0.6、z:-0.15(Body前部から若干はみ出す位置に) Transform Scsle:x:0.8、y:0.8、z:0.8(大きさを変更、任意の大きさにして下さい) SphereCollider:チェックをはずして無効化 Material:任意の色のマテリアルをセット(サンプルでは水色、これもmetallicを強めにしてガラスの素材感を演出)
タイヤ部分の作成
WheelCollider は、タイヤ4本をまとめて管理します。
タイヤ4本をまとめるオブジェクトを「Car」の子要素として「CreateEnputy」で作成、以下のように指定します。 Name:Frame Transform Position:x:0、y:-0.2、z:0(若干下部分に配置
まずはタイヤを1つ(左前のタイヤ)作成します。上で作成した「Frame」の配下に「Sphere」を配置、以下のように修正します。 Name:TireFL(Tire Front Left の意味) Transform Position:x:-08、y:0、z:1(Bodyの左前部分の位置に) Transform Scsle:x:0.2、y:1、z:1(タイヤのイメージに) SphereCollider:RemoveCompornent で外しておきます。
さらに、下のように追加変更 WheelCollider:Add Compornent から追加、プロパティを以下のように修正 Center:x:0、y:0.18、z:0 (なぜかCollider の位置が下にずれたようなので、中心を 0.18 だけ上部に移動) Material:タイヤの色のマテリアルを追加(任意の色で調整してください)
タイヤの回転がわかるように、配下にSpokeを追加します。 上で作成した「TireFL」の子要素に「CreateEnputy」で空のオブジェクトを作成、以下のように指定します。 Name:Sporke Transform 情報はすべてそのままで結構です。
さらに作成した「Sporke」の配下にスポークの実態を作りますが、一体作成し完成したものを複製します。ということで、「Spork」の配下に「Cube」を作成、以下のように調整します。 Name:Sporke_1 Transform Rotation:x:0、y:0、z:0(1体目はそのまま、複製したものはここを修正) Transform Scsle:x:1、y:0.9、z:0.05 BoxCollider:チェックをはずし、無効化 Material:シルバー系のマテリアルを作成しセットしています。
その後、作成した「Spork_1」を複製し、「Spork_2」「Spork_3」「Spork_4」としておきます。
複製したオブジェクトの Transform Rotation のx軸をそれぞれ以下のように修正します。 「Spork_2」 x:45 「Spork_3」 x:90 「Spork_4」 x:135 これで4本のスポークが均等に配置されるようになります。 左前方のタイヤのモデルは完成です。
作成した「TireFL」を複製し、以下のように変更します。 Name:TireFR(Tire Front Right の意味) Transform Position:x:08、y:0、z:1(x座標のみ変更しています)
最後は作成した「TireFL」「TireFR」を複製し、後輪「TireRL」「TireRR」を作ります。(それぞれ Tire Rear Left とTire Rear Right の意味)複製したタイヤは以下のように修正します。 TireRL Transform Position:x:-0.8、y:0、z:-1 TireRR Transform Position:x:0.8、y:0、z:-1
これでようやく自動車のモデルが完成しました。
2,自動車のプログラム
続いてスクリプトファイルを作成し、キー操作で自動車を動かしてみます。
移動のプログラム
「WheelController」というC#スクリプトファイルを作成し、以下のコードを入力します。
public float maxPower; //最高出力トルク
public float angle; //ハンドルを切った時の最高角度
public float breake; //ブレーキの力
[SerializeField] WheelCollider wcFR, wcFL, wcRR, wcRL; //4輪のWheelCollider
enum DRIBE { FRONT,REAR} //前輪駆動か後輪駆動かを指定する列挙型変数
DRIBE drive; //列挙型変数を管理する変数
void Start()
{
drive = DRIBE.REAR; //最初は前輪駆動で検証
}
void Drive()
{
float power = maxPower * Input.GetAxis("Vertical"); //前後の力
float steering = angle * Input.GetAxis("Horizontal"); //ハンドルを切る角度
wcFR.steerAngle = steering; //ハンドルの動きを右前タイヤに伝える
wcFL.steerAngle = steering; //ハンドルの動きを左前タイヤに伝える
if (drive == DRIBE.REAR) //後輪駆動なら
{
wcRR.motorTorque = power; //トルクを右後タイヤに伝える
wcRL.motorTorque = power; //トルクを左後タイヤに伝える
}
else //前輪駆動なら
{
wcFR.motorTorque = power; //トルクを右前タイヤに伝える
wcFL.motorTorque = power; //トルクを左前タイヤに伝える
}
}
void FixedUpdate()
{
Drive(); //ドライブ関数発動
}
このプロジェクトのテーマは「ドリフト」をさせることでしたね。いったん後輪駆動を優先で作成しますが、前輪にも力を加えられるように作っています。 ※ドリフトの仕組みに詳しくないもので、様々なケースに対応できるようにしています。
作成したスクリプトファイルを「Car」オブジェクトにアタッチします。 Car の Inspector から各変数を以下のように指定します。 MaxPower:2000(前後の移動出力の最大値) Angle:45(ハンドルを切った時にタイヤが向く角度の最大値) Breake:2500(ブレーキの力:前後の力より大きくするのが推奨の様です) 各WheelCollider の変数を下の図のようにセットします。
ではこの状態で起動させてみましょう。 上キーで前進、下キーでバック、左右キーで方向を変えることができたと思います。 しかし、動き出したら上下キーを離してもなかなか止まりません。ブレーキが無いので空気抵抗や摩擦で止まるのを待つしかありません。
ブレーキのプログラム
続いてブレーキ部分を作成します。以下のコードを追記してください。
void Breake()
{
if (Input.GetKey(KeyCode.LeftShift)) //左シフトが押されたら
{
wcFL.brakeTorque = breake;
wcFR.brakeTorque = breake;
wcRL.brakeTorque = breake;
wcRR.brakeTorque = breake;
}
else //そうでなければ
{
wcFL.brakeTorque = 0;
wcFR.brakeTorque = 0;
wcRL.brakeTorque = 0;
wcRR.brakeTorque = 0;
}
}
void FixedUpdate()
{
Drive(); //記述済み
Breake(); //ブレーキ関数発動
}
ブレーキの力は WheelCollider の breakTorque プロパティで管理します。左シフトキーを押すことで、breakTorque プロパティ に指定した値がセットされオブジェクトに停止する力が加わりますが、一度力を値を入力するとそのままになってしまいます。そこで、左シフトキーが押されていない状態ではbreakTorque プロパティに 0 の値を渡しておきます。
起動させてみてブレーキが利くのを確認しておきましょう。
タイヤの回転プログラム
ここまで作成してきて移動と停止は制御できましたが、タイヤが回転していないので違和感があります。続いてWheelColliderの持つ関数を使って、タイヤの回転プログラムを作成します。 新たにC#スクリプトファイル「TireManager」を作成し、以下のコードを記述します。なお「TireManager」は4本のタイヤ、それぞれにアタッチします。
Vector3 pos; //タイヤの座標を管理する変数
Quaternion rot; //タイヤの回転を管理する変数
WheelCollider wc; //それぞれのWheelCollider
Transform spoke; //それぞれのタイヤの子要素のSpoke
void Start()
{
wc = GetComponent<WheelCollider>(); //変数:wcに接続情報を取得し代入
spoke = transform.GetChild(0); //変数:spoke に子要素の Spoke を代入
}
void FixedUpdate()
{
wc.GetWorldPose(out pos, out rot); //WheelColliderから位置と回転情報を取得
spoke.transform.position = pos; //spoke の位置を指定
spoke.transform.rotation = rot; //spoke の角度を指定
}
作成済みの4本のTireオブジェクトをまとめて選択し、作成したスクリプトファイル(TireManager)をアタッチしておきます。
では起動させて、タイヤが回っているのを確認しましょう。スポークを作成しているので、動きが見えると思います。
3,カメラの追従プログラム
せっかく作った自動車ですが、遠くに行ってしまったら見えにくくなってしまいます。カメラが作った自動車を追従する仕組みを作っておきます。 Unity についている CinemaScene のカメラを使えば簡単にできるのですが、今回は簡単なプログラムを自作します。
新たなC#スクリプトファイル「FollowCamera」を作成し、「MainCamera」にアタッチしておきます。「FollowCamera」には以下のコードを記述します。
[SerializeField] Transform target; //追従するターゲット
[SerializeField ]Vector3 camerapos; //ターゲットとの距離
void Update()
{
transform.position = target.position + camerapos; //カメラの座標を指定
transform.LookAt(target); //ターゲットに向かせる
}
Inspectorから準備した変数に以下のオブジェクトと値をセットします。 Target:Carオブジェクト(のtransformコンポーネント) CameraPos:x:0、y:3、z:-7(車の 7.0 後ろ、3.0 上部)
これで車が遠くに行ってもカメラが追いかけてとらえることができるようになりました。
4,地面の追加
地面の加工
ここまで来たらもっと地面を広くしたいですね。 その前に、現状地面が無地でカメラとの距離も同じになったので、車の進んだ距離がわかりににくくなりました。そこで地面にテクスチャを貼り、視覚的に動いている速さがわかるようにしたいと思います。 サンプルでは Architecture textures pack(https://assetstore.unity.com/packages/2d/textures-materials/free-architectural-textures-23834?locale=ja)からAssetを取得して、その中の「Bricks grey」フォルダのBricks grey Mat というテクスチャを使ってみました。
各自お好みの素材を使ってみてください。
改造したPlaneをプレハブ化します。Asset内にドラッグ&ドロップ。ステージ上のPlaneは削除しておきます。
地面複製のプログラム
このプログラムはただ複製するだけなので、すでに作成済みの「FollowCamera」に任せたいと思います。FollowCamer に以下のコードを追記します。
[SerializeField] GameObject ground; //地面のプレハブを変数化
void Awake()
{
for(int i = 0; i < 100; i++) //100回繰り返す
{
//地面プレハブを、Z軸方向に 100×i 進んだ場所に生成する
Instantiate(ground, new Vector3(0, 0, i * 100), Quaternion.identity);
}
}
「Awake()関数」はプロジェクトを起動した際に一番最初に起動されるイベント関数です。一番最初に地面を作っておかないと、自動車が落下してしまいます。 今回は for()文 を使って地面を100面作ってみます。Planeの基準の大きさが縦横100なので、i(回)×100 ずつZ座標(前方)に移動した場所=Vector3(0, 0, i * 100) に生成します。
MainCamera の FollowCamera に作成した変数:Ground をセットするスペースができました。上で作成した「Groundプレハブ」をセットしておきます。
これでとても長いコースができました。
5,駆動輪の切替
ここまでのサンプルでは、後輪駆動のプログラムになっています。ドリフトの検証を進めるために、UIのボタンを利用して前輪駆動、後輪駆動の切替を行います。
ボタン作成
先にボタンを2つ作成しておきます。Hierarchy上に、UI ⇒ Button-TextMeshPro で2つのボタンを配置します。ボタンの配下には自動的に Text(TMP) が配置されています。 ※このサンプルではTMPの機能を活用しませんので、Legacy からButton を選択して頂いても結構です。
配置したボタンを以下のように指定します。Anchorを Right Top にしておくと、右上からの位置を指定することができます。画面サイズを大きくしたときもボタン位置が崩れません。 配下の Text(TMP) はFrontButton のサンプルを表示していますが、RearButton も同じような感じで作ってみましょう。
切替プログラム
ボタンに切り替えプログラムをセットしますが、そのプログラムは作成済みのスクリプトファイル「WheelCollider」に作っておきます。「WheelCollider」に以下のコードを追記します。
まずは NameSpace にUIのシステムを使うための宣言をします。
using UnityEngine.UI;
Start()関数では、初期設定の「後輪駆動選択ボタン」の色を青色に指定するコードを追記しています。 続いてclass内に2つの関数を追加します。 ChangeFront() 関数は前輪駆動へ切り替える関数です。先に変数:drive に enum 型の DRIBE.FRONT を渡して、その後「前輪駆動選択ボタン」を青に、「後輪駆動選択ボタン」を白に変更しています。 ChangeRear()関数はその逆ですね。
void Start()
{
drive = DRIBE.REAR; //記述済み
//RearButton を探してきて青色に変える
GameObject.Find("Canvas/RearButton").GetComponent<Image>().color = Color.blue;
}
//前輪駆動への切替
public void ChangeFront()
{
drive = DRIBE.FRONT;
GameObject.Find("Canvas/FrontButton").GetComponent<Image>().color = Color.blue;
GameObject.Find("Canvas/RearButton").GetComponent<Image>().color = Color.white;
}
//後輪駆動への切替
public void ChangeRear()
{
drive = DRIBE.REAR;
GameObject.Find("Canvas/FrontButton").GetComponent<Image>().color = Color.white;
GameObject.Find("Canvas/RearButton").GetComponent<Image>().color = Color.blue;
}
関数のセット
作成した関数をそれぞれのボタンにセットします。
まずは「FrontButton」から。Buttonコンポーネントの On Click() プロパティ部分、左下に「Car」オブジェクトをセットします。右上の関数を選択する部分で、WheelController ⇒ ChangeFront() 関数を選択します。
同様に「RearButton」にChangeRear()関数をセットして下さい。
これでボタンで、前輪駆動、後輪駆動の変更ができるようになりました。 出もうまくドリフトができない。なかなかドリフトって難しいですね? ん、バックするとうまくいく?ということはステアリングの対象wheelを変えれば・・・。
6,スピードメーター追加
ここまで来たらどんどんやりたいことが増えてきました。今度は「スピードメーター」を作ってみましょう。
テキスト作成
Hierarchy上に、UI ⇒Text-TextMeshPro からテキストを配置しました。 ※ここでも TMP の機能を使いませんので Legacy から Text を選択してもかまいません。 今回はテキストを下のように配置しました。
大きい画面にしたときも左上に配置されるように、Anchorを「Right Top」に指定しています。
さらに Textコンポーネント部分を以下のように指定しています。ここの部分は任意で色や大きさ、フォントのスタイルなどを変えてください。
スピード表示プログラム
スピードはRigidbodyコンポーネンVelocitytyプロパティから取得できます。velocity.magnitude()関数を使って秒速を取得、それを時速に換算し、小数点下2桁で表示させてみましょう。 まずは TextMeshPro を使うことをnameSpaceで宣言します。
using TMPro;
続いて、class内にコードを追記します。
[SerializeField] TMP_Text meter; //Meterのテキスト を使うための変数
Rigidbody rb; //Rigidbody を使うための変数
void Start()
{
rb = GetComponent<Rigidbody>(); //Rigidbodyコンポーネント接続情報取得
}
void FixedUpdate()
{
Drive(); //記述済み
Breake(); //記述済み
//MeterText にVelocity の大きさを時速換算、小数点第二位まで表示されるようにする
meter.text = Mathf.Floor(rb.velocity.magnitude * 3.6f * 100) / 100 + " Km/h";
}
コードができたら、CarのWheelController に変数の:Meter の枠ができていますので、SppedMeter オブジェクトを渡しておきます。 これで時速が表示されるようになっています。
本編はこれで完成となります。
さてテーマのドリフトですが、ステアリングをリアタイやにしたところ、見事に実現することができました。ただ気を付けることは、リアタイヤの場合、ステアリングが逆になります。右にハンドルを切ると左に曲がり、左にハンドルを切ると右に曲がる、ということで以下のような記述を追記してみましょう。
enum STEERING { FRONT, REAR } //ハンドル操作の対象
STEERING steer; //STEERINGを使うための変数
void Start()
{
steer = STEERING.FRONT; //デフォルトは前輪でハンドル操作
GameObject.Find("Canvas/StrFrontButton").GetComponent<Image>().color = Color.blue;
}
void Drive()
{
//前半記述済み 省略
if (steer==STEERING.FRONT) //前輪操作なら
{
wcFR.steerAngle = steering; //ハンドルの動きを右前タイヤに伝える
wcFL.steerAngle = steering; //ハンドルの動きを左前タイヤに伝える
}
else //後輪操作なら
{
wcRR.steerAngle = -steering; //ハンドルの動きを右後タイヤに伝える
wcRL.steerAngle = -steering; //ハンドルの動きを左後タイヤに伝える
}
}
//前輪操作切替関数
public void SteeringFront()
{
steer = STEERING.FRONT;
GameObject.Find("Canvas/StrFrontButton").GetComponent<Image>().color = Color.blue;
GameObject.Find("Canvas/StrRearButton").GetComponent<Image>().color = Color.white;
}
//後輪操作切替関数
public void SteeringRear()
{
steer = STEERING.REAR;
GameObject.Find("Canvas/StrFrontButton").GetComponent<Image>().color = Color.white;
GameObject.Find("Canvas/StrRearButton").GetComponent<Image>().color = Color.blue;
}
後は先に説明した操作と同じ要領で切替ボタンを作って、上で作った2つの関数 SteeringFront() と SteeringRear()
をボタンにセットしてみてください。
Comments