using System.Collections; using System.Collections.Generic; using UnityEngine; using NanoBrain; namespace CreatureControl { /// /// Simulated ant using a NanoBrain /// [RequireComponent(typeof(Brain))] public class Ant : Insect { /// /// inertia controls how quickly the ant can change it direction /// private readonly float inertia = 0.2f; /// /// The maximum distance at which the ant can smell things /// /// The strength of the smell decreases with the inverse square law /// to zero at this distance private readonly float smellRadius = 0.2f; /// /// The angle to the left and right within the ant can smell things /// private readonly float smellAngle = 80.0f; public GameObject homePheromonePrefab; public GameObject foodPheromonePrefab; public AntennaTouch touchLeft; public AntennaTouch touchRight; public Brain nanoBrain; // brain input /// /// The (heart) beat for the brain /// /// It is used by the brain to do things periodically /// like placing pheromones public Neuron beat; public Neuron foodReceptor; public Neuron homeReceptor; public Cluster foodReceptors; public Cluster homeReceptors; // brain output public Neuron targetDirection; public Neuron hasFood; public Vector3 linearVelocity; // = Vector3.forward; public Vector3 angularVelocity; #region Init protected override void Awake() { base.Awake(); this.nanoBrain = GetComponentInChildren(); Cluster brain = this.nanoBrain.brain; if (brain != null) { //--- brain inputs this.beat = brain.GetNeuron("Beat"); if (touchLeft != null) touchLeft.receptor = brain.GetNucleus("Hit Left") as Neuron; if (touchRight != null) touchRight.receptor = brain.GetNucleus("Hit Right") as Neuron; this.foodReceptor = brain.GetNucleus("Food Receptor") as Neuron; this.homeReceptor = brain.GetNucleus("Home Receptor") as Neuron; this.foodReceptors = brain.GetNucleus("Food Receptors") as Cluster; this.homeReceptors = brain.GetNucleus("Home Receptors") as Cluster; //--- brain outputs this.targetDirection = brain.GetNucleus("Movement") as Neuron; this.hasFood = brain.GetNucleus("Having Food") as Neuron; } } #endregion Init #region Start protected override void Start() { base.Start(); Cluster brain = this.nanoBrain.brain; if (brain != null) { // Try to find the Home Pheromones Neuron if (brain.GetNucleus("Home Pheromones") is Neuron homePheromones) // and call PlaceHomePheromone when it is firing homePheromones.WhenFiring += PlaceHomePheromone; // Try to find the Food Pheromones Neuron Neuron foodPheromones = brain.GetNeuron("Food Pheromones"); if (foodPheromones != null) // and call PlaceFoodPheromone when it is firing foodPheromones.WhenFiring += PlaceFoodPheromone; } StartCoroutine(Beat()); } #endregion Start #region Update private int foodPheromoneIx = 0; void PlaceFoodPheromone() { if (foodPheromonePrefab == null) return; GameObject pheromoneObj = Instantiate(foodPheromonePrefab); pheromoneObj.name = $"To Food {foodPheromoneIx++}"; pheromoneObj.transform.position = this.model.position; } private int homePheromoneIx = 0; void PlaceHomePheromone() { if (homePheromonePrefab == null) return; GameObject pheromoneObj = Instantiate(homePheromonePrefab); pheromoneObj.name = $"To Home {homePheromoneIx++}"; pheromoneObj.transform.position = this.model.position; } public override void Update() { base.Update(); UpdateSmell(); UpdateMovement(); } public virtual void FixedUpdate() { CheckGrounded(); } protected void UpdateMovement() { if (this.targetDirection == null || this.animator == null) return; Vector3 movementDir = this.targetDirection.outputValue; this.linearVelocity = (1 - this.inertia) * (Time.deltaTime * movementDir.normalized) + this.inertia * this.linearVelocity; this.linearVelocity = this.linearVelocity.normalized; this.forwardSpeed = this.linearVelocity.z; float angleRad = this.linearVelocity.sqrMagnitude > 1e-10f ? Mathf.Atan2(this.linearVelocity.x, this.linearVelocity.z) : 0; // map -20..20 degrees to -1..1 this.rotationSpeed = Mathf.Clamp(angleRad * 3, -1f, 1f); this.animator.SetFloat("Forward", this.forwardSpeed); this.animator.SetFloat("Rotation", this.rotationSpeed); } private static readonly WaitForSeconds _waitForSeconds3 = new(3); IEnumerator Beat() { while (Application.isPlaying) { // Beat signal to the brain beat?.SetBias(Vector3.one); // Set random direction to simulate noisy smells perception // which will result in a bit of random walking when no clear // smells are received float randomAngle = Random.Range(-smellAngle, smellAngle); Vector3 randomDirection = Quaternion.AngleAxis(randomAngle, Vector3.up) * Vector3.forward * 0.01f; foodReceptor?.SetBias(randomDirection); randomAngle = Random.Range(-smellAngle, smellAngle); randomDirection = Quaternion.AngleAxis(randomAngle, Vector3.up) * Vector3.forward * 0.01f; homeReceptor?.SetBias(randomDirection); yield return _waitForSeconds3; } } void UpdateSmell() { Collider[] colliders = Physics.OverlapSphere(this.transform.position, smellRadius); foreach (Collider collider in colliders) { SmellPheromones(collider); } } void SmellPheromones(Collider thing) { Vector3 smellDirection = this.transform.InverseTransformPoint(thing.transform.position); float distance = smellDirection.magnitude; float angle = Vector3.Angle(Vector3.forward, smellDirection); if (angle < smellAngle && smellDirection.magnitude > 0.01) { // float intensity = pheromone.StrengthAt(distance); // Vector3 smell = smellDirection.normalized * intensity; Vector3 smell = Vector3.zero; int id = thing.GetInstanceID(); Neuron receptor = null; Pheromone pheromone = thing.GetComponentInParent(); if (pheromone != null) { // multiply by distance to compensate for strong and weak smells smell = pheromone.StrengthAt(smellDirection) * distance / 10; switch (pheromone.type) { case Pheromone.Type.Food: receptor = foodReceptors?.GetNeuron(id, "Output", pheromone.name); //Debug.DrawRay(this.transform.position, this.transform.rotation * smell, Color.magenta); break; case Pheromone.Type.Home: receptor = homeReceptors?.GetNeuron(id, "Output", pheromone.name); //Debug.DrawRay(this.transform.position, this.transform.rotation * smell, Color.cyan); break; } } Food food = thing.GetComponentInParent(); if (food != null) { receptor = foodReceptors?.GetNeuron(id, "Output", food.name); smell = food.StrengthAt(smellDirection) * distance / 10; //Debug.DrawRay(this.transform.position, this.transform.rotation * smell, Color.magenta); } AntsNest home = thing.GetComponentInParent(); if (home != null) { receptor = homeReceptors?.GetNeuron(id, "Output", home.name); smell = home.StrengthAt(smellDirection) * distance / 10; //Debug.DrawRay(this.transform.position, this.transform.rotation * smell, Color.cyan); } receptor?.ProcessStimulus(smell); } } #endregion Update } }