using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using UnityEngine; using Random = UnityEngine.Random; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public class Boid : MonoBehaviour, IDroppable { [field: Title("FishInfo")] [field: SerializeField] public int ItemDropTableIdx { get; set; } [Title("개체 설정")] [SerializeField] private float obstacleDistance = 10; [SerializeField] private float viewAngle = 120; [SerializeField] private int maxNeighbourCount = 10; [SerializeField] private float neighbourDistance = 6; [SerializeField] private float avoidAdditionalSpeed = 10; [Title("Extensions Data")] [SerializeField] private LayerMask boidUnitLayer; [SerializeField] private LayerMask obstacleLayer; private Boids myBoids; private List neighbours = new(); private Collider[] hitColliders; private Coroutine findNeighbourCoroutine; private Coroutine calculateEgoVectorCoroutine; private Vector3 targetPos; private Vector3 egoVector; private float moveSpeed; private float additionalSpeed ; public void Init(Boids boids, float speed) { myBoids = boids; moveSpeed = speed; hitColliders = new Collider[maxNeighbourCount]; findNeighbourCoroutine ??= StartCoroutine("FindNeighbourCoroutine"); calculateEgoVectorCoroutine ??= StartCoroutine("CalculateEgoVectorCoroutine"); } private void OnDrawGizmosSelected() { foreach (var neighbour in neighbours) { if (neighbour == null) continue; var myPos = transform.position; Gizmos.color = Color.red; Gizmos.DrawLine(myPos, neighbour.transform.position); Gizmos.color = Color.blue; Gizmos.DrawLine(myPos, myPos + targetPos); } } private void Update() { if (additionalSpeed > 0) additionalSpeed -= Time.deltaTime; var cohesionPos = CalculateCohesionPos() * myBoids.CohesionWeight; var alignmentPos = CalculateAlignmentPos() * myBoids.AlignmentWeight; var separationPos = CalculateSeparationPos() * myBoids.SeparationWeight; var boundsPos = CalculateBoundsVector() * myBoids.BoundsWeight; var obstaclePos = CalculateObstacleVector() * myBoids.ObstacleWeight; var egoPos = egoVector * myBoids.EgoWeight; targetPos = cohesionPos + alignmentPos + separationPos + boundsPos + obstaclePos + egoPos; targetPos = Vector3.Lerp(transform.forward, targetPos, Time.deltaTime); targetPos = targetPos.normalized; if (targetPos == Vector3.zero) targetPos = egoVector; transform.rotation = Quaternion.LookRotation(targetPos); transform.position += targetPos * ((moveSpeed + additionalSpeed) * Time.deltaTime); } private IEnumerator CalculateEgoVectorCoroutine() { moveSpeed = Random.Range(myBoids.RandomSpeedRange.x, myBoids.RandomSpeedRange.y); egoVector = Random.insideUnitSphere; yield return new WaitForSeconds(Random.Range(1, 3f)); calculateEgoVectorCoroutine = StartCoroutine("CalculateEgoVectorCoroutine"); } private IEnumerator FindNeighbourCoroutine() { neighbours.Clear(); var size = Physics.OverlapSphereNonAlloc(transform.position, neighbourDistance, hitColliders, boidUnitLayer); for (var i = 0; i < size; i++) { if (Vector3.Angle(transform.forward, hitColliders[i].transform.position - transform.position) <= viewAngle) { neighbours.Add(hitColliders[i].GetComponent()); } } yield return new WaitForSeconds(Random.Range(0.5f, 2f)); findNeighbourCoroutine = StartCoroutine("FindNeighbourCoroutine"); } private Vector3 CalculateCohesionPos() { var cohesionPos = Vector3.zero; var validNeighbours = 0; foreach (var neighbour in neighbours.Where(neighbour => neighbour != null)) { cohesionPos += neighbour.transform.position; validNeighbours++; } if (validNeighbours == 0) { return Vector3.zero; } cohesionPos /= validNeighbours; cohesionPos -= transform.position; cohesionPos.Normalize(); return cohesionPos; } private Vector3 CalculateAlignmentPos() { var alignmentPos = transform.forward; var validNeighbours = 0; foreach (var neighbour in neighbours.Where(neighbour => neighbour != null)) { alignmentPos += neighbour.transform.forward ; validNeighbours++; } if (validNeighbours == 0) { return alignmentPos; } alignmentPos /= validNeighbours; alignmentPos.Normalize(); return alignmentPos; } private Vector3 CalculateSeparationPos() { var separationPos = Vector3.zero; var validNeighbours = 0; foreach (var neighbour in neighbours.Where(neighbour => neighbour != null)) { separationPos += transform.position - neighbour.transform.position; validNeighbours++; } if (validNeighbours == 0) { return separationPos; } separationPos /= validNeighbours; separationPos.Normalize(); return separationPos; } private Vector3 CalculateBoundsVector() { var myPos = transform.position; var offsetToCenter = myBoids.BoundMeshRenderer.transform.position - myPos; var insideBounds = myBoids.BoundMeshRenderer.bounds.Contains(myPos); return insideBounds ? Vector3.zero : offsetToCenter.normalized; } private Vector3 CalculateObstacleVector() { var obstaclePos = Vector3.zero; if (Physics.Raycast(transform.position,transform.forward, out var hit, obstacleDistance, obstacleLayer)) { Debug.DrawLine(transform.position, hit.point, Color.black); obstaclePos = hit.normal; additionalSpeed = avoidAdditionalSpeed; } return obstaclePos; } } }