263 lines
10 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NanoBrain;
namespace CreatureControl {
/// <summary>
/// Simulated ant using a NanoBrain
/// </summary>
[RequireComponent(typeof(Brain))]
public class Ant : Insect {
/// <summary>
/// inertia controls how quickly the ant can change it direction
/// </summary>
private readonly float inertia = 0.2f;
/// <summary>
/// The maximum distance at which the ant can smell things
/// </summary>
/// The strength of the smell decreases with the inverse square law
/// to zero at this distance
private readonly float smellRadius = 0.2f;
/// <summary>
/// The angle to the left and right within the ant can smell things
/// </summary>
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
/// <summary>
/// The (heart) beat for the brain
/// </summary>
/// It is used by the brain to do things periodically
/// like placing pheromones
public Neuron beat;
public Neuron pheromoneSteering;
public Neuron hitLeft;
public Neuron 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<Brain>();
Cluster brain = this.nanoBrain.brain;
if (brain != null) {
//--- brain inputs
this.beat = brain.GetNucleus("Beat") as Neuron;
this.hitLeft = brain.GetNucleus("Hit Left") as Neuron;
this.hitRight = brain.GetNucleus("Hit Right") as Neuron;
this.foodReceptor = brain.GetNucleus("Food Receptor") as Neuron;
this.homeReceptor = brain.GetNucleus("Home Receptor") as Neuron;
this.pheromoneSteering = brain.GetNucleus("Pheromone Steering") as Neuron;
//--- brain outputs
this.targetDirection = brain.GetNucleus("Output") 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
if (brain.GetNucleus("Food Pheromones") is Neuron foodPheromones)
// and call PlaceFoodPheromone when it is firing
foodPheromones.WhenFiring += PlaceFoodPheromone;
}
// 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<Pheromone>();
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<Food>();
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<AntsNest>();
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
}
}