using System; using System.Collections; using Sirenix.OdinInspector; using UnityEngine; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public class Fish : MonoBehaviour { // 초기화 방식 [Title("초기화 방식")] [SerializeField] private bool autoInit = true; // 물고기의 기본 설정 [Title("물고기의 기본 설정")] [SerializeField] private bool isDrawGizmos = true; [Tooltip("타겟 인식 범위")] [SerializeField] private float viewRadius = 10f; [Tooltip("이동속도")] [SerializeField] private float moveSpd = 500f; [Tooltip("회전속도")] [SerializeField] private float rotationSpeed = 10f; [Tooltip("랜덤 방향으로 도주 여부")] [SerializeField] private bool isRandomAngle = true; [ShowIf("@isRandomAngle")] [Tooltip("도망가는 방향의 랜덤 각도")] [SerializeField] private float randomAngle = 180f; [Tooltip("타겟을 재검색하는 시간")] [SerializeField] private float rescanTime = 0.5f; [Tooltip("도망가는 시간")] [SerializeField] private float escapeTime = 10f; [Tooltip("리스폰 여부")] [SerializeField] private bool isRespawn = true; [ShowIf("@isRespawn == true")] [Tooltip("리스폰되는데 걸리는 시간")] [SerializeField] private float respawnTime = 5f; [Tooltip("도망치면서 랜덤 방향 전환 여부")] [SerializeField] private bool randomDirectionChange; [ShowIf("@!randomDirectionChange")] [Tooltip("도망치면서 방향 전환하는데 걸리는 시간")] [SerializeField] private float directionChangeInterval = 1f; [ShowIf("@randomDirectionChange")] [Tooltip("도망치면서 방향 전환하는데 걸리는 랜덤 최소 시간")] [SerializeField] private float minDirectionChangeInterval = 0.1f; [ShowIf("@randomDirectionChange")] [Tooltip("도망치면서 방향 전환하는데 걸리는 랜덤 최대 시간")] [SerializeField] private float maxDirectionChangeInterval = 1f; [Tooltip("도주 방식")] [SerializeField] private EscapeMode escapeMode = EscapeMode.STRAIGHT; // ZIGZAG [Title("ZIGZAG")] [ShowIf("@escapeMode == EscapeMode.ZIGZAG")] [Tooltip("흔들림의 정도")] [SerializeField] private bool randomZigzag; [ShowIf("@escapeMode == EscapeMode.ZIGZAG && !randomZigzag")] [Tooltip("흔들림의 정도")] [SerializeField] private float zigzagAmplitude = 1f; [ShowIf("@escapeMode == EscapeMode.ZIGZAG && !randomZigzag")] [Tooltip("흔들림의 주기")] [SerializeField] private float zigzagFrequency = 1f; [ShowIf("@escapeMode == EscapeMode.ZIGZAG && randomZigzag")] [Tooltip("흔들림의 정도 랜덤 최솟값")] [SerializeField] private float minZigzagAmplitude = 0.1f; [ShowIf("@escapeMode == EscapeMode.ZIGZAG && randomZigzag")] [Tooltip("흔들림의 정도 랜덤 최댓값")] [SerializeField] private float maxZigzagAmplitude = 1f; [Space] [ShowIf("@escapeMode == EscapeMode.ZIGZAG && randomZigzag")] [Tooltip("흔들림의 주기 랜덤 최솟값")] [SerializeField] private float minZigzagFrequency = 0.1f; [ShowIf("@escapeMode == EscapeMode.ZIGZAG && randomZigzag")] [Tooltip("흔들림의 주기 랜덤 최댓값")] [SerializeField] private float maxZigzagFrequency = 1f; // 디버깅 [Title("디버깅")] [SerializeField] private Collider[] hitColliders = new Collider[MAX_HIT_NUM]; [SerializeField] private LayerMask targetLayer; private Rigidbody rb; private Coroutine findTargetCoroutine; private Coroutine escapeCoroutine; private WaitForSeconds findCoroutineTime; private Vector3 spawnPos; private const int MAX_HIT_NUM = 3; private void OnValidate() { findCoroutineTime = new WaitForSeconds(rescanTime); } private void OnDrawGizmosSelected() { if (!isDrawGizmos) return; Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, viewRadius); } [Button("셋팅 초기화")] private void Init() { rb = GetComponent(); targetLayer = LayerMask.GetMask("Player"); } #region Preset [HorizontalGroup("Split")] [Button("프리셋 1번")] private void Preset1() { isDrawGizmos = true; viewRadius = 20f; moveSpd = 500f; rotationSpeed = 10f; isRandomAngle = false; rescanTime = 0.5f; escapeTime = 10f; isRespawn = true; respawnTime = 5f; randomDirectionChange = false; directionChangeInterval = 3f; escapeMode = EscapeMode.STRAIGHT; } [HorizontalGroup("Split")] [Button("프리셋 2번")] private void Preset2() { isDrawGizmos = true; viewRadius = 20f; moveSpd = 500f; rotationSpeed = 10f; isRandomAngle = true; randomAngle = 120f; rescanTime = 0.5f; escapeTime = 10f; isRespawn = true; respawnTime = 5f; randomDirectionChange = true; minDirectionChangeInterval = 1f; maxDirectionChangeInterval = 3f; directionChangeInterval = 3f; escapeMode = EscapeMode.ZIGZAG; randomZigzag = false; zigzagAmplitude = 0.5f; zigzagFrequency = 1f; } [HorizontalGroup("Split")] [Button("프리셋 3번")] private void Preset3() { isDrawGizmos = true; viewRadius = 20f; moveSpd = 500f; rotationSpeed = 10f; isRandomAngle = true; randomAngle = 120f; rescanTime = 0.5f; escapeTime = 10f; isRespawn = true; respawnTime = 5f; randomDirectionChange = true; minDirectionChangeInterval = 1f; maxDirectionChangeInterval = 3f; directionChangeInterval = 3f; escapeMode = EscapeMode.ZIGZAG; randomZigzag = true; minZigzagAmplitude = 0.1f; maxZigzagAmplitude = 2f; minZigzagFrequency = 0.1f; maxZigzagFrequency = 2f; } #endregion private void Awake() { if (autoInit) { Init(); } } private void OnEnable() { hitColliders = new Collider[MAX_HIT_NUM]; findCoroutineTime = new WaitForSeconds(rescanTime); if (findTargetCoroutine != null) return; findTargetCoroutine = StartCoroutine(FindTargetCoroutine()); } private void Start() { spawnPos = transform.position; } private IEnumerator FindTargetCoroutine() { while (true) { var size = Physics.OverlapSphereNonAlloc(transform.position, viewRadius, hitColliders, targetLayer); for (var i = 0; i < size; i++) { var hitCollider = hitColliders[i]; if (hitCollider == null || !hitCollider.CompareTag("ShipPlayer")) continue; findTargetCoroutine = null; escapeCoroutine = StartCoroutine(EscapeCoroutine(hitCollider)); yield break; } yield return findCoroutineTime; } } private IEnumerator EscapeCoroutine(Collider targetCollider) { var currentDirectionChangeInterval = randomDirectionChange ? UnityEngine.Random.Range(minDirectionChangeInterval, maxDirectionChangeInterval) : directionChangeInterval; var rotatedEscapeDirection = CalculateEscapeDirection(targetCollider.transform.position); var time = 0f; var directionChangeTime = 0f; while (time < escapeTime) { time += Time.deltaTime; directionChangeTime += Time.deltaTime; if (directionChangeTime >= currentDirectionChangeInterval) { rotatedEscapeDirection = CalculateEscapeDirection(targetCollider.transform.position); directionChangeTime = 0f; currentDirectionChangeInterval = randomDirectionChange ? UnityEngine.Random.Range(minDirectionChangeInterval, maxDirectionChangeInterval) : directionChangeInterval; if (escapeMode == EscapeMode.ZIGZAG && randomZigzag) { zigzagFrequency = UnityEngine.Random.Range(minZigzagFrequency, maxZigzagFrequency); zigzagAmplitude = UnityEngine.Random.Range(minZigzagAmplitude, maxZigzagAmplitude); } } var newDirection = escapeMode switch { EscapeMode.NONE => throw new ArgumentOutOfRangeException(), EscapeMode.STRAIGHT => rotatedEscapeDirection, EscapeMode.ZIGZAG => rotatedEscapeDirection + new Vector3(Mathf.Sin(Time.time * zigzagFrequency) * zigzagAmplitude,0, Mathf.Sin(Time.time * zigzagFrequency) * zigzagAmplitude), EscapeMode.TOWARDS => -rotatedEscapeDirection, _ => throw new ArgumentOutOfRangeException() }; rb.velocity = newDirection.normalized * (moveSpd * Time.deltaTime); var targetRotation = Quaternion.LookRotation(newDirection); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); yield return null; } rb.velocity = Vector3.zero; escapeCoroutine = null; if (isRespawn) { //FishManager.Inst.RespawnFish(gameObject, respawnTime, spawnPos); } gameObject.SetActive(false); } private Vector3 CalculateEscapeDirection(Vector3 targetPos) { var escapeDirection = (transform.position - targetPos).normalized; escapeDirection.y = 0; if (!isRandomAngle) return escapeDirection; var randomRotationAngle = UnityEngine.Random.Range(-randomAngle * 0.5f, randomAngle * 0.5f); var rotation = Quaternion.Euler(0, randomRotationAngle, 0); return rotation * escapeDirection; } } }