互动媒体课-海洋模拟
1030516230 张倬。
本期互动媒体技术课要求使用手头的软件和数学工具试图模拟自然现象中的规律。
我收到了上课的演示程序的启发,也做了一个使用柏林噪音模拟海水流动的演示程序,同时用到了以前几次项目的模型,也顺便再asset store下了几个免费模型。
话不多说,直接上代码。
最主要的就是洋流的模拟。
先看模型
Ui是自己加的,为了演示流动方向。同时加了碰撞体。给触碰到的船体加对应方向的力。
首先是海洋的构建。
void generate(int x,int y)
{
var orgpos = gameObject.transform.position;
var r = block.transform.localScale.x;
for(int i = 0; i < x; i++)
{
for(int j = 0; j < y; j++)
{
float d = i % 2 == 0 ? 0 : 0.5f*1.732f * r;
Instantiate(block, new Vector3(1.5f*r*i,0,d+1.732f*r*j), Quaternion.Euler(new Vector3(0,90,0)));
}
}
}
输入行数和列数,根据对应的差值情况填充六边形方块。
对于奇数列而言初始位置不需要做调整,偶数列则需要二分之根号三半径的位移。才可以完美连接其他的方块。
如图。
void Wave()
{
float pa = Mathf.PerlinNoise(gameObject.transform.position.x*0.5f, Time.time);
float pb = Mathf.PerlinNoise(gameObject.transform.position.z*0.5f, Time.time);
float h = Mathf.PerlinNoise(pa*0.5f, pb*0.5f);
gameObject.transform.position = new Vector3(gameObject.transform.position.x, 2*h, gameObject.transform.position.z);
if (click.instance.open && Vector3.Distance(gameObject.transform.position, click.instance.pos) <= click.instance.k)
{
canvas.transform.LookAt(new Vector3(click.instance.pos.x, canvas.transform.position.y, click.instance.pos.z));
}
else
{
canvas.transform.rotation = Quaternion.Euler(new Vector3(0, (h - 0.5f) * 1800, 0));
}
}
对于水流的方向,有两方面的体现。首先使用柏林噪音计算出方块的高度。
再把该值映射到一个旋转方向上,就可以使得方块的流动与周边的方块连续有关联,而unity自带了柏林噪音是二维的,那么如何联动时间t呢,这里使用了2次柏林噪音,在横坐标与时间进行一次柏林噪音运算,纵坐标同理,得到了2个参数,再将这两个参数重新传入计算最终值,由于方块在地理位置上连续,t本身也连续,所以得到的2个参数值也是连续的,那么最终的结果也是连续的,简单地说就是连续函数相乘得到的结果函数依然连续。
由此本身的海洋部分全部做完了。之后我们来做船的部分。
void crash()
{
while (que.Count != 0)
{
var tu = que.Dequeue();
if (tu.Item1 == null || tu.Item2 == null) continue;
if (tu.Item1.large == tu.Item2.large)
{
tu.Item2.sink();
tu.Item2.sink();
continue;
}
if(tu.Item1.large > tu.Item2.large)
{
tu.Item2.sink();
}
else
{
tu.Item1.sink();
}
}
}
先做最有意思的部分,沉船。
沉船的部分不应该由船本身判定,因为相撞的相互的,导致一次相撞会被判定至少2次,很多余,而且判定结果也会先后的执行两次,很容易出现bug。所以用第三方进行判定,使用一个队列,将待判定的船对压入。如果有任意船已经不存在了就直接结束,碰撞后根据结果调用船本身的沉船函数。
void adjustv()
{
if (rb.velocity.magnitude >= boatcenter.instance.speedlimit) rb.velocity = rb.velocity * 0.95f;
}
public void sink()
{
if (rb.isKinematic == true) return;
rb.isKinematic = true;
StartCoroutine(drop());
}
private void OnTriggerEnter(Collider other)
{
if (rb.isKinematic == true) return;
var boat = other.gameObject.GetComponent<boat>();
if (boat != null)
{
boatcenter.instance.que.Enqueue(new System.Tuple<boat, boat>(this, boat));
}
}
IEnumerator drop()
{
var d = gameObject.transform.position.y;
while (gameObject.transform.position.y >= d - 10)
{
gameObject.transform.position -= down;
yield return null;
}
boatcenter.instance.lack++;
Destroy(gameObject);
}
这里是船本身所有功能,限制自身速度,碰撞的函数,沉船函数,沉船使用了协程函数缓慢下降,并屏蔽其他的沉船调用,保证每条船只沉一次。
回到总体上控制船的脚本,加入一条自动创建船的函数
void adjustv()
{
if (rb.velocity.magnitude >= boatcenter.instance.speedlimit) rb.velocity = rb.velocity * 0.95f;
}
public void sink()
{
if (rb.isKinematic == true) return;
rb.isKinematic = true;
StartCoroutine(drop());
}
private void OnTriggerEnter(Collider other)
{
if (rb.isKinematic == true) return;
var boat = other.gameObject.GetComponent<boat>();
if (boat != null)
{
boatcenter.instance.que.Enqueue(new System.Tuple<boat, boat>(this, boat));
}
}
IEnumerator drop()
{
var d = gameObject.transform.position.y;
while (gameObject.transform.position.y >= d - 10)
{
gameObject.transform.position -= down;
yield return null;
}
boatcenter.instance.lack++;
Destroy(gameObject);
}
在每条船沉下去以后给创建计数加一,如果计数大于0则会创建一条船。
接下来实现交互。
if (Input.GetMouseButton(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
{
Debug.DrawLine(ray.origin, hitInfo.point);
GameObject gameObj = hitInfo.collider.gameObject;
//当射线碰撞目标为boot类型的物品,执行拾取操作
if (gameObj.tag == "plain")
{
open = true;
pos = hitInfo.point;
Debug.Log("click object name is " + gameObj.name);
}
}
}
else
{
open = false;
}
如果鼠标点击,在点击的海面位置为中心,强制一定半径内的海面方块都指向自己,创造一个向内的引力区域。
程序运行如图。
截图中没有鼠标,观察水流的方向的话,可以看见已经向左侧聚拢,这是刚才实现的交互效果。
以上。