Unity学习笔记(3)-----制作一个仿真星系(复杂版)【Step2】

接上文, 不多赘述, 直接继续。

之前是实现了绕转功能。 有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);
	}
}


Name 为Sphere的是我的中心天体,其他应该都挺好懂, 还有大量自由的设置内容, 就不提了。

现在可以做到的是, 始终维持星系中天体数量为某个值, 即使有行星被毁灭, 依旧可以补上。

这样的确达到了约束条件, 并且如果会耍一些小聪明, 运气好的话得到2~3 颗行星的稳定星系, 应该花个20min也差不多。

---------------------???的分割线------------------------------------

再看整个过程, 发现筛选的大部分时间花在等待, 等待的原因是因为这个约束条件具有较强的不可知性。 必须等到触发条件时才知道其违反了该条件。

所以下一篇我想谈谈刚才提到的第三个约束条件: “其他”, 要怎样设立。



这里补上之前没有传的Gif, 其实效果不是很好看, 第一是我没怎么用心设计外观, 第二是其实在Unity中变换角度看还是蛮不错的~。~


最后, 欢迎读者对我的项目提出建议或意见, 尤其是后一篇, 自己主观的因素略多, 欢迎一同交流讨论。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章