238 lines
9.3 KiB
C#
238 lines
9.3 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 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 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<Brain>();
|
|
|
|
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<Pheromone>();
|
|
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<Food>();
|
|
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<AntsNest>();
|
|
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
|
|
|
|
}
|
|
|
|
} |