这不是一个游戏,只是一个测试……测试一下工程是否能够正常使用。有兴趣的可以把下列核心代码插入到自己的Unity工程中,绑定到实例预设上即可。虽然也不是什么有技术含量的代码,只是一个面向对象编程的小例子……
核心代码中有详细注释,部分代码经过修改以适应Unity3D2019.2.17f1。不保证普适。
该工程基于webGL,部分浏览器可能不支持。移动端可能会弹出性能警示窗口(选择继续即可。)
成果链接:Bios多对象模拟
成果食用方式:开袋即食。小飞机会飞向光标,但如果太近了,就会远离光标。对应到移动端为触碰位置。
参考书籍:《Introduction to GAME DESIGN,PROTOTYPING,and DEVELOPMENT》
开发引擎:Unity3D
引擎版本:2019.2.17f1
编程语言:C#
核心代码:
BiodSpawner
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoidSpawner : MonoBehaviour
{
static public BoidSpawner S;
public int numBoids = 100;
public GameObject boidPrefab;
public float spawnRadius = 100f;
public float spawnVelocity = 10f;
public float minVelocity = 0f;
public float maxVelocity = 30f;
public float nearDist = 30f;
public float collisionDist = 5f;
public float velocityMatchingAmt = 0.01f;
public float flockCenteringAmt = 0.15f;
public float collisionAvoidanceAmt = -0.5f;
public float mouseAttractionAmt = 0.01f;
public float mouseAvoidanceAmt = 0.75f;
public float mouseAvoidanceDist = 15f;
public float velocityLerpAmt = 0.25f;
public bool _________________;
public Vector3 mousePos;
// Start is called before the first frame update
void Start()
{
S = this;//设置单例变量S为BoidSpawner的当前实例
//初始化NumBoids(当前值为100)个Boids
for (int i = 0; i < numBoids; i++)
{
Instantiate(boidPrefab);
}
}
// Update is called once per frame
void LateUpdate()
{
//追踪鼠标位置,适用于所有Boid。
Vector3 mousePos2d = new Vector3(Input.mousePosition.x, Input.mousePosition.y, this.transform.position.y);
mousePos = this.GetComponent<Camera>().ScreenToWorldPoint(mousePos2d);
}
}
Biod
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Boid : MonoBehaviour
{
//该静态List变量boids用于存放所有的Boid实例,并被所有Boid实例所共享
static public List<Boid> boids;
//未使用刚体组件,由代码直接处理速度
public Vector3 velocity; //当前速度
public Vector3 newVelocity; //下一帧的速度
public Vector3 newPosition; //下一帧的位置
public List<Boid> neighbors; //附近的所有Boid
public List<Boid> collisionRisks; //距离过近的所有Boid
public Boid Closest; //最近的Boid
void Awake()
{
if (boids == null) { boids = new List<Boid>(); } //初次定义检查
//添加Boid实例
boids.Add(this);
//为当前实例提供初始的随机位置和速度
Vector3 randPos = Random.insideUnitSphere * BoidSpawner.S.spawnRadius;
randPos.y = 0;//限制在xz平面上
this.transform.position = randPos;
velocity = Random.onUnitSphere;
velocity *= BoidSpawner.S.spawnVelocity;
//初始化两个List
neighbors = new List<Boid>();
collisionRisks = new List<Boid>();
//让this.transform成为Boid游戏对象的子对象
this.transform.parent = GameObject.Find("Boids").transform;
//给Boid对象一个随机颜色(已经删去)
Color randColor = Color.black;
while (randColor.r + randColor.g + randColor.b < 1.0f)
{
randColor = new Color(Random.value, Random.value, Random.value);
}
//this.GetComponent<SkinnedMeshRenderer>().material.color = randColor;
}
// Update is called once per frame
void Update()
{
//获取附近所有的Boids(当前Boid的临近Boid对象)
List<Boid> neighbors = GetNeighbors(this);
//初始化
newVelocity = velocity;
newPosition = this.transform.position;
//速度匹配:
//
Vector3 neighborVel = GetAverageVelocity(neighbors);
newVelocity += neighborVel * BoidSpawner.S.velocityMatchingAmt;
//凝聚向心性:向邻近Boid的中心移动
Vector3 neighborCenterOffset = GetAveragePosition(neighbors) - this.transform.position;
newVelocity += neighborCenterOffset * BoidSpawner.S.flockCenteringAmt;
//排斥性:避免碰到邻近的Boid
Vector3 dist;
if(collisionRisks.Count>0)
{
Vector3 collisionAveragePos = GetAveragePosition(collisionRisks);
dist = collisionAveragePos - this.transform.position;
newVelocity += dist * BoidSpawner.S.collisionAvoidanceAmt;
}
//跟随鼠标光标:所有Boid都向鼠标光标移动
dist = BoidSpawner.S.mousePos - this.transform.position;
if (dist.magnitude > BoidSpawner.S.mouseAvoidanceDist)
{
newVelocity += dist * BoidSpawner.S.mouseAttractionAmt;
}
else
{
//排斥(快速):距离光标过近
newVelocity -= dist.normalized * BoidSpawner.S.mouseAvoidanceDist * BoidSpawner.S.mouseAvoidanceAmt;
}
//等待所有Boid实例的Update()完成后。(即均已获得了newPosition和newVelocity)
//在所有Boid实例的LateUpdata()中应用
//避免竞态条件的发生。
}
void LateUpdate()
{
//使用线性插值法计算出的新速度替换当前速度。(使元素的移动更加平滑)
velocity = (1 - BoidSpawner.S.velocityLerpAmt) * velocity + BoidSpawner.S.velocityLerpAmt * newVelocity;
//说实话,我越看这个越觉得像我之前在小车PID控制中用到的一阶低通滤波函数——不过想想也是,毕竟趋向相同,都是为了“平滑”。
//防止超限
//有没有发现,这个又和之前在PID控制中用的防止矫正速度超限有相似之处
if(velocity.magnitude > BoidSpawner.S.maxVelocity)
{
velocity = velocity.normalized * BoidSpawner.S.maxVelocity; //normalized是Vector3类的一个属性,返回单位方向向量
}
if(velocity.magnitude < BoidSpawner.S.minVelocity)
{
velocity = velocity.normalized * BoidSpawner.S.minVelocity;
}
//确定新位置
newPosition = this.transform.position + velocity * Time.deltaTime;
this.transform.LookAt(newPosition);
this.transform.position = newPosition;
}
//查找邻近对象
public List<Boid> GetNeighbors(Boid boi)
{
float closestDist = float.MaxValue;
Vector3 delta;
float dist;
neighbors.Clear();
collisionRisks.Clear();
foreach (Boid b in boids)
{
if (b == boi) continue;
delta = b.transform.position - boi.transform.position; //向量差
dist = delta.magnitude; //求模长
if (dist < closestDist)
{
closestDist = dist;
Closest = b;
}
if (dist < BoidSpawner.S.nearDist)
{
neighbors.Add(b);
}
if(dist < BoidSpawner.S.collisionDist)
{
collisionRisks.Add(b);
}
}
if (neighbors.Count == 0)
{
neighbors.Add(Closest);
}
return (neighbors);
}
//获取一个List<Boid>当中所有Boid的平均位置
public Vector3 GetAveragePosition(List<Boid> someBoids)
{
Vector3 sum = Vector3.zero;
foreach(Boid b in someBoids)
{
sum += b.transform.position;
}
Vector3 center = sum / someBoids.Count;
return (center);
}
//获取平均速度
public Vector3 GetAverageVelocity(List<Boid> someBoids)
{
Vector3 sum = Vector3.zero;
foreach (Boid b in someBoids)
{
sum += b.velocity;
}
Vector3 avg = sum / someBoids.Count;
return (avg);
}
}