using UnityEngine; public class Boid : MonoBehaviour { public float speed = 0.2f; public int neighbourCount = 0; public float inertia = 0.2f; public float alignmentForce = 1.0f; public float cohesionForce = 1.0f; public float separationForce = 1.0f; public float separationDistance = 0.5f; public float bodyForce = 1; public SwarmControl sc; public Vector3 velocity = Vector3.zero; public Vector3 acceleration = Vector3.zero; private Bounds bounds; readonly Collider[] results = new Collider[10]; //public SensoryNeuroid[] neighbourSensor = new SensoryNeuroid[6]; public Perception perception; public NeuroidNetwork neuroidNet = new(); public Neuroid bodyVector; public Neuroid cohesion; public Neuroid alignment; public Neuroid separation; public Neuroid target; public Neuroid boundary; public Neuroid totalForce; public int id; void Awake() { this.id = this.GetInstanceID(); sc = FindFirstObjectByType(); bounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth); perception = new Perception(neuroidNet); //neighbourSensor = new(neuroidNet) { name = "Neighbour", id = 879 }; cohesion = new(neuroidNet) { name = "Cohesion", mode = Neuroid.Mode.Sum }; perception.SendPositions(cohesion); //cohesion.GetInputFrom(neighbourSensor); alignment = new(neuroidNet) { name = "Alignment", mode = Neuroid.Mode.Average }; //perception.SendVelocities(alignment); separation = new(neuroidNet) { name = "Separation", mode = Neuroid.Mode.Sum }; target = new(neuroidNet) { name = "Target", mode = Neuroid.Mode.Sum }; boundary = new(neuroidNet) { name = "Boundary", mode = Neuroid.Mode.Sum }; totalForce = new(neuroidNet) { name = "Total force", mode = Neuroid.Mode.Sum }; totalForce.GetInputFrom(alignment, sc.alignmentForce); totalForce.GetInputFrom(cohesion, sc.cohesionForce); totalForce.GetInputFrom(separation, sc.separationForce); totalForce.GetInputFrom(target, sc.bodyForce); totalForce.GetInputFrom(boundary, sc.boundaryForce); } void Update() { Physics.OverlapSphereNonAlloc(this.transform.position, sc.perceptionDistance, results); neighbourCount = 0; cohesion.ResetWeights(); alignment.ResetWeights(); //separation.ResetWeights(); foreach (Collider c in results) { if (c == null) continue; if (c as CapsuleCollider != null) { Boid neighbour = c.GetComponentInParent(); if (neighbour == null || neighbour == this) continue; Vector3 localPosition = neighbour.transform.position - this.transform.position; Vector3 relativeVelocity = neighbour.velocity - this.velocity; int thingId = neighbour.GetInstanceID(); perception.ProcessStimulus(thingId, localPosition); Vector3 separationForce = -localPosition / localPosition.sqrMagnitude; // which is equivalent to -(localPosition.normalized / localPosition.magnitude) separation.SetInput(thingId, separationForce, sc.separationDistance, neuroidNet); //cohesion.SetInput(thingId, localPosition, sc.cohesionForce); alignment.SetInput(thingId, relativeVelocity, sc.alignmentForce, neuroidNet); neighbourCount++; } } //Vector3 spaceLocalPosition = sc.transform.InverseTransformPoint(this.transform.position); if (!bounds.Contains(this.transform.position)) { Vector3 point = this.transform.position; // Vector3 distanceOutside = Vector3.Max(bounds.min - this.transform.position, this.transform.position - bounds.max); // // Ensure value is > 0 (but isn't this already) // Vector3 outside = distanceOutside; // Vector3.Max(Vector3.zero, distanceOutside); Vector3 below = bounds.min - point; // positive where point < min Vector3 above = point - bounds.max; // positive where point > max // outside distances per axis (0 if inside on that axis) Vector3 outside = Vector3.Max(Vector3.zero, Vector3.Max(below, above)); float magnitude = outside.magnitude; Vector3 direction = (sc.transform.position - this.transform.position).normalized; outside = direction * magnitude; boundary.SetInput(id, outside, sc.boundaryForce, neuroidNet); // Debug.Log($"boundary {this.transform.position} {outside} force = {outside * sc.boundaryForce}"); } Vector3 totalForceVector = totalForce.outputValue; //Debug.DrawRay(this.transform.position, totalForceVector, Color.magenta); this.velocity = (1 - sc.inertia) * (totalForceVector * Time.deltaTime) + sc.inertia * velocity + (sc.speed * transform.forward); //this.velocity = Vector3.ClampMagnitude(this.velocity, sc.speed); this.transform.position += this.velocity * Time.deltaTime; if (this.velocity != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(this.velocity); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 2f); // Adjust the speed of rotation } //Debug.Log($"neighbours: {neighbourCount} synapses: {cohesion.synapses.Count}"); neuroidNet.Update(); } /* Receptor GetReceptor(Neuroid perceptionNeuroid, int id) { int availableIx = -1; for (int i = 0; i < neighbourSensor.Length; i++) { if (neighbourSensor[i] == null || neighbourSensor[i].IsStale()) availableIx = i; else if (neighbourSensor[i].thingId == id) return neighbourSensor[i].receptor; } if (availableIx != -1) { if (neighbourSensor[availableIx] != null) { Debug.Log($"revived receptor {availableIx} for {id}"); neighbourSensor[availableIx].thingId = id; return neighbourSensor[availableIx].receptor; } else { Debug.Log($"new receptor for {id}"); SensoryNeuroid neuroid = new(neuroidNet, id); perceptionNeuroid.GetInputFrom(neuroid); neighbourSensor[availableIx] = neuroid; return neuroid.receptor; } } //Debug.LogWarning($"No available receptor for {id}"); return null; } /* void ProcessStimulus(int thingId, Vector3 value) { int availableIx = -1; SensoryNeuroid leastInterestingNeuroid = null; for (int i = 0; i < neighbourSensor.Length; i++) { if (neighbourSensor[i] == null || neighbourSensor[i].IsStale()) availableIx = i; else if (neighbourSensor[i].thingId == thingId) { neighbourSensor[i].receptor.SetValue(value); return; } if (neighbourSensor[i] != null) { if (leastInterestingNeuroid == null || leastInterestingNeuroid.receptor.GetValue().magnitude > neighbourSensor[i].receptor.GetValue().magnitude) leastInterestingNeuroid = neighbourSensor[i]; } } if (availableIx != -1) { if (neighbourSensor[availableIx] != null) { // Debug.Log($"revived receptor {availableIx} for {thingId}"); neighbourSensor[availableIx].thingId = thingId; neighbourSensor[availableIx].receptor.SetValue(value); } else { // Debug.Log($"new receptor for {thingId}"); SensoryNeuroid neuroid = new(neuroidNet, thingId); cohesion.GetInputFrom(neuroid); neighbourSensor[availableIx] = neuroid; neuroid.receptor.SetValue(value); } } else if (leastInterestingNeuroid != null) { //Debug.Log($"replaced receptor {leastInterestingNeuroid.thingId} for {thingId}"); leastInterestingNeuroid.thingId = thingId; leastInterestingNeuroid.receptor.SetValue(value); } //Debug.LogWarning($"No available receptor for {id}"); } */ void OnDrawGizmosSelected() { Gizmos.DrawWireSphere(transform.position, sc.perceptionDistance); } }