Skip to content

Instantly share code, notes, and snippets.

@mminer
Created November 4, 2024 01:35
Show Gist options
  • Save mminer/227ce5f4eaaf70d382e6823118ae031f to your computer and use it in GitHub Desktop.
Save mminer/227ce5f4eaaf70d382e6823118ae031f to your computer and use it in GitHub Desktop.
2D flocking implementation for Unity.
using UnityEngine;
public partial class Flock : MonoBehaviour
{
[SerializeField, Min(0)] float spawnRadius = 5f;
[SerializeField, Min(1)] int unitCount = 50;
[SerializeField] Transform unitPrefab;
[Header("Unit")]
[SerializeField, Range(0, 360)] float fieldOfView = 270f;
[SerializeField, Min(0)] float separationRadius = 1f;
[SerializeField, Min(0)] float sightRadius = 3f;
[SerializeField, Min(0)] float speed = 1f;
[SerializeField, Min(0)] float steeringForce = 1f;
Transform[] units;
void Start()
{
InstantiateUnits();
}
void Update()
{
foreach (var unit in units)
{
UpdateUnit(unit);
}
}
void InstantiateUnits()
{
units = new Transform[unitCount];
for (var i = 0; i < unitCount; i++)
{
var position = Random.insideUnitCircle * spawnRadius;
var rotation = Quaternion.Euler(0, 0, Random.Range(0, 360));
units[i] = Instantiate(unitPrefab, position, rotation, transform);
}
}
void UpdateUnit(Transform unit)
{
var unitPosition = (Vector2)unit.position;
var unitDirection = (Vector2)unit.up;
var neighborCount = 0;
var averageNeighborPosition = Vector2.zero;
var averageNeighborDirection = Vector2.zero;
var separation = Vector2.zero;
foreach (var otherUnit in units)
{
if (otherUnit == unit)
{
continue;
}
var otherUnitPosition = (Vector2)otherUnit.position;
var otherUnitDirection = (Vector2)otherUnit.up;
var directionToOther = otherUnitPosition - unitPosition;
var sqrDistanceToOther = directionToOther.sqrMagnitude;
var isWithinSightRadius = sqrDistanceToOther <= sightRadius * sightRadius;
if (!isWithinSightRadius)
{
continue;
}
var angleToOther = Vector2.Angle(unitDirection, directionToOther);
var isWithinFieldOfView = angleToOther <= fieldOfView / 2;
if (!isWithinFieldOfView)
{
continue;
}
neighborCount++;
averageNeighborPosition += otherUnitPosition;
averageNeighborDirection += otherUnitDirection;
var isWithinSeparationRadius = sqrDistanceToOther <= separationRadius * separationRadius;
if (isWithinSeparationRadius)
{
separation -= directionToOther * separationRadius / sqrDistanceToOther;
}
Debug.DrawLine(unitPosition, otherUnitPosition, Color.green);
}
if (neighborCount > 0)
{
averageNeighborPosition /= neighborCount;
averageNeighborDirection /= neighborCount;
var cohesion = (averageNeighborPosition - unitPosition).normalized;
var alignment = averageNeighborDirection.normalized;
unit.up = Vector3.Lerp(unit.up, cohesion + alignment + separation, steeringForce * Time.deltaTime);
}
unit.position += unit.up * (speed * Time.deltaTime);
}
}
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
public partial class Flock
{
void OnDrawGizmos()
{
if (units == null)
{
return;
}
Handles.color = Color.red;
foreach (var unit in units)
{
Handles.DrawLine(unit.position, unit.position + (unit.up * sightRadius));
var arcStartDirection = Quaternion.Euler(0, 0, -fieldOfView / 2) * unit.up;
var arcEndDirection = Quaternion.Euler(0, 0, fieldOfView / 2) * unit.up;
Handles.DrawWireArc(unit.position, Vector3.forward, arcStartDirection, fieldOfView, separationRadius);
Handles.DrawWireArc(unit.position, Vector3.forward, arcStartDirection, fieldOfView, sightRadius);
var arcStartPosition = unit.position + (arcStartDirection * sightRadius);
var arcEndPosition = unit.position + (arcEndDirection * sightRadius);
Handles.DrawLine(unit.position, arcStartPosition);
Handles.DrawLine(unit.position, arcEndPosition);
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment