Unity学習記録 - 物理システムと衝突
序文
この記事は、中山大学ソフトウェア工学部の 2020 年 3D ゲーム プログラミングとデザインの課題 6 です。
プログラミングの質問: 単純な空飛ぶ円盤 - 物理エンジンの改良版
1. 質問要件
改良されたヒットUFOゲーム:
- ゲームコンテンツの要件:
- 押すアダプター モード設計図修正空飛ぶ円盤ゲーム
- 身体的な動きと運動学的な(変形)動きの両方をサポートできるようにします。
2.基本的な紹介
(1)Adapter模式1
- 定義: クラスのインターフェイスを顧客が望む別のインターフェイスに変換し、互換性のないインターフェイスのために連携できないクラスが連携できるようにします。
- アドバンテージ:
- クライアントはアダプターを通じてターゲット インターフェイスを透過的に呼び出すことができます。
- 既存のクラスが再利用されるため、プログラマは元のコードを変更して既存のアダプター クラスを再利用する必要がありません。
- ターゲット クラスとアダプター クラスを分離すると、ターゲット クラスとアダプター クラス間のインターフェイスの不一致の問題が解決されます。
- 多くのビジネス シナリオにおける開閉の原則に準拠しています。
- 実装: 既存のコンポーネント ライブラリに実装されているコンポーネントをアダプタ クラスに導入することにより、多重継承を通じて実装できます。アダプタ クラスは現在のシステムのビジネス インターフェイスも実装します。
(2)Unity物理エンジン
- 物理エンジン
Physici Engine は、ゲーム世界のオブジェクトを現実世界の物理属性 (重量、形状など) に割り当て、それらを剛体 (Rigid) モデル (滑車、ロープなども含む) に抽象化するソフトウェア組織です。オブジェクトは、 のアクションの下で、現実世界の動きとオブジェクト間の衝突プロセスをシミュレートできます。 Newton の古典力学モデルに基づいて、オブジェクトの動き、回転、衝突を計算するためのシンプルな API を提供し、現実に近い動きと衝突の効果を実現します。
-
unity物理引擎2
Unity 物理エンジンには主に、キャラクター コントローラー、剛体、力とモーメント、衝突体、トリガーなどのコンポーネントが含まれています。
- キャラクター コントローラー: 主に、剛体物理コンポーネントを使用しない三人称プレーヤー コントロールまたは一人称プレーヤー コントロールに使用されます。
- Rigidbody: ゲーム オブジェクトに物理属性を割り当て、ゲーム オブジェクトが物理システムの制御下で推力とねじれを受け入れることができるようにし、それによって現実世界でのモーション効果を実現します。
- 力とモーメント: 物体間の相互作用。このうち、物体の重心に作用する力は物体にその方向の加速度を発生させ、トルクは物体に角加速度を発生させます。
- コライダー: 物理衝突で使用するオブジェクトの形状を定義します。
- トリガー: イベントをトリガーするために使用され、衝突の検出に使用できます。
3. コードの解釈
このプロジェクトは以前のプロジェクトの改良版であり、主に物理エンジンを使用して空飛ぶ円盤の動きを実現し、アダプター モードを使用して物理的な動き部分のコードの再利用を実現します。コード部分の実装は比較的単純で、部分的な追加と変更のみが必要です。
必要な操作
前のプレハブを開き、剛体コンポーネントを追加し、空飛ぶ円盤が飛行中に全方向に回転しないように 3 軸の回転をロックします。
コード修正
この課題を書いているときに、前の課題にバグを発見しました。
前回の割り当てではモーション変換を計算する関数を使用したため、このバグは表示されませんでしたが、このバグはモーション エンジンを使用してモーションをシミュレートした後に発見できます。
具体的には、空飛ぶ円盤工場では、空飛ぶ円盤をリサイクルする際に、フリー オブジェクトの Active を false に設定するのを忘れたため、物理エンジンで後続の空飛ぶ円盤が元の値の一部を保持し、落下の原因となりました。修正 この問題は現在解決されています。
public void FreeDisk()
{
for (int i = 0; i < used.Count; i++)
{
if (used[i].gameObject.transform.position.y <= -10f)
{
used[i].gameObject.SetActive(false); // 此处修复了bug
free.Add(used[i]);
used.Remove(used[i]);
}
}
}
SSAアクション
物理エンジンに適応するために追加FixedUpdate()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject
{
public bool lived = true;
public bool deleted = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;
// 不允许代码中生成该类
protected SSAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
// 添加部分
public virtual void FixedUpdate() {
throw new System.NotImplementedException();
}
}
SSAアクションマネージャー
物理エンジンに適応するためにも追加されましたFixUpdate()
protected void Update()
{
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.deleted)
{
waitingDelete.Add(ac.GetInstanceID());
}
else if (ac.lived)
{
ac.Update();
ac.FixedUpdate(); // 新增部分
}
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
Object.Destroy(ac);
}
waitingDelete.Clear();
}
フライアクション
も追加されますFixUpdate()
が、前のコードには影響せず、親クラス コードを派生する機能のみを備えています
public class FlyAction : SSAction
{
public float gravity = -5; //向下的加速度
private Vector3 start_vector; //初速度向量
private Vector3 gravity_vector = Vector3.zero; //加速度的向量,初始时为0
private Vector3 current_angle = Vector3.zero; //当前时间的欧拉角
private float time; //已经过去的时间
private FlyAction() { }
public static FlyAction GetSSAction(int lor, float angle, float power)
{
//初始化物体将要运动的初速度向量
FlyAction action = CreateInstance<FlyAction>();
if (lor == -1)
{
action.start_vector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
}
else
{
action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
}
return action;
}
public override void Update()
{
//计算物体的向下的速度,v=at
time += Time.fixedDeltaTime;
gravity_vector.y = gravity * time;
//位移模拟
transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
transform.eulerAngles = current_angle;
//如果物体y坐标小于-10,动作就做完了
if (this.transform.position.y < -10)
{
this.deleted = true;
this.callback.SSActionEvent(this);
}
}
public override void FixedUpdate() { } // 新增部分
public override void Start() { }
}
Physisフライアクション
新しいクラスは、上記の FlyAction 実装と一致する物理エンジンを通じてモーションを実現します。
public class PhysisFlyAction : SSAction {
private Vector3 start_vector;
public float power;
private PhysisFlyAction() { }
public static PhysisFlyAction GetSSAction(int loc, float power) {
PhysisFlyAction action = CreateInstance<PhysisFlyAction>();
if (loc == -1) {
action.start_vector = Vector3.left * power;
}
else {
action.start_vector = Vector3.right * power;
}
action.power = power;
return action;
}
public override void Update() { }
public override void FixedUpdate() {
if (transform.position.y <= -10f) {
gameobject.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 0);
this.deleted = true;
this.callback.SSActionEvent(this);
}
}
public override void Start() {
gameobject.GetComponent<Rigidbody>().AddForce(start_vector*3, ForceMode.Impulse);
}
}
フライアクションマネージャー
空飛ぶ円盤の行動管理クラスに、新たに身体動作の呼び出し機能を追加しました。
考え方は基本的に先の運動学変換関数の考え方と同じですが、物理エンジンの特性上、パラメータとして力の値のみを渡す必要があり、角度を渡す必要はありません。
public class FlyActionManager : SSActionManager
{
public FlyAction fly;
public PhysisFlyAction ph_fly;
public FirstController scene_controller;
protected void Start()
{
scene_controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
scene_controller.action_manager = this;
}
public void DiskFly(GameObject disk, float angle, float power)
{
disk.GetComponent<Rigidbody>().isKinematic = true;
int loc = disk.transform.position.x < 0 ? 1 : -1;
fly = FlyAction.GetSSAction(loc, angle, power);
this.RunAction(disk, fly, this);
}
// 新增部分,与上面的函数实现思路一致
public void DiskFly(GameObject disk, float power)
{
disk.GetComponent<Rigidbody>().isKinematic = false;
int loc = disk.transform.position.x < 0 ? 1 : -1;
ph_fly = PhysisFlyAction.GetSSAction(loc, power);
this.RunAction(disk, ph_fly, this);
}
}
最初のコントローラー
SendDisk()
を追加しました
private void SendDisk(int type)
{
GameObject disk = disk_factory.GetDisk(type);
float ran_y = 0;
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
float power = 0;
float angle = 0;
if (type == 1)
{
ran_y = Random.Range(5f, 8f);
power = Random.Range(5f, 8f);
angle = Random.Range(15f, 20f);
}
else if (type == 2)
{
ran_y = Random.Range(3f, 6f);
power = Random.Range(10f, 13f);
angle = Random.Range(10f, 15f);
}
else
{
ran_y = Random.Range(1f, 3f);
power = Random.Range(15f, 18f);
angle = Random.Range(5f, 10f);
}
disk.transform.position = new Vector3(ran_x * 16f, ran_y, 0);
// action_manager.DiskFly(disk, angle, power);
action_manager.DiskFly(disk, power);
}
4.デモンストレーション
gif画像は以下の通りです ここでデモしている動きは物理エンジンの動きで、基本的には前回と同じです。
コードの場所
コードとドキュメントはhw6 · XiaoChen04_3/3D_Computer_Game_Programming - gitee にアップロードされました。
いくつかの情報の参照