Unity 3dタンクチュートリアル:シャーシ(レッスン2.追跡済みシャーシ)

レッスン1 <<



画像



エントリー



自宅で戦車を組み立てるレッスンを続けます。 前のレッスンでは、Unityで車両を運転する基本について学びました。このレッスンでは、このデモで確認できることを行います。



つまり、3Dエディタで追跡されたシャーシをより適切にモデル化して、適切に移動して地形のバンプに対応できるようにする方法について説明します。また、デモで見ることができる既製のモデルを提供し、その後、このすべてを実現し、もたらす方法を学習しますWheel Collidersで動きを設定します。



したがって、優先順位の問題の決定を進める前に、最初にそれらを策定する必要があります。前のレッスンから、車輪を回転させて不整地に対応させる方法をすでに知っていますが、問題は車輪(トラックローラー)に加えて、タンクが多くのキャタピラーを持っていることですトラックローラーが回転するのと同じ速度で移動する部品(トラック)ほど、キャタピラは車輪とともに不均一な地形に反応するはずです。 これをどうやってやるの? 実装する前に、これをシミュレートする必要があります。



1.モデリングテクニックの追跡



奇妙なことではありませんが、これらの方法はキャタピラーの移動方法と密接に関連していますが、当然、すべては、その移動を実装するエンジンが提供する可能性によっても異なります。 Unity 3dの場合、2つのシナリオがあります。

  1. この例のように、多くの別個のオブジェクトからの毛虫をシミュレートします。

    (3ds max)

    画像



    後続のトラックの位置を取得して各トラックを移動し、現在のトラックの位置を次のトラックの位置に徐々に補間し、 ColliderクラスのRaycast()メソッドを使用して地形の不規則性に基づいてトラックの変形を計算します。これにより、特定の方向に特定の長さのビームが投影され、戻りますコライダーと交差する場合はtrue。

    この方法の利点:キャタピラーの詳細度が高く、キャタピラーの動きがリアルになります。

    欠点:実装の複雑さ、以降のトラック数に応じた計算能力への負荷 それぞれについてRaycast()を計算する必要があります。それらが多すぎる場合、パフォーマンスの低下は避けられません。

    正直なところ、私はこのメソッドを実装しようとしませんでした。 gamedev フォーラムに登ると、2番目が見つかりました。

  2. キャタピラーを連続テープでモデル化します。

    (3ds max)





    次に、1つ以上のトラックの繰り返しテクスチャを適用します。



    ほら、すべてがここでよりシンプルになりました。キャタピラーのトラックの動きの錯覚は、テクスチャ座標を変更してテクスチャを動かすことで作成できます。 また、最初のレッスンからWheelColliderのホイールに取り付けられたGetGroundHit()メソッドに基づいてホイールの位置を計算する方法をすでに知っているため、地形の不規則性に応じた変形は、トラックローラーが配置されているテープのそれらの場所で骨を結ぶことによって実行できますテープが取り付けられている骨の位置は難しくありません。 または、同じRaycastを使用することもできます(誰でも、WheelColliderサスペンションへのバインディングに満足しています)。

    この方法の利点:実装の容易さ、計算の負荷が少ないこと。

    短所:トラックの詳細は最初の方法に比べてそれほど高くありません(質問は相対的ですが、World of Tanks、Crysis、Battlefield:Bad Companyで同じことがどのように行われたかを確認してください)そしてそれは非常に現実的に見える)。



そこで、2番目の方法を選択し、次のように変形できるようにキャタピラにボーンをアタッチします。

(3ds max)









スクリーンショットを見るとわかるように、車輪もキャタピラーと同じ骨に取り付けられています。 試してみて、ある種のプリミティブを作成し、それをボーンまたはいくつかにアタッチしてからUnityにエクスポートします( ここでさまざまな3Dモデリングパッケージからのエクスポートについて読むことができます。すべてをFBX形式にエクスポートして、結果のファイルをフォルダーにコピーすることをお勧めしますプロジェクトのアセット。その後、プロジェクトの[プロジェクト]タブでこのファイルの名前を確認し、さらに操作するためにシーンにドラッグするだけです。 その後、モデル自体を選択して、移動、回転、ストレッチを試みます。 わかった? そうです、うまくいきません! 事実、Unityでは、ボーン(ボーン)にアタッチされたスキンメッシュはその変換のスレーブになり、アタッチされたボーンで同じ操作を実行しようとすると成功します。



したがって、ホイールを回転させる必要があるため、ホイールを骨に固定する必要はありません。 それらを解くと、次のようになります。

(3ds max)





基本的にはこれで、モデルの準備は完了しました。約束したとおり、 モデルトラックのテクスチャーへのリンクを提供しました。 モデルはFBX形式であるため、この形式をサポートする3dエディターにインポートし(私の知る限り、すべての一般的なエディターがサポートします)、作成方法とボーンの接続方法を詳細に調べるか、すぐにUnityにインポートして既に確認できます実際には、次に実際に行うこと。



2.モデルをインポートしてスクリプトを準備する



したがって、上で書いたように、モデルとテクスチャをプロジェクトのAssetsフォルダーにコピーするか、エクスプローラーからプロジェクトタブに直接ドラッグすることができます。主なことは、モデルとテクスチャが同じフォルダーにあるため、何らかの理由で、トラックにテクスチャが表示されない(モデルを自然にステージにドラッグする必要がある前に)、または別のフォルダーに投げたい場合は、トラックの1つを選択するだけです(Track_line_leftおよびTrack_line_rightと呼ばれます) )、インスペクタで テクスチャ設定に移動し、選択ボタンを押してトラックテクスチャを選択します。

画像

また、トラックの数を増やすために、画像のように、yのタイリングを2に設定します。



新しいC#スクリプトを作成し(Assets-> Create-> C Sharp Script)、TankTrackControllerという名前を付けて開き、将来使用する必要がある変数を宣言します。



using UnityEngine; using System.Collections; using System.Collections.Generic; //1 public class TankTrackController : MonoBehaviour { public GameObject wheelCollider; //2 public float wheelRadius = 0.15f; //3 public float suspensionOffset = 0.05f; //4 public float trackTextureSpeed = 2.5f; //5 public GameObject leftTrack; //6 public Transform[] leftTrackUpperWheels; //7 public Transform[] leftTrackWheels; //8 public Transform[] leftTrackBones; //9 public GameObject rightTrack; //6 public Transform[] rightTrackUpperWheels; //7 public Transform[] rightTrackWheels; //8 public Transform[] rightTrackBones; //9 public class WheelData { //10 public Transform wheelTransform; //11 public Transform boneTransform; //12 public WheelCollider col; //13 public Vector3 wheelStartPos; //14 public Vector3 boneStartPos; //15 public float rotation = 0.0f; //16 public Quaternion startWheelAngle; //17 } protected WheelData[] leftTrackWheelData; //18 protected WheelData[] rightTrackWheelData; //18 protected float leftTrackTextureOffset = 0.0f; //19 protected float rightTrackTextureOffset = 0.0f; //19 }
      
      





  1. 動的リストを使用するために必要な名前空間を解決します。それらは後で便利になります。
  2. Wheel Colliderのプレハブが格納される変数を宣言します(前のレッスンの項目2を思い出してください)。
  3. ホイールの半径。
  4. 表面に接触していないときの開始位置に対するホイールのオフセット。
  5. キャタピラーの速度(実際には、テクスチャ座標の変位の速度)。
  6. 左右のトラック。
  7. ホイールコライダーが追加されない左右の上部ホイール。
  8. Wheel Colliderを追加するホイール(前のレッスンとは異なり、Wheel Colliderの配列は宣言しませんでした。ホイールの位置を使用してスクリプトから直接ホイールに追加されるため、押す前にホイールを動かさないでください。 [再生]ボタンで、それらは互いに相対的に整列する必要があります。
  9. 左右の毛虫に付着した骨。
  10. 各ホイールについて必要な情報(UpperWheelsを除く)を保存するクラスを宣言します。
  11. 車輪の変形。
  12. キャタピラーに取り付けられた骨を変形させます。
  13. WheelColliderホイール;
  14. ホイールの開始位置。
  15. 骨の開始位置。
  16. ホイール回転角。
  17. ホイールの回転の初期角度。
  18. ここでわかるように、保護されたアクセス修飾子が使用されているため、データをクラス外で変更できないように、左右のホイールにデータを格納する配列を宣言します。
  19. そして最後に、トラック上のテクスチャ座標の現在のオフセットを格納する変数を宣言します。


したがって、メイン変数は宣言され、それらをモデルに関連付け、スクリプトを保存し、エディターに移動し、スクリプトをtankオブジェクトにドラッグします(もちろん名前を変更しない限り)。



ご覧のように、tankオブジェクト自体に移動します。添付されているスクリプトに加えて、Animationオブジェクトがあります。そこから何でも安全に削除できます。

画像



次に、WheelColliderを使用してオブジェクトのプレハブを作成します(前のレッスンの項目2を思い出します)。これをtank_colliderと呼びます。次のパラメーターがあります。

画像



オブジェクトをスクリプトにドラッグし始めます。最初にできることは、新しく作成したプレハブをスクリプト内のWheel Colliderフィールドにドラッグすることです。 次に、トラック(Track_line_leftおよびTrack_line_rightと呼ばれます)をLeft TrackおよびRight Trackフィールドにドラッグします。 次に、上部の車輪(Upper_wheel [number] _left、Upper_wheel [number] _right)を左トラックの上部ホイールと右トラックの上部ホイールの配列に配置します。 さて、ホイールとボーンの残りの部分(ホイールはrowheel_ [number] _rightとrowheel_ [number] _leftと呼ばれ、ボーンはSuspension_bone [number] _leftとSuspension_bone [number] _rightと呼ばれる)を理解できると思います。簡単にするために、特別に番号を付け、Chain_bone [number] _leftおよびChain_bone [number] _rightと呼ばれるボーンには触れないでください。キャタピラーの静的な部分はそれらに接続されています。



最終的には、次のようになります。

画像



次に、tankオブジェクトを選択し、次のパラメーターを使用してRigidbodyを追加します。

画像



次に、ハルの子オブジェクト(タンク本体)を見つけ、それにメッシュコライダーを追加し(コンポーネント->物理->メッシュコライダー)、凸ボックスをオンにします(このチェックボックスは、このメッシュコライダーがタンク本体に含まれるすべての三角形の衝突を計算しないことを意味しますが、最大255個の三角形を含む独自のメッシュを作成します)



3.ホイールコライダーとホイールパラメーターを設定する



そこで、変数を宣言して初期化し、先に進み、ホイールパラメーターをWheelData [] leftTrackWheelDataおよびWheelData [] rightTrackWheelData配列に保存することから始めます。 これをAwake()関数内で行い、補助関数WheelData SetupWheels()を宣言します。これは、すべての操作を行う前に、WheelData型の値を返す必要があります。 すべて次のようになります。

 void Awake() { leftTrackWheelData = new WheelData[leftTrackWheels.Length]; //1 rightTrackWheelData = new WheelData[rightTrackWheels.Length]; //1 for(int i=0;i<leftTrackWheels.Length;i++){ leftTrackWheelData[i] = SetupWheels(leftTrackWheels[i],leftTrackBones[i]); //2 } for(int i=0;i<rightTrackWheels.Length;i++){ rightTrackWheelData[i] = SetupWheels(rightTrackWheels[i],rightTrackBones[i]); //2 } Vector3 offset = transform.position; //3 offset.z +=0.01f; //3 transform.position = offset; //3 } WheelData SetupWheels(Transform wheel, Transform bone){ //2 WheelData result = new WheelData(); GameObject go = new GameObject("Collider_"+wheel.name); //4 go.transform.parent = transform; //5 go.transform.position = wheel.position; //6 go.transform.localRotation = Quaternion.Euler(0,wheel.localRotation.y,0); //7 WheelCollider col = (WheelCollider) go.AddComponent(typeof(WheelCollider));//8 WheelCollider colPref = wheelCollider.GetComponent<WheelCollider>();//9 col.mass = colPref.mass;//10 col.center = colPref.center;//10 col.radius = colPref.radius;//10 col.suspensionDistance = colPref.suspensionDistance;//10 col.suspensionSpring = colPref.suspensionSpring;//10 col.forwardFriction = colPref.forwardFriction;//10 col.sidewaysFriction = colPref.sidewaysFriction;//10 result.wheelTransform = wheel; //11 result.boneTransform = bone; //11 result.col = col; //11 result.wheelStartPos = wheel.transform.localPosition; //11 result.boneStartPos = bone.transform.localPosition; //11 result.startWheelAngle = wheel.transform.localRotation; //11 return result; //12 }
      
      





  1. ホイールに関する情報を含む配列の次元を宣言します。
  2. これらの配列を埋めます。このため、上記で書いたように、最初の引数にTransformホイールを渡し、2番目の引数にTransformボーンを渡すSetupWheels()関数を作成しました。
  3. 奇妙なことですが、私がプレイモードに入ったとき、コライダーは絶えず地下に落下しました。幸いなことに、これは単にタンクオブジェクトを少し前方に動かすことで処理されます。
  4. 名前がCollider_プレフィックスを持つホイールの名前で構成される新しい空のGameObjectを作成します。
  5. 新しく作成されたオブジェクトを、スクリプトがアタッチされているオブジェクトの子(この場合はtankオブジェクトの子)にします。
  6. 新しく作成したオブジェクトを、ホイールがある場所に移動します。
  7. 現在のホイールが回転する角度と同じ角度だけ、オブジェクトをY軸を中心にローカルに回転します。
  8. Wheel Colliderコンポーネントを空のGOに追加します。
  9. プレハブからWheel Collider設定を取得します。
  10. プレハブから取得した設定を新しく作成したWheel Colliderに割り当てます(ご覧のとおり、各設定を個別に割り当てる必要がありました。この場合、col = colPrefと書くだけではコンパイラはこの設計をスキップしますが、Collider設定はデフォルトのままです)。
  11. 必要な残りの情報を割り当てます。
  12. 結果を返します。


安全に「再生」をクリックして、各ホイールに各コライダーを手動で追加した最初のレッスンではなく、ホイールコライダーが下のホイールに自動的に追加されるようにすることができます。このアプローチの利点について話すべきではないと思います。



4.車輪とトラックを「復活」させる



最初のレッスンから、車輪を回転させて地形の不規則性に対応する方法を既に知っている必要があります。ここでは同じ原理を使用しますが、乗用車の場合、前輪と後輪は異なる速度で回転できますが、タンクには別々のトラックローラーがありますキャタピラーは同じ速度で回転するため、車輪の特定の平均回転速度を見つける必要があります。 そして当然、キャタピラーのテクスチャの移動速度をこの速度にバインドする必要があります。 すべてについて詳しく説明しましょう。

 void FixedUpdate(){ UpdateWheels(); //1 } public void UpdateWheels(){ //1 float delta = Time.fixedDeltaTime; //2 float trackRpm = CalculateSmoothRpm(leftTrackWheelData); //3 foreach (WheelData w in leftTrackWheelData){ //4 w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); //5 w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); //6 w.rotation = Mathf.Repeat(w.rotation + delta * trackRpm * 360.0f / 60.0f, 360.0f); //7 w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); //8 } leftTrackTextureOffset = Mathf.Repeat(leftTrackTextureOffset + delta*trackRpm*trackTextureSpeed/60.0f,1.0f); //9 leftTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-leftTrackTextureOffset)); //10 trackRpm = CalculateSmoothRpm(rightTrackWheelData); //3 foreach (WheelData w in rightTrackWheelData){ //4 w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); //5 w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); //6 w.rotation = Mathf.Repeat(w.rotation + delta * trackRpm * 360.0f / 60.0f, 360.0f); //7 w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); //8 } rightTrackTextureOffset = Mathf.Repeat(rightTrackTextureOffset + delta*trackRpm*trackTextureSpeed/60.0f,1.0f); ///9 rightTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-rightTrackTextureOffset)); //10 for(int i=0;i<leftTrackUpperWheels.Length;i++){ //11 leftTrackUpperWheels[i].localRotation = Quaternion.Euler(leftTrackWheelData[0].rotation, leftTrackWheelData[0].startWheelAngle.y, leftTrackWheelData[0].startWheelAngle.z); //11 } for(int i=0;i<rightTrackUpperWheels.Length;i++){ //11 rightTrackUpperWheels[i].localRotation = Quaternion.Euler(rightTrackWheelData[0].rotation, rightTrackWheelData[0].startWheelAngle.y, rightTrackWheelData[0].startWheelAngle.z); //11 } } private float CalculateSmoothRpm(WheelData[] w){ //12 float rpm = 0.0f; List<int> grWheelsInd = new List<int>(); //13 for(int i = 0;i<w.Length;i++){ //14 if(w[i].col.isGrounded){ //14 grWheelsInd.Add(i); //14 } } if(grWheelsInd.Count == 0){ //15 foreach(WheelData wd in w){ //15 rpm +=wd.col.rpm; //15 } rpm /= w.Length; //15 }else{ //16 for(int i = 0;i<grWheelsInd.Count;i++){ //16 rpm +=w[grWheelsInd[i]].col.rpm; //16 } rpm /= grWheelsInd.Count; //16 } return rpm; //17 } private Vector3 CalculateWheelPosition(Transform w,WheelCollider col,Vector3 startPos){ //18 WheelHit hit; Vector3 lp = w.localPosition; if (col.GetGroundHit(out hit)) { lp.y -= Vector3.Dot(w.position - hit.point, transform.up) - wheelRadius; }else { lp.y = startPos.y - suspensionOffset; } return lp; }
      
      





  1. FixedUpdate()関数内で関数UpdateWheels()を呼び出し、ホイールの位置と回転角度を計算します。
  2. レッスン1を参照してください。
  3. 関数CalculateSmoothRpm()は、車輪の平均速度を計算し(後で詳しく説明します)、車輪が同じ速度で回転するように、leftTrackWheelData []配列全体を渡し、次にrightTrackWheelData []配列を渡します。
  4. 左右の車輪にデータを含む配列のすべての要素に対して、次の操作を実行します。
  5. Y軸に沿ったホイールのローカル位置を計算します。これは、CalculateWheelPosition()関数(詳細)で役立ちます。この関数には、ホイールの変換、そのWheelCollider、および初期ローカル位置が渡されます。
  6. 同じ操作。今回は、キャタピラーのこの部分が取り付けられている骨のローカル位置を計算します。
  7. ホイールの回転角度を計算します(レッスン1を参照)が、今回はコライダーのrpmではなく、以前に計算された平均回転速度を使用します。
  8. 計算された回転角度をホイールのローカル回転角度に適用します(レッスン1を参照)。
  9. キャタピラーのテクスチャのオフセットを計算します。 残念ながら、ホイールを回転させてキャタピラーを同じ速度で動かすことができる普遍的な公式は見つかりませんでしたので、追加の変数trackTextureSpeed(上記を参照)を導入する必要がありましたが、これは後でホイールとキャタピラーが均一な速度で動くように手動で調整する必要があります
  10. キャタピラーオフセットを、Y座標(新しいVector2(0、-leftTrackTextureOffset))、GO leftTrackおよびrightTrackで使用されるマテリアルのメインテクスチャ( "_MainTex")に適用します。
  11. WheelColliderが取り付けられていない上部の車輪の回転を計算します。他の車輪から回転角を借りることができますが、すべて同じ速度で動きます。
  12. 最後に、CalculateSmoothRpm()関数に到達しました。この関数は、WheelData型の配列を最初の引数として受け取り、元の名前はwです。
  13. WheelCollider colが現在表面(地形)に接触している配列wの要素のインデックスを含む新しい動的リストを作成します。
  14. 配列を調べて、コライダーがサーフェスに触れる要素のインデックスを見つけます。
  15. リスト内の要素数がゼロの場合、つまり、どのコライダーも地面に触れていない場合、配列w内のすべてのrpmコライダーを合計し、結果の値を配列wの要素数で除算して、平均値を求めます。
  16. リストの要素が1つ以上ある場合、つまり1つ以上のコライダーが地面に触れている場合は、これらのコライダーのみの速度を合計し、リスト内の要素の数で除算します。
  17. 結果の値を返します。 (質問があります:地面に接触するコライダーを計算するためのこれらすべての操作と、すべてのホイールの平均回転速度を常に見つけることができない理由は何ですか?答えは宿題です。状況:

    画像
  18. 関数CalculateWheelPosition()は、ホイール(またはボーン)の変換を最初の引数、ホイールColliderの2番目、およびホイール(またはボーン)の開始ローカル位置の3番目として受け取ります。 上で述べたように、この関数はホイール(またはボーン)のローカル位置を計算し、最初のレッスンでこのアルゴリズムを検討したため、まったく新しいものを運びません。


これで、Playを安全にクリックして、車輪が動いていることを確認できますが、戦車はまだ運転方法を知らないので、平らでない場所に移動してください。 また、車輪が地下に移動したことは重要であることに注意してください。変数wheelRadiusもあり、トラックのある車輪が地面に位置するようにPlayモードを終了せずに値を調整します。変数trackTextureSpeedの値は、トラックの動きを同期します車輪の動きで。 私のデータ値は次のとおりです。

画像



5.乗ることを学ぶ



おそらく、従来の自動車と区別される無限軌道車両の動きの最も重要な特徴は何ですか? これは適切な転換点だと思います。 信じられない? ご覧ください 。 普通の車に向きを変える機会があれば、金髪の人が駐車場を離れるのを見ることができるので、前の車に頻繁に衝突することはありません。



それでは、どうやってタンクを所定の位置に回すことができますか? 上記のビデオからわかるように、すべてがとてつもなく単純です。1つの毛虫が前方に移動し、他の毛虫が後方に移動するため、タンクはその軸の周りを回転します。 移動中のタンクの回転とほぼ同じ方法ですが、回転する側にあるキャタピラーは反対方向に移動せず、少し減速します。



最も簡単なものから始めて、戦車にその場で電源を入れるように教え始めることを提案します。 ここではすべてが明らかであるように見えました。 そして、これを行うことができます。

 public float rotateOnStandTorque = 1500.0f; //1 public float rotateOnStandBrakeTorque = 500.0f; //2 public float maxBrakeTorque = 1000.0f; //3 void FixedUpdate(){ float accelerate = 0; float steer = 0; accelerate = Input.GetAxis("Vertical"); //4 steer = Input.GetAxis("Horizontal"); //4 UpdateWheels(accelerate,steer); //5 } public void UpdateWheels(float accel,float steer){ //5 float delta = Time.fixedDeltaTime; float trackRpm = CalculateSmoothRpm(leftTrackWheelData); foreach (WheelData w in leftTrackWheelData){ w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); w.rotation = Mathf.Repeat(w.rotation + delta * trackRpm * 360.0f / 60.0f, 360.0f); w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); CalculateMotorForce(w.col,accel,steer); //6 } leftTrackTextureOffset = Mathf.Repeat(leftTrackTextureOffset + delta*trackRpm*trackTextureSpeed/60.0f,1.0f); leftTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-leftTrackTextureOffset)); trackRpm = CalculateSmoothRpm(rightTrackWheelData); foreach (WheelData w in rightTrackWheelData){ w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); w.rotation = Mathf.Repeat(w.rotation + delta * trackRpm * 360.0f / 60.0f, 360.0f); w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); CalculateMotorForce(w.col,accel,-steer); //6 } rightTrackTextureOffset = Mathf.Repeat(rightTrackTextureOffset + delta*trackRpm*trackTextureSpeed/60.0f,1.0f); rightTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-rightTrackTextureOffset)); for(int i=0;i<leftTrackUpperWheels.Length;i++){ leftTrackUpperWheels[i].localRotation = Quaternion.Euler(leftTrackWheelData[0].rotation, leftTrackWheelData[0].startWheelAngle.y, leftTrackWheelData[0].startWheelAngle.z); } for(int i=0;i<rightTrackUpperWheels.Length;i++){ rightTrackUpperWheels[i].localRotation = Quaternion.Euler(rightTrackWheelData[0].rotation, rightTrackWheelData[0].startWheelAngle.y, rightTrackWheelData[0].startWheelAngle.z); } } public void CalculateMotorForce(WheelCollider col, float accel, float steer){ //6 if(accel == 0 && steer == 0){ //7 col.brakeTorque = maxBrakeTorque; //7 }else if(accel == 0.0f){ //8 col.brakeTorque = rotateOnStandBrakeTorque; //9 col.motorTorque = steer*rotateOnStandTorque; //10 } }
      
      





  1. タンクが所定の位置にあるときにコライダーに伝達するトルク。
  2. タンクが所定の位置にあるときにコライダーに送信するブレーキモーメント。
  3. 最大制動トルク。
  4. レッスン1を参照してください。
  5. UpdateWheels()関数を変更して、仮想軸から値を取得できるようにします。
  6. CalculateMotorForce()関数を使用してコライダーのトルクと制動モーメントを制御します。コライダーと仮想軸をこの関数に渡します(leftTrackWheelDataには水平軸から正の値を渡し、rightTrackWheelDataには負の値を渡します。これにより、トラックを異なる方向に移動できます) )
  7. 移動キーが押されていない場合は、最大の制動トルクをコライダーに転送します(不均一な場所から転がらないようにします)。
  8. 進むボタンは押されていないが、横ボタンが押されている場合:
  9. 制動モーメントをコライダーに転送します。
  10. 横軸から取得した値を掛けたトルクを送信します(このトルクは、タンクが移動するためにrotateOnStandBrakeTorqueの制動トルクよりも大きくなければなりません)。


そのため、[再生]をクリックしてから、横向きのキー(AまたはD)をクリックします。 そして、私たちが見ているのは、私たちの戦車がその場で向きを変えようとする哀れな試みだけであり、力が足りないように感じます。 もちろん、変数rotateOnStandTorqueの値を増やすことはできますが、結果は非常に面白いものになります。



実際には、トルクの問題ではなく、タンクを回すのに十分以上です。 ルーツに戻りましょう。つまり、設定がタンクのすべてのWheelColliderに継承されるプレハブtank_colliderに戻ります。 横摩擦フィールドに注意してください。前のレッスンでこの設定について言及しましたが、これはホイールの「横方向の」摩擦力であり、ドリフトを実装する場合に役立ちます。 現在、側面からコライダーに作用するこの非常に摩擦力は非常に大きく、ホイールトルクはそれを克服できないため、タンクは回転しません。 ここで、横方向摩擦内の剛性係数変数に注意してください。これが必要です。実際、これは横方向の摩擦力に乗じる数値であり、ゼロに設定して再生ボタンを押します。

画像



奇跡的に、私たちの戦車は夢中になり、最高速度で回転できるようになり、さらに横方向にドライブすることもできます。 Playモードを終了し、値を0.06に設定して、もう一度Playを押します。 さて、最後に、今や私たちの戦車はその軸の周りを回っており、かなり適切です。 プレイモードを終了し、Sideways Frictionの値を1に戻します。もちろん、プレハブから直接値を変更するのは良いことです。しかし、サイドフリクションを制御できない場合、タンクが実際のドリフトに入る可能性があるため、スクリプトからこれを行う方が良いです。 同時に、運転中に前進して曲がるように戦車に教えます。 関数CalculateMotorForce()を変更し、さらにいくつかのグローバル変数を宣言します。

 public float forwardTorque = 500.0f; //1 public float rotateOnMoveBrakeTorque = 400.0f; //2 public float minBrakeTorque = 0.0f; //3 public float minOnStayStiffness = 0.06f; //4 public float minOnMoveStiffness = 0.05f; //5 public float rotateOnMoveMultiply = 2.0f; //6 public void CalculateMotorForce(WheelCollider col, float accel, float steer){ WheelFrictionCurve fc = col.sidewaysFriction; //7 if(accel == 0 && steer == 0){ col.brakeTorque = maxBrakeTorque; }else if(accel == 0.0f){ col.brakeTorque = rotateOnStandBrakeTorque; col.motorTorque = steer*rotateOnStandTorque; fc.stiffness = 1.0f + minOnStayStiffness - Mathf.Abs(steer); }else{ //8 col.brakeTorque = minBrakeTorque; //9 col.motorTorque = accel*forwardTorque; //10 if(steer < 0){ //11 col.brakeTorque = rotateOnMoveBrakeTorque; //12 col.motorTorque = steer*forwardTorque*rotateOnMoveMultiply;//13 fc.stiffness = 1.0f + minOnMoveStiffness - Mathf.Abs(steer); //14 } if(steer > 0){ //15 col.motorTorque = steer*forwardTorque*rotateOnMoveMultiply;//16 fc.stiffness = 1.0f + minOnMoveStiffness - Mathf.Abs(steer); //17 } } if(fc.stiffness > 1.0f)fc.stiffness = 1.0f; //18 col.sidewaysFriction = fc; //19 if(col.rpm > 0 && accel < 0){ //20 col.brakeTorque = maxBrakeTorque; //21 }else if(col.rpm < 0 && accel > 0){ //22 col.brakeTorque = maxBrakeTorque; //23 } }
      
      





  1. 移動時のトルク(前方、後方)。
  2. 運転中のコーナリング時の制動トルク。
  3. 最小制動トルク。
  4. 所定の位置に回したときの最小横摩擦。
  5. 運転中の旋回時の最小横摩擦。
  6. 運転中に回すときのトルク乗数。
  7. WheelFrictionCurve型のfc変数を宣言し、Wheel ColliderのSideways Frictionを格納します。
  8. モーションキーが押された場合、またはモーションキーとターンキーが同時にある場合:
  9. コライダーに最小制動トルクを送信します。
  10. 縦軸から得られた値に最大トルクを掛けます。
  11. ( , , ) :
  12. ( , );
  13. ( , );
  14. fc.
  15. ( , , ) :
  16. , .
  17. fc.
  18. , , .
  19. sidewaysFriction fs ( col.sidewaysFriction.stiffnes = (float), ).
  20. , :
  21. .
  22. ホイールが後方に回転し、キーを前方に押すと:
  23. コメントはありません。


さて、それですべてです。あなたの戦車は警戒態勢に入っています。敵をまだ撃つことはできませんが、敵を潰すことができます。



結論として、これは戦車の動きに対する普遍的なアルゴリズムとはほど遠い、それはまだ機能しており、それに取り組んでいると言います。たとえば、私たちの戦車が300 kmまで加速できないように最大速度制限を規定する必要があります。1時間あたり。上記のすべてを理解していれば、簡単に修正できると思います。ご清聴ありがとうございました。次のレッスンがまもなく始まります。



All Articles