🐱 算神的小窝 🤓

使用CUnity实现Boids算法.md


CreationTime:8/13/2025 2:35:07 PM LastAccessTime:8/21/2025 3:44:03 PM


使用C#+Unity实现Boids算法

什么是Boids算法

Boids 算法是一种模拟群体行为(如鸟群、鱼群)的算法,由Craig Reynolds在1986年提出。它通过三个简单的规则实现了复杂的群体行为:分离(避免碰撞)、对齐(匹配方向)和凝聚(向群体中心靠拢)。

Unity 提供了强大的渲染和物理引擎,非常适合实现这种群体行为模拟。

Unity 设置指南

安装Unity Hub

  1. 打开Visual Studio Installer
  2. 点击修改
  3. 勾选使用 Unity 的游戏开发并勾选右边的Unity Hub
  4. 点击修改按钮开始安装

安装Unity Editer

  1. 打开Unity Hub
  2. 点击Installs
  3. 点击右上的Install Editer按钮,安装LTS版本的编辑器

Unity本地化

Unity Hub本地化

  1. 点击左侧边栏的齿轮
  2. 点击Appearance
  3. 点击Language选择简体中文

Unity Editer本地化

  1. 在Unity Hub中点击已安装编辑器右边的齿轮
  2. 点击添加模块
  3. 勾选简体中文,点击安装
  4. 打开 Unity 编辑器。
  5. 在菜单栏中选择 “Edit(编辑)” > “Preferences(偏好设置)”
  6. 在弹出的窗口中,点击 “General(常规)” 选项卡。
  7. 找到 “Language(语言)” 下拉菜单,选择 “Chinese(中文)”
  8. 关闭窗口并重启 Unity 编辑器以应用更改。

正篇

  1. 创建新项目

    • 打开 Unity Hub,创建一个新的 2D 项目
    • 命名为 "BoidsSimulation"
  2. 设置场景

    • 在场景中创建一个空对象,命名为 "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;
              }
          }
      }
      
  3. 创建 Boid 预制体

    • 在场景中创建一个 2D 精灵(Sprite)
    • 创建一个三角形精灵(可以使用 Unity 的 "Create > Sprites > Triangle")
    • 调整三角形大小为 (0.3, 0.3)
    • 将这个三角形拖到项目窗口中创建为预制体
    • BoidsManagerBoid Prefab 字段中分配这个预制体
  4. 配置参数

    • BoidsManager 的 Inspector 中调整参数:
      • Boid Count: 群体数量(默认 150,推荐 10)
      • Max Speed: 最大速度(默认 4)
      • Perception Radius: 感知范围(默认 5,推荐 3)
      • Separation Radius: 分离范围(默认 3,推荐 1)
      • 权重参数可以根据需要调整

Boids 算法核心原理

  1. 分离 (Separation)

    if (distance > 0 && distance < simulation.separationRadius)
    {
        Vector2 repelForce = -toOther.normalized / distance;
        separation += repelForce;
        separationCount++;
    }
    
    • 避免与邻近个体碰撞
    • 距离越近,排斥力越大
  2. 对齐 (Alignment)

    alignment += other.velocity;
    alignmentCount++;
    
    • 计算邻近个体的平均速度方向
    • 调整自身速度向平均方向靠拢
  3. 凝聚 (Cohesion)

    cohesion += (Vector2)other.transform.position;
    cohesionCount++;
    
    • 计算邻近个体的平均位置
    • 向该位置移动,保持群体聚集

Github开源

完整的代码我放在这里,欢迎Star

https://github.com/lishewen/BoidsSimulation

An unhandled error has occurred. Reload 🗙