使用C#+Unity实现Boids算法
什么是Boids算法
Boids 算法是一种模拟群体行为(如鸟群、鱼群)的算法,由Craig Reynolds在1986年提出。它通过三个简单的规则实现了复杂的群体行为:分离(避免碰撞)、对齐(匹配方向)和凝聚(向群体中心靠拢)。
Unity 提供了强大的渲染和物理引擎,非常适合实现这种群体行为模拟。
Unity 设置指南
安装Unity Hub
- 打开
Visual Studio Installer
- 点击
修改
- 勾选
使用 Unity 的游戏开发
并勾选右边的Unity Hub
- 点击
修改
按钮开始安装
安装Unity Editer
- 打开
Unity Hub
- 点击
Installs
- 点击右上的
Install Editer
按钮,安装LTS
版本的编辑器
Unity本地化
Unity Hub本地化
- 点击左侧边栏的齿轮
- 点击
Appearance
- 点击
Language
选择简体中文
Unity Editer本地化
- 在Unity Hub中点击已安装编辑器右边的齿轮
- 点击
添加模块
- 勾选
简体中文
,点击安装
- 打开 Unity 编辑器。
- 在菜单栏中选择 “Edit(编辑)” > “Preferences(偏好设置)”。
- 在弹出的窗口中,点击 “General(常规)” 选项卡。
- 找到 “Language(语言)” 下拉菜单,选择 “Chinese(中文)”。
- 关闭窗口并重启 Unity 编辑器以应用更改。
正篇
创建新项目:
- 打开 Unity Hub,创建一个新的 2D 项目
- 命名为 "BoidsSimulation"
设置场景:
在场景中创建一个空对象,命名为 "BoidsManager"
点击
添加组件
选择
New Script
新建一个C#脚本,命名为BoidsSimulation
双击
BoidsSimulation
打开VS编辑内容如下:using UnityEngine; using System.Collections.Generic; public class BoidsSimulation : MonoBehaviour { [Header("群体设置")] public int boidCount = 150; public GameObject boidPrefab; public float spawnRadius = 10f; [Header("行为参数")] public float maxSpeed = 4f; public float maxForce = 0.2f; public float perceptionRadius = 5f; public float separationRadius = 3f; public float separationWeight = 1.5f; public float alignmentWeight = 1.0f; public float cohesionWeight = 1.0f; public float boundaryPadding = 1f; [Header("显示设置")] public bool showPerceptionRange = true; public bool showInstructions = true; public Color boidColor = new Color(0, 0.8f, 1f); public Color highlightColor = new Color(1f, 0.4f, 0.4f); public Color perceptionColor = new Color(0.12f, 0.32f, 0.48f, 0.3f); public Color separationColor = new Color(0.8f, 0.2f, 0.2f, 0.3f); // 修复:将 mainCamera 设为公共属性 public Camera MainCamera { get; private set; } private List<Boid> boids = new List<Boid>(); private GUIStyle guiStyle; private Rect instructionsRect = new Rect(20, 70, 400, 150); private Rect rangeInfoRect = new Rect(20, 220, 400, 80); void Start() { // 修复:正确初始化 MainCamera MainCamera = Camera.main; // 创建GUI样式 guiStyle = new GUIStyle(); guiStyle.normal.textColor = new Color(0.8f, 0.9f, 1f); guiStyle.fontSize = 16; // 生成Boids SpawnBoids(); } void SpawnBoids() { // 清除现有的Boids foreach (var boid in boids) { if (boid != null && boid.gameObject != null) Destroy(boid.gameObject); } boids.Clear(); // 创建新的Boids for (int i = 0; i < boidCount; i++) { Vector2 spawnPos = (Vector2)transform.position + Random.insideUnitCircle * spawnRadius; GameObject newBoid = Instantiate(boidPrefab, spawnPos, Quaternion.identity); Boid boidComponent = newBoid.GetComponent<Boid>(); if (boidComponent == null) boidComponent = newBoid.AddComponent<Boid>(); boidComponent.Initialize(this, i == 0); // 第一个Boid高亮显示 boids.Add(boidComponent); } } void Update() { // 处理输入 HandleInput(); // 更新所有Boids foreach (var boid in boids) { if (boid != null) boid.UpdateBoid(boids); } } void HandleInput() { // 重置模拟 if (Input.GetKeyDown(KeyCode.C)) { SpawnBoids(); } // 切换说明显示 if (Input.GetKeyDown(KeyCode.I)) { showInstructions = !showInstructions; } // 添加新的Boids if (Input.GetMouseButtonDown(0)) { Vector2 mousePos = MainCamera.ScreenToWorldPoint(Input.mousePosition); for (int i = 0; i < 5; i++) { Vector2 spawnPos = mousePos + Random.insideUnitCircle * 1f; GameObject newBoid = Instantiate(boidPrefab, spawnPos, Quaternion.identity); Boid boidComponent = newBoid.GetComponent<Boid>(); if (boidComponent == null) boidComponent = newBoid.AddComponent<Boid>(); boidComponent.Initialize(this, false); boids.Add(boidComponent); } } // 切换说明显示 if (Input.GetKeyDown(KeyCode.Q)) { Application.Quit(); } } void OnGUI() { // 绘制标题 GUI.Label(new Rect(Screen.width / 2 - 150, 15, 300, 40), "Boids 算法模拟 - 群体行为", guiStyle); // 绘制信息 string info = $"Boids数量: {boids.Count} | 按 'I' 切换说明 | 按 'C' 重置 | 按 'Q' 退出 | 点击添加更多Boids"; GUI.Label(new Rect(Screen.width / 2 - 250, Screen.height - 30, 500, 30), info, guiStyle); // 绘制规则说明 if (showInstructions) { GUI.Box(instructionsRect, ""); GUI.Label(new Rect(instructionsRect.x + 20, instructionsRect.y + 20, 300, 30), "Boids算法规则:", guiStyle); GUI.Label(new Rect(instructionsRect.x + 20, instructionsRect.y + 50, 300, 30), "1. 分离 (Separation): 避免与邻近个体碰撞", guiStyle); GUI.Label(new Rect(instructionsRect.x + 20, instructionsRect.y + 80, 300, 30), "2. 对齐 (Alignment): 与邻近个体的平均方向保持一致", guiStyle); GUI.Label(new Rect(instructionsRect.x + 20, instructionsRect.y + 110, 300, 30), "3. 凝聚 (Cohesion): 向邻近个体的平均位置移动", guiStyle); } } } public class Boid : MonoBehaviour { private BoidsSimulation simulation; private bool isHighlighted; private Vector2 velocity; private Vector2 acceleration; public void Initialize(BoidsSimulation sim, bool highlight) { simulation = sim; isHighlighted = highlight; // 随机初始速度 velocity = Random.insideUnitCircle.normalized * Random.Range(simulation.maxSpeed * 0.5f, simulation.maxSpeed); // 设置颜色 UpdateColor(); } public void UpdateBoid(List<Boid> allBoids) { // 应用三个行为规则 ApplyRules(allBoids); // 更新位置 UpdatePosition(); // 边界处理 HandleBoundaries(); // 更新朝向 UpdateRotation(); } void ApplyRules(List<Boid> allBoids) { Vector2 separation = Vector2.zero; Vector2 alignment = Vector2.zero; Vector2 cohesion = Vector2.zero; int separationCount = 0; int alignmentCount = 0; int cohesionCount = 0; foreach (var other in allBoids) { if (other == this) continue; Vector2 toOther = (Vector2)other.transform.position - (Vector2)transform.position; float distance = toOther.magnitude; // 分离规则 if (distance > 0 && distance < simulation.separationRadius) { Vector2 repelForce = -toOther.normalized / distance; separation += repelForce; separationCount++; } // 对齐和凝聚规则 if (distance > 0 && distance < simulation.perceptionRadius) { // 对齐 alignment += other.velocity; alignmentCount++; // 凝聚 cohesion += (Vector2)other.transform.position; cohesionCount++; } } // 计算分离力 if (separationCount > 0) { separation /= separationCount; separation = separation.normalized * simulation.maxSpeed; separation -= velocity; separation = Vector2.ClampMagnitude(separation, simulation.maxForce); } // 计算对齐力 if (alignmentCount > 0) { alignment /= alignmentCount; alignment = alignment.normalized * simulation.maxSpeed; alignment -= velocity; alignment = Vector2.ClampMagnitude(alignment, simulation.maxForce); } // 计算凝聚力 if (cohesionCount > 0) { cohesion /= cohesionCount; Vector2 toCenter = cohesion - (Vector2)transform.position; toCenter = toCenter.normalized * simulation.maxSpeed; cohesion = toCenter - velocity; cohesion = Vector2.ClampMagnitude(cohesion, simulation.maxForce); } // 应用加权力 acceleration = Vector2.zero; acceleration += separation * simulation.separationWeight; acceleration += alignment * simulation.alignmentWeight; acceleration += cohesion * simulation.cohesionWeight; } void UpdatePosition() { // 更新速度 velocity += acceleration; velocity = Vector2.ClampMagnitude(velocity, simulation.maxSpeed); // 更新位置 transform.position += (Vector3)velocity * Time.deltaTime; } void HandleBoundaries() { Vector2 pos = transform.position; // 修复:使用 simulation.MainCamera 而不是 simulation.mainCamera if (simulation.MainCamera == null) return; Vector3 screenPoint = simulation.MainCamera.WorldToViewportPoint(pos); bool onScreen = screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1; if (onScreen) return; // 环绕屏幕边界 if (screenPoint.x < 0) screenPoint.x = 1; else if (screenPoint.x > 1) screenPoint.x = 0; if (screenPoint.y < 0) screenPoint.y = 1; else if (screenPoint.y > 1) screenPoint.y = 0; pos = simulation.MainCamera.ViewportToWorldPoint(screenPoint); transform.position = pos; } void UpdateRotation() { // 根据速度方向旋转 if (velocity != Vector2.zero) { float angle = Mathf.Atan2(velocity.y, velocity.x) * Mathf.Rad2Deg; transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward); } } void UpdateColor() { SpriteRenderer renderer = GetComponent<SpriteRenderer>(); if (renderer != null) { renderer.color = isHighlighted ? simulation.highlightColor : simulation.boidColor; } } }
创建 Boid 预制体:
- 在场景中创建一个 2D 精灵(Sprite)
- 创建一个三角形精灵(可以使用 Unity 的 "Create > Sprites > Triangle")
- 调整三角形大小为 (0.3, 0.3)
- 将这个三角形拖到项目窗口中创建为预制体
- 在
BoidsManager
的Boid Prefab
字段中分配这个预制体
配置参数:
- 在
BoidsManager
的 Inspector 中调整参数:Boid Count
: 群体数量(默认 150,推荐 10)Max Speed
: 最大速度(默认 4)Perception Radius
: 感知范围(默认 5,推荐 3)Separation Radius
: 分离范围(默认 3,推荐 1)- 权重参数可以根据需要调整
- 在
Boids 算法核心原理
分离 (Separation):
if (distance > 0 && distance < simulation.separationRadius) { Vector2 repelForce = -toOther.normalized / distance; separation += repelForce; separationCount++; }
- 避免与邻近个体碰撞
- 距离越近,排斥力越大
对齐 (Alignment):
alignment += other.velocity; alignmentCount++;
- 计算邻近个体的平均速度方向
- 调整自身速度向平均方向靠拢
凝聚 (Cohesion):
cohesion += (Vector2)other.transform.position; cohesionCount++;
- 计算邻近个体的平均位置
- 向该位置移动,保持群体聚集
Github开源
完整的代码我放在这里,欢迎Star