接上文, 不多贅述, 直接繼續。
之前是實現了繞轉功能。 有Bug的話自行理解解決, 沒有Bug, 只是效果有問題的, 自行調整參數, 根據高中物理。(就比如一來直接就飛走了Force就調小點啊什麼的...)
其實基本功能已經實現, 這篇主要是談談參數設計問題。
先放一張之後有比較大作用的圖。
我們先從之前被我們直接忽略的那幾個函數開始。
1. 萬有引力的計算。
萬有引力公式不貼了。 這個是比較好確定的一個函數。 因爲理論給出了具體計算方法, 不需要分析實際數據。
代碼如下:
float UniversalG(GameObject obj1, GameObject obj2) {
var pos1 = obj1.transform.position;
var pos2 = obj2.transform.position;
Rigidbody rb1 = obj1.GetComponent<Rigidbody>();
Rigidbody rb2 = obj2.GetComponent<Rigidbody>();
return rb1.mass * rb2.mass / Distance(pos1, pos2);
}
float Distance(Vector3 pos1, Vector3 pos2) {
Vector3 diff = (pos1 - pos2);
float dist = Mathf.Sqrt (diff.x * diff.x + diff.y * diff.y + diff.z * diff.z);
if (dist < 1)
return 1;
else return (dist);
}
在這裏, 萬有引力常數我直接忽略了。 這是因爲並不是實際天體, 我們若是要控制一個常數, 只需增減其Mass即可。 不過這裏我也感覺加一個常數會更好, 因爲之後還是要用到一個類似於G的量。 ~。~ 看起來是我當時寫的時候考慮欠周了。
Dist控制最小值不解釋。 防Bug粗暴方法, 當然確信自己不會出現Bug 的可以不加。。。
剩下兩個函數, ProperDirect, ProperForce, 都不好弄啊。 我試着說說我的看法吧, 個人感覺只是一個湊合用的解決方案。。。
先看ProperDirect:
Vector3 ProperDirect(Vector3 pos) {
float x = Random.value , y = Random.value / 10, z = Random.value;
float Norm = Mathf.Sqrt (y * y + z * z);
if (pos.y > 0)
y = -y;
if (pos.x > 0) return new Vector3 (0 , y / Norm, z / Norm);
else return new Vector3 (0 , y / Norm, -z / Norm);
}
一句句解釋吧....
y = Random.value / 10
在Unity中,y是豎直軸。 將豎直軸參數變小, 意圖是將它的軌道“扁平”一些, 先看太陽系中的數據:
事實上, 我也是模擬過不控制其傾斜角的情況, 發現這樣對於一個星系來說太不穩定。
float Norm = Mathf.Sqrt (y * y + z * z);
歸一化操作。 數據分析做過的話應該很熟悉, 這樣就不會因爲某個參數的值造成結果的偏移很大, 更多的是看其比值。
if (pos.y > 0)
y = -y;
這個實際上也是一個維持傾斜角的問題。 通俗的解釋就是, 若開始隨機出現的位置在y軸上方, 那我就讓初始力方向(也就是初始速度方向) 有一個向y軸負方向的分量。
if (pos.x > 0) return new Vector3 (0 , y / Norm, z / Norm);
else return new Vector3 (0 , y / Norm, -z / Norm);
除Norm解釋過了。 而根據x的正負確定z 的正負是一個比較有趣的事情。 不妨在紙上畫一下座標軸, 然後畫出兩種情況的受力, 就能發現: 其實這是讓星球的公轉方向相同的。 儘管在實際星系中會有公轉方向不同的行星, 但不可否認, 自轉方向相同絕對要穩定一些。 換言之, 可以使你更快得到穩定的星系。
下一個函數, ProperForce:
float ProperForce(GameObject Obj) {
var pos = Obj.transform.position;
var rb = Obj.GetComponent<Rigidbody> ();
return G * Mathf.Sqrt(rb.mass / Distance (pos, Vector3.zero));
}
float ProperForce(GameObject Obj) {
var pos = Obj.transform.position;
var rb = Obj.GetComponent<Rigidbody> ();
var Rb = GameObject.Find ("Sphere").GetComponent<Rigidbody> ();
return G * rb.mass * Mathf.Sqrt(Rb.mass / Distance (pos, Vector3.zero));
}
這樣纔是對的。
懶得放公式推導過程了, 就是用動量定理和向心力與萬有引力平衡來推。 注意的是AddForce在這裏不是指力, 而應該是一個動量, 因爲這裏隱含了時間T。
其實應該是F * Time.FixedDeltaTime, 注意這個函數是在start裏面被執行, 所以在DeltaTime和FixedDeltaTIme之間還要再轉化一下。
恩基本參數弄完了, 下面來做一點修飾工作。 讓整個運動過程美觀一點。
首先是給行星加上Color與軌跡, 當然軌跡的顏色和它本身顏色一樣最好了, 便於識別。
軌跡添加用TrailRender組件, 在裏面可以設置相應的顏色。
代碼的話, 就是如何創建一個美觀的行星了。 這部分沒有什麼解釋的, 直接上代碼吧。
void CreateRandomPlanet() {
GameObject Star = Resources.Load ("Prefabs/Star") as GameObject;
GameObject T = Instantiate (Star);
T.transform.SetParent (GameObject.Find("Galaxy").transform);
T.transform.position= ProperArea(GalaxyRange / 20, GalaxyRange / 3);
float Scale = 4 + Random.value * 20;
T.transform.localScale = new Vector3(Scale, Scale, Scale);
T.GetComponent<Rigidbody> ().mass = 5 + 100 * Random.value;
var TColor = new Color(Random.value, Random.value, Random.value);
T.GetComponent<MeshRenderer> ().material.color = TColor;
T.GetComponent<TrailRenderer> ().material.color = TColor;
}
裏面的API倒是挺多, 不過大概看名字能猜出是幹什麼用的...
這部分設置決定了星系整體的畫面, 每個人按照喜好自己設吧(例如喜歡亮色的就把RGB中R的權值調高點啊什麼的) 就是注意mass 和 position, 自己調整成合適(即大多數 星球能正常繞行)即可。
做完了這些, 星系已經可以建立了。 在中心天體周圍, 自己手動添加或是向上面代碼那樣添加繞行行星, 調整各個參數, 運行即可。
但是,爲了 “稍微”更加接近真實情況, 我們還必須設定一些約束條件。 我採用的是如下約束條件:
1.行星飛出引力場範圍外, 自動毀滅。
2. 2顆星球對撞, 若a.mass > 10 * b.mass, 則只有b毀滅, 否則a, b 均毀滅。
3. 其他。
這裏, 1,2 是約束的基礎, 也是對客觀事實的描述。 事實上只有約束條件1,2即可。 然而我們加上這個約束代碼之後, 將會發現事情並不是那麼簡單。 我們將很難得到一個 穩定存在的 星系。而3條件其實就是爲了使我們更易得到穩定星系所需要的約束性更強的條件。
如果前面工作都做好, 那麼這裏的代碼簡單是相當簡單的:
void OnTriggerEnter(Collider other) {
if (10 * other.gameObject.GetComponent<Rigidbody> ().mass < this.GetComponent<Rigidbody> ().mass)
Destroy (other.gameObject);
}
bool FarAway() {
return (Distance (this.transform.position, Vector3.zero) > GalaxyRange);
}
如果前面代碼都按照我前面的變量名寫, 我甚至懷疑這裏會寫的和我一模一樣。。。
好了, 現在再去試試, 除非你運氣特別好, 或者你真的按高中物理算出了比例縮小下的各個參數值! 否則一般都是得不到正常星系的, 別隻看一兩分鐘, 你運行半小時再看看你的行星還剩多少。。。
我們可以用不斷產生行星的方法, 以剛剛說的1,2條件爲篩選, 總能篩到合適的。 這是我構建穩定星系想法的基礎。
首先, 在MainCamara下, 新建一個C#腳本,然後將剛纔寫好的創建對象的函數放進去。這就是這個腳本的主要目的, 我們再通過寫一些小函數, 來規範我們創建行星的規則。
這次我直接貼整個文件代碼了:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreateStar : MonoBehaviour {
private float GalaxyRange;
private float Timer = 2.0f;
// Use this for initialization
void Start () {
GalaxyRange = GameObject.Find ("Sphere").transform.localScale.x * 20;
}
// Update is called once per frame
void Update () {
if (Timer <= 0) {
if (StarLessthan(10)) CreateRandomPlanet ();
Timer = 0.3f;
} else
Timer -= Time.deltaTime;
}
void CreateRandomPlanet() {
GameObject Star = Resources.Load ("Prefabs/Star") as GameObject;
GameObject T = Instantiate (Star);
T.transform.SetParent (GameObject.Find("Galaxy").transform);
T.transform.position= ProperArea(GalaxyRange / 20, GalaxyRange / 3);
float Scale = 4 + Random.value * 20;
T.transform.localScale = new Vector3(Scale, Scale, Scale);
T.GetComponent ().mass = 5 + 100 * Random.value;
var TColor = new Color(Random.value, Random.value, Random.value);
T.GetComponent ().material.color = TColor;
T.GetComponent ().material.color = TColor;
}
Vector3 ProperArea(float min, float max) {
float x = (max - min) * Random.value + min;
float y = (max - min) * Random.value + min;
float z = (max - min) * Random.value + min;
return new Vector3 (x * PosiOrNega(), y * PosiOrNega() / 10, z * PosiOrNega() / 10);
}
int PosiOrNega() {
return Random.Range (1, 3) * 2 - 3;
}
bool StarLessthan(int x) {
Rigidbody[] T = GameObject.FindObjectsOfType ();
return (T.Length < x + 1);
}
}
這樣的確達到了約束條件, 並且如果會耍一些小聰明, 運氣好的話得到2~3 顆行星的穩定星系, 應該花個20min也差不多。
---------------------???的分割線------------------------------------
再看整個過程, 發現篩選的大部分時間花在等待, 等待的原因是因爲這個約束條件具有較強的不可知性。 必須等到觸發條件時才知道其違反了該條件。
所以下一篇我想談談剛纔提到的第三個約束條件: “其他”, 要怎樣設立。
這裏補上之前沒有傳的Gif, 其實效果不是很好看, 第一是我沒怎麼用心設計外觀, 第二是其實在Unity中變換角度看還是蠻不錯的~。~
最後, 歡迎讀者對我的項目提出建議或意見, 尤其是後一篇, 自己主觀的因素略多, 歡迎一同交流討論。