这不是一个游戏,只是一个测试……测试一下工程是否能够正常使用。有兴趣的可以把下列核心代码插入到自己的
核心代码中有详细注释,部分代码经过修改以适应
该工程基于
成果链接:
成果食用方式:开袋即食。小飞机会飞向光标,但如果太近了,就会远离光标。对应到移动端为触碰位置。
参考书籍
开发引擎:Unity3D
引擎版本:2019.2.17f1
编程语言:C#
核心代码:
BiodSpawner
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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()
{
for (int i = 0; i & lt; numBoids; i++)
{
Instantiate(boidPrefab);
}
}
// Update is called once per frame
void LateUpdate()
{
Vector3 mousePos2d = new Vector3(Input.mousePosition.x, Input.mousePosition.y, this.transform.position.y);
mousePos = this.GetComponent & lt; Camera & gt; ().ScreenToWorldPoint(mousePos2d);
}
}
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);
}
}
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
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Boid : MonoBehaviour
{
static public List<Boid> boids;
void Awake()
{
boids.Add(this);
Vector3 randPos = Random.insideUnitSphere * BoidSpawner.S.spawnRadius;
this.transform.position = randPos;
velocity = Random.onUnitSphere;
velocity *= BoidSpawner.S.spawnVelocity;
neighbors = new List& lt; Boid & gt; ();
collisionRisks = new List& lt; Boid & gt; ();
this.transform.parent = GameObject.Find("Boids").transform;
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()
{
List & lt; Boid & gt; neighbors = GetNeighbors(this);
newVelocity = velocity;
newPosition = this.transform.position;
//
Vector3 neighborVel = GetAverageVelocity(neighbors);
newVelocity += neighborVel * BoidSpawner.S.velocityMatchingAmt;
Vector3 neighborCenterOffset = GetAveragePosition(neighbors) - this.transform.position;
newVelocity += neighborCenterOffset * BoidSpawner.S.flockCenteringAmt;
Vector3 dist;
if (collisionRisks.Count & gt; 0)
{
Vector3 collisionAveragePos = GetAveragePosition(collisionRisks);
dist = collisionAveragePos - this.transform.position;
newVelocity += dist * BoidSpawner.S.collisionAvoidanceAmt;
}
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;
}
}
void LateUpdate()
{
velocity = (1 - BoidSpawner.S.velocityLerpAmt) * velocity + BoidSpawner.S.velocityLerpAmt * newVelocity;
if (velocity.magnitude & gt; BoidSpawner.S.maxVelocity)
{
}
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;
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);
}
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);
}
}
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);
}
}
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); } }