这不是一个游戏,只是一个测试……测试一下工程是否能够正常使用。有兴趣的可以把下列核心代码插入到自己的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 & lt; 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 & lt; Camera & gt; ().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& lt; Boid & gt; (); } //初次定义检查 //添加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& lt; Boid & gt; (); collisionRisks = new List& lt; Boid & gt; (); //让this.transform成为Boid游戏对象的子对象 this.transform.parent = GameObject.Find("Boids").transform; //给Boid对象一个随机颜色(已经删去) Color randColor = Color.black; while (randColor.r + randColor.g + randColor.b & lt; 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 & lt; Boid & gt; 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 & gt; 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 & gt; 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 & gt; BoidSpawner.S.maxVelocity) { velocity = velocity.normalized * BoidSpawner.S.maxVelocity; //normalized是Vector3类的一个属性,返回单位方向向量 } if (velocity.magnitude & lt; 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 & lt; closestDist) { closestDist = dist; Closest = b; } if (dist & lt; BoidSpawner.S.nearDist) { neighbors.Add(b); } if (dist & lt; 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 & gt; someBoids) { Vector3 sum = Vector3.zero; foreach (Boid b in someBoids){ sum += b.velocity; } Vector3 avg = sum / someBoids.Count; return (avg); } }