using System.Collections; using UnityEngine; namespace NanoBrain.Unity.Braitenberg { /// /// A non-directional sensor /// /// The sensor has a field of view, but the signal returned does not include a direction public class Sensor : MonoBehaviour { /// /// Maximum distance the sensor detects anything /// [Tooltip("Max distance sensor detects anything")] public float sensorRange = 10f; /// /// Time between samples in seconds /// [Tooltip("Time between samples (sec.)")] public float sampleInterval = 0.1f; /// /// Unity Layer for the light objects /// /// This is used to improve performance [Tooltip("Unity Layer for the light objects")] public LayerMask senseLayer; /// /// Output value of the sensor /// [Tooltip("Output value of the sensor")] public float output { get; protected set; } /// /// The NanoBrain::Neuron which is stimulated by this sensor /// public Neuron sensoryNeuron; /// /// Unity calls Awake when loading an instance of a script component. /// protected virtual void Awake() { Vehicle vehicle = GetComponentInParent(); if (vehicle == null) return; Cluster brain = vehicle.brain; if (brain == null) return; sensoryNeuron = brain.GetNeuron(this.name); } /// /// Called when a component of an active GameObject is first enabled. /// void OnEnable() => StartCoroutine(SampleRoutine()); /// /// Called when a component itself is disabled or its parent GameObject is deactivated. /// void OnDisable() => StopAllCoroutines(); /// /// The sensor samping routine. /// /// IEnumerator such that it can run as a Unity Coroutine /// The sample rate is defined by sampleInterval /// This also stimulates the sensoryNeuron if is has been found. private IEnumerator SampleRoutine() { WaitForSeconds wait = new(sampleInterval); while (true) { output = SampleSensor(); sensoryNeuron?.ProcessStimulus(Vector3.one * (output + 0.00001f)); yield return wait; } } /// /// Performs a sampling function to retrieve a new sensor output value /// /// Sensor output value // Cast a short set of rays in a cone and accumulate "brightness" from hits. protected virtual float SampleSensor() { int rays = 7; float halfAngle = 30f; float total = 0f; for (int i = 0; i < rays; i++) { float t = rays == 1 ? 0.5f : (float)i / (rays - 1); float angle = Mathf.Lerp(-halfAngle, halfAngle, t); Vector3 dir = Quaternion.AngleAxis(angle, this.transform.up) * this.transform.forward; //Debug.DrawRay(this.transform.position, dir * sensorRange); if (Physics.Raycast(this.transform.position, dir, out RaycastHit hit, sensorRange, senseLayer)) { // Strength inversely proportional to distance, clamped to [0,1] float str = 1f - (hit.distance / sensorRange); // You can also sample material emission or color here if desired total += Mathf.Clamp01(str); } } return Mathf.Clamp01(total / rays); } } }