234 lines
9.5 KiB
C#
234 lines
9.5 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using NanoBrain;
|
|
|
|
namespace Passer.CreatureControl {
|
|
|
|
[RequireComponent(typeof(Brain))]
|
|
public class Ant : Insect {
|
|
private readonly float inertia = 0.2f;
|
|
private readonly float smellRadius = 0.2f;
|
|
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 hasFood;
|
|
// brain input
|
|
public Nucleus pheromoneSteering;
|
|
public Nucleus hitLeft;
|
|
public Nucleus hitRight;
|
|
public Nucleus beat;
|
|
public Receptor foodReceptor;
|
|
public Receptor homeReceptor;
|
|
|
|
public Animator animator;
|
|
public Vector3 linearVelocity;
|
|
public Vector3 angularVelocity;
|
|
|
|
#region Init
|
|
|
|
protected virtual void Awake() {
|
|
if (this.targetRig != null)
|
|
this.animator = this.targetRig.animator;
|
|
|
|
this.nanoBrain = GetComponentInChildren<Brain>();
|
|
}
|
|
|
|
#endregion Init
|
|
|
|
#region Start
|
|
|
|
protected override void Start() {
|
|
base.Start();
|
|
|
|
Cluster brain = this.nanoBrain.brain;
|
|
if (brain != null) {
|
|
// brain outputs
|
|
this.pheromoneSteering = brain.GetNucleus("Pheromone Steering");
|
|
|
|
if (brain.GetNucleus("Home Pheromones") is Neuron homePheromones)
|
|
homePheromones.WhenFiring += PlaceHomePheromone;
|
|
if (brain.GetNucleus("Food Pheromones") is Neuron foodPheromones)
|
|
foodPheromones.WhenFiring += PlaceFoodPheromone;
|
|
|
|
this.hasFood = brain.GetNucleus("Having Food") as Neuron;
|
|
// 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 Receptor;
|
|
this.homeReceptor = brain.GetNucleus("Home Receptor") as Receptor;
|
|
}
|
|
|
|
this.linearVelocity = Vector3.forward;
|
|
|
|
if (touchLeft != null)
|
|
touchLeft.touched += OnAntennaTouchLeft;
|
|
if (touchRight != null)
|
|
touchRight.touched += OnAntennaTouchRight;
|
|
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
// Update is called once per frame
|
|
public override void Update() {
|
|
base.Update();
|
|
|
|
UpdateBeat();
|
|
UpdateSmell();
|
|
|
|
if (this.nanoBrain == null || this.nanoBrain.brain == null || this.animator == null)
|
|
return;
|
|
|
|
Vector3 localForce = nanoBrain.brain.defaultOutput.outputValue;
|
|
this.linearVelocity = (1 - inertia) * (Time.deltaTime * localForce.normalized) + inertia * this.linearVelocity;
|
|
this.linearVelocity = this.linearVelocity.normalized * 0.2f;
|
|
|
|
this.animator.SetFloat("Forward", this.forwardSpeed); //this.linearVelocity.z * this.forwardSpeed);
|
|
|
|
// Rotate towards the movement direction
|
|
if (this.linearVelocity != Vector3.zero) {
|
|
Quaternion targetRotation = Quaternion.LookRotation(this.linearVelocity);
|
|
Quaternion worldRotation = transform.rotation * targetRotation;
|
|
Quaternion deltaRotation = worldRotation * Quaternion.Inverse(transform.rotation);
|
|
|
|
Vector3 eulerAngleChange = deltaRotation.eulerAngles;
|
|
// Normalize the Euler angles to avoid unexpected jumps due to 360-degree rotations
|
|
eulerAngleChange = new Vector3(
|
|
LinearAlgebra.Angles.Normalize(eulerAngleChange.x),
|
|
LinearAlgebra.Angles.Normalize(eulerAngleChange.y),
|
|
LinearAlgebra.Angles.Normalize(eulerAngleChange.z)
|
|
);
|
|
|
|
this.animator.SetFloat("Rotate", eulerAngleChange.y / 45 * this.rotationSpeed);
|
|
}
|
|
}
|
|
|
|
public float beatInterval = 3;
|
|
float lastBeatTime = 0;
|
|
void UpdateBeat() {
|
|
if (lastBeatTime == 0) {
|
|
ulong delay = (ulong)(Random.value * beatInterval);
|
|
lastBeatTime = Time.time - delay;
|
|
}
|
|
if (Time.time - lastBeatTime >= beatInterval) {
|
|
lastBeatTime = Time.time;
|
|
beat?.SetBias(Vector3.one); //, 0);
|
|
|
|
float randomAngle = Random.Range(-smellAngle, smellAngle);
|
|
Vector3 randomDirection = Quaternion.AngleAxis(randomAngle, Vector3.up) * Vector3.forward * 1.01f;
|
|
pheromoneSteering?.SetBias(randomDirection); //, 0, "random");
|
|
}
|
|
}
|
|
|
|
void UpdateSmell() {
|
|
// To generate random basic movement, we add a small with a random direction with low intensity
|
|
|
|
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
|
|
}
|
|
|
|
} |