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 output public Neuron targetDirection; public Neuron hasFood; // brain input /// /// The (heart) beat for the brain /// /// It is used by the brain to do things periodically /// like placing pheromones public Nucleus beat; public Nucleus pheromoneSteering; public Nucleus hitLeft; public Nucleus hitRight; public Neuron foodReceptor; public Neuron homeReceptor; public Vector3 linearVelocity = Vector3.forward; public Vector3 angularVelocity; #region Init protected override void Awake() { base.Awake(); this.nanoBrain = GetComponentInChildren(); } #endregion Init #region Start protected override void Start() { base.Start(); Cluster brain = this.nanoBrain.brain; if (brain != null) { //--- brain inputs this.beat = brain.GetNucleus("Beat"); this.hitLeft = brain.GetNucleus("Hit Left"); this.hitRight = brain.GetNucleus("Hit Right"); this.foodReceptor = brain.GetNucleus("Food Receptor") as Neuron; this.homeReceptor = brain.GetNucleus("Home Receptor") as Neuron; this.pheromoneSteering = brain.GetNucleus("Pheromone Steering"); //--- brain outputs this.targetDirection = brain.defaultOutput; // 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 if (brain.GetNucleus("Food Pheromones") is Neuron foodPheromones) // and call PlaceFoodPheromone when it is firing foodPheromones.WhenFiring += PlaceFoodPheromone; this.hasFood = brain.GetNucleus("Having Food") as Neuron; } // Initialize the callbacks for the antenna colliders if (touchLeft != null) touchLeft.touched += OnAntennaTouchLeft; if (touchRight != null) touchRight.touched += OnAntennaTouchRight; StartCoroutine(Beat()); } #endregion Start #region Update void PlaceFoodPheromone() { GameObject pheromoneObj = Instantiate(foodPheromonePrefab); pheromoneObj.transform.position = this.model.position; } void PlaceHomePheromone() { GameObject pheromoneObj = Instantiate(homePheromonePrefab); 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; float forwardParam = 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 float rotateParam = Mathf.Clamp(angleRad * 3, -1f, 1f); this.animator.SetFloat("Forward", forwardParam); this.animator.SetFloat("Rotation", rotateParam); } 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; pheromoneSteering?.SetBias(randomDirection); yield return _waitForSeconds3; } } void UpdateSmell() { Collider[] colliders = Physics.OverlapSphere(this.transform.position, smellRadius); foreach (Collider collider in colliders) { SmellPheromones(collider); SmellFood(collider); SmellHome(collider); } if (nanoBrain != null && nanoBrain.brain != null) nanoBrain.brain.UpdateNuclei(); } void SmellPheromones(Collider thing) { Pheromone pheromone = thing.GetComponentInParent(); if (pheromone == null) return; Vector3 smellDirection = this.transform.InverseTransformPoint(pheromone.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; switch (pheromone.type) { case Pheromone.Type.Food: foodReceptor?.ProcessStimulus(smellDirection.normalized * intensity); //, pheromone.GetInstanceID(), "food pheromone"); break; case Pheromone.Type.Home: homeReceptor?.ProcessStimulus(smellDirection.normalized * intensity); //, pheromone.GetInstanceID(), "home pheromone"); break; } //Debug.DrawLine(this.transform.position, pheromone.transform.position, Color.magenta); } } void SmellFood(Collider thing) { if (hasFood != null && hasFood.outputValue.x > 0) // if it has food... return; Food food = thing.GetComponentInParent(); if (food == null) return; Vector3 smellDirection = this.transform.InverseTransformPoint(food.transform.position); float distance = smellDirection.magnitude; float angle = Vector3.Angle(Vector3.forward, smellDirection); if (angle < smellAngle && distance > 0.01) { float intensity = food.StrengthAt(distance); foodReceptor?.ProcessStimulus(smellDirection.normalized * intensity); //, food.GetInstanceID(), "food"); Debug.DrawLine(this.transform.position, food.transform.position, Color.red); } } void SmellHome(Collider thing) { if (hasFood != null && hasFood.outputValue.x < 0) // if it does not have food.... return; AntsNest nest = thing.GetComponentInParent(); if (nest == null) return; Vector3 smellDirection = this.transform.InverseTransformPoint(nest.transform.position); float distance = smellDirection.magnitude; float angle = Vector3.Angle(Vector3.forward, smellDirection); if (angle < smellAngle && distance > 0.01) { float intensity = nest.StrengthAt(distance); Vector3 value = smellDirection.normalized * intensity; homeReceptor?.ProcessStimulus(value); //, nest.GetInstanceID(), "nest"); Debug.DrawLine(this.transform.position, nest.transform.position, Color.red); } } void OnAntennaTouchLeft(Collider other, bool isTouching) { Vector3 touchDirection = Vector3.zero; if (isTouching) touchDirection = this.transform.InverseTransformVector(touchLeft.transform.forward); hitLeft?.SetBias(touchDirection); } void OnAntennaTouchRight(Collider other, bool isTouching) { Vector3 touchDirection = Vector3.zero; if (isTouching) touchDirection = this.transform.InverseTransformVector(touchRight.transform.forward); hitRight?.SetBias(touchDirection); } #endregion Update } }