Untitled

Anonymous
csharp
10/17/2026 8:53 PM
80.5 KB
6
Indexable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(BotAI))]
[RequireComponent(typeof(CharacterPerception))]
public class BotMovement : MonoBehaviour
{
    // =========================================================
    // BOT REGISTRY
    // =========================================================

    private static readonly List<BotMovement> allBots =
        new List<BotMovement>();

    // =========================================================
    // REUSABLE PERFORMANCE DATA
    // =========================================================

    private readonly List<BotMovement> nearbyBotsCache =
        new List<BotMovement>(16);

    private NavMeshPath reusableNavMeshPath;

    private const int WaypointClearanceBufferSize = 64;

    private readonly Collider[] waypointClearanceResults =
        new Collider[WaypointClearanceBufferSize];

    private static readonly Vector3[] reachableSearchDirections =
    {
        Vector3.forward,
        Vector3.back,
        Vector3.right,
        Vector3.left,

        new Vector3(
            0.7071068f,
            0f,
            0.7071068f
        ),

        new Vector3(
            -0.7071068f,
            0f,
            0.7071068f
        ),

        new Vector3(
            0.7071068f,
            0f,
            -0.7071068f
        ),

        new Vector3(
            -0.7071068f,
            0f,
            -0.7071068f
        )
    };

    private static readonly float[] reachableSearchDistances =
    {
        2f,
        4f,
        6f,
        8f
    };

    // =========================================================
    // MOVEMENT
    // =========================================================

    [Header("Yürüme hızı")]
    public float moveSpeed = 5f;

    [Header("Savaşma ve hız ayarları")]
    public float combatMoveSpeed = 2.5f;
    public float combatMoveDurationMin = 0.25f;
    public float combatMoveDurationMax = 0.55f;

    [Header("Stratejik bölgelere yakınlık")]
    public float strategicApproachDistance = 8f;

    private float combatDirection = 0f;
    private float combatDecisionTimer = 0f;

    private BotAI.AIState previousState;

    private NavMeshAgent agent;
    private BotAI botAI;
    private CharacterPerception perception;
    private TeamStrategy teamStrategy;

    // =========================================================
    // COMBAT MOVEMENT SAFETY
    // =========================================================

    [Header("Combat Movement Safety")]
    public float combatSideCheckDistance = 1.2f;
    public float combatMinimumSideDistance = 0.65f;
    public float combatStuckCheckTime = 0.3f;
    public float combatMinimumMovement = 0.05f;

    private float combatStuckTimer = 0f;
    private Vector3 combatLastPosition;
    private bool combatHasLastPosition = false;

    private CharacterAnimator characterAnimator;

    // =========================================================
    // TARGET APPROACH
    // =========================================================

    [Header("Target Approach")]
    public float approachOffsetDistance = 2f;

    private Vector3 investigateApproachPosition;
    private bool hasInvestigateApproachPosition;

    private Vector3 approachTargetPosition;
    private bool hasApproachTargetPosition;

    // =========================================================
    // BOT AVOIDANCE
    // =========================================================

    [Header("Bot Avoidance")]
    public float botAvoidanceDistance = 3f;
    public float botAvoidanceStrength = 2f;

    [Header("Bot Avoidance Cache")]
    [SerializeField] private float avoidanceCheckInterval = 0.1f;

    private float nextAvoidanceCheckTime;

    private Vector3 cachedAvoidedTargetPosition;
    private Vector3 cachedAvoidanceTargetPosition;

    private bool avoidanceCacheValid;

    private const int AvoidanceBufferSize = 64;

    private readonly Collider[] avoidanceResults =
        new Collider[AvoidanceBufferSize];

    // =========================================================
    // COMBAT SIDE SENSORS
    // =========================================================

    [Header("Combat Side Sensors")]
    [SerializeField] private Transform combatLeftSensor;
    [SerializeField] private Transform combatRightSensor;

    [SerializeField] private float combatSensorRadius = 0.3f;
    [SerializeField] private LayerMask combatObstacleLayers;

    // =========================================================
    // TEMPORARY WAYPOINT
    // =========================================================

    [Header("Temporary Waypoint")]
    public float waypointDistance = 2.5f;
    public float waypointSideOffset = 1.2f;
    public float waypointObstacleCheckDistance = 2f;
    public float waypointWallCheckDistance = 1.5f;

    public float waypointReachDistance = 1.1f;

    public float waypointMinimumClearance = 0.45f;

    public float waypointSampleRadius = 1.0f;

    public float waypointStuckCheckTime = 1.5f;

    public float waypointMinimumMovement = 0.08f;

    [SerializeField] private LayerMask waypointObstacleLayers;

    private Vector3 temporaryWaypoint;
    private bool hasTemporaryWaypoint;

    private float waypointStuckTimer;
    private Vector3 waypointLastPosition;
    private bool waypointHasLastPosition;

    // =========================================================
    // FALLBACK WAYPOINT
    // =========================================================

    [Header("Fallback Waypoint System")]
    [SerializeField] private BotWaypointManager waypointManager;

    [SerializeField] private bool useFallbackWaypoints = true;

    private bool usingFallbackWaypoint;

    // =========================================================
    // WAYPOINT DEBUG
    // =========================================================

    [Header("Waypoint Debug")]
    [SerializeField] private bool waypointDebug = false;

    private string lastWaypointDebugMessage;
    private float lastWaypointDebugTime;


    // =========================================================
    // LOOK AROUND
    // =========================================================

    [Header("Look Around")]
    public float lookAroundIntervalMin = 3f;
    public float lookAroundIntervalMax = 6f;

    private float lookAroundTimer;
    private BotRotation botRotation;
    //yeni eklenen
    private BotReaction botReaction;

    // =========================================================
    // INVESTIGATE PATH CACHE
    // =========================================================

    [Header("Investigate Path")]
    [SerializeField] private float investigatePathRecalculateInterval = 0.25f;

    private float investigatePathRecalculateTimer;

    private Vector3 investigateLastTargetPosition;

    private bool investigateHasPathTarget;





    // =========================================================
    // UNITY - AWAKE
    // =========================================================

    private void Awake()
    {
        agent =
            GetComponent<NavMeshAgent>();

        agent.speed =
            moveSpeed;
        //eski
        //agent.updateRotation = true;
        //agent.angularSpeed = 360f;
        //yeni
        agent.updateRotation = false;

        botAI =
            GetComponent<BotAI>();

        perception =
            GetComponent<CharacterPerception>();

        characterAnimator =
            GetComponentInChildren<CharacterAnimator>();

        botRotation = GetComponentInChildren<BotRotation>(true);
        botReaction = GetComponentInChildren<BotReaction>(true);

        reusableNavMeshPath =
            new NavMeshPath();

        previousState =
            botAI.currentState;

        lookAroundTimer =
            Random.Range(
                lookAroundIntervalMin,
                lookAroundIntervalMax
            );

        if (waypointManager == null)
        {
            waypointManager =
                FindFirstObjectByType<BotWaypointManager>();
        }
    }

    // =========================================================
    // UNITY - ON ENABLE
    // =========================================================

    private void OnEnable()
    {
        if (!allBots.Contains(this))
        {
            allBots.Add(this);
        }

        InvalidateAvoidanceCache();
    }

    // =========================================================
    // UNITY - ON DISABLE
    // =========================================================

    private void OnDisable()
    {
        allBots.Remove(this);

        InvalidateAvoidanceCache();
    }

    // =========================================================
    // START
    // =========================================================

    private void Start()
    {
        FindTeamStrategy();

        if (waypointManager == null &&
            useFallbackWaypoints)
        {
            waypointManager =
                FindFirstObjectByType<BotWaypointManager>();

            if (waypointManager == null)
            {
                WaypointDebugLog(
                    "BotWaypointManager bulunamadı. " +
                    "Fallback waypoint sistemi kullanılamayacak."
                );
            }
        }
    }

    // =========================================================
    // UPDATE
    // =========================================================

    private void Update()
    {
        if (!CompareTag("Bot"))
            return;

        if (botAI == null ||
            perception == null ||
            agent == null)
        {
            return;
        }

        if (!agent.enabled)
            return;

        //botları test ederken bu kısımı kapatıyorum yoksa botlar server olmadan hareket etmiyor
        //if (GameServer.Instance == null)
        //    return;

        //if (!GameServer.Instance.IsPlaying())
        //{
        //    if (agent.isOnNavMesh)
        //    {
        //        agent.isStopped = true;
        //        agent.ResetPath();
        //    }

        //    return;
        //}

        HandleStateTransition();
        HandleMovement();
        HandleLookAround();
        UpdateAnimation();

        previousState =
            botAI.currentState;
    }

    // =========================================================
    // ANIMATION
    // =========================================================

    private void UpdateAnimation()
    {
        if (characterAnimator == null)
            return;

        Vector3 velocity =
            agent.velocity;

        Vector3 horizontalVelocity =
            new Vector3(
                velocity.x,
                0f,
                velocity.z
            );

        float speed =
            horizontalVelocity.magnitude;

        characterAnimator.SetMovementSpeed(
            speed
        );

        if (speed > 0.01f)
        {
            Vector3 localVelocity =
                transform.InverseTransformDirection(
                    horizontalVelocity
                );

            localVelocity.Normalize();

            characterAnimator.SetMovementDirection(
                localVelocity
            );
        }
        else
        {
            characterAnimator.SetMovementDirection(
                Vector3.zero
            );
        }

        characterAnimator.SetGrounded(
            agent.isOnNavMesh
        );

        characterAnimator.SetCrouching(false);
    }

    // =========================================================
    // LOOK AROUND
    // =========================================================

    private void HandleLookAround()
    {
        if (!agent.isOnNavMesh)
            return;

        if (botAI.currentState == BotAI.AIState.Combat)
            return;

        if (botRotation == null)
            return;

        Vector3 velocity = agent.velocity;
        velocity.y = 0f;

        if (velocity.sqrMagnitude < 0.01f)
            return;

        lookAroundTimer -= Time.deltaTime;

        if (lookAroundTimer <= 0f)
        {
            botRotation.LookAround("", false);

            lookAroundTimer = Random.Range(
                lookAroundIntervalMin,
                lookAroundIntervalMax
            );
        }
    }

    // =========================================================
    // STATE TRANSITION
    // =========================================================

    private void HandleStateTransition()
    {
        if (!agent.isOnNavMesh)
            return;

        if (previousState ==
            botAI.currentState)
        {
            return;
        }

        InvalidateAvoidanceCache();

        if (previousState ==
                BotAI.AIState.Combat &&
            botAI.currentState ==
                BotAI.AIState.Investigate)
        {
            agent.isStopped = true;

            agent.ResetPath();

            hasApproachTargetPosition = false;

            InvalidateTemporaryWaypoint();

            ResetInvestigatePathCache();

            CreateInvestigateApproachPosition();
        }

        if (botAI.currentState !=
            BotAI.AIState.Investigate)
        {
            ResetInvestigatePathCache();
        }
    }

    // =========================================================
    // INVESTIGATE APPROACH POSITION
    // =========================================================

    private void CreateInvestigateApproachPosition()
    {
        if (!agent.isOnNavMesh)
            return;

        Vector3 targetPosition =
            botAI.lastSeenPosition;

        if (!TryFindReachablePosition(
                targetPosition,
                out Vector3 reachablePosition))
        {
            Debug.LogWarning(
                gameObject.name +
                " → Investigate için ulaşılabilir hedef bulunamadı. " +
                "Son görülen konum: " +
                targetPosition
            );

            hasInvestigateApproachPosition = false;

            hasApproachTargetPosition = false;

            ResetInvestigatePathCache();

            return;
        }

        approachTargetPosition =
            reachablePosition;

        hasApproachTargetPosition = true;

        investigateApproachPosition =
            reachablePosition;

        hasInvestigateApproachPosition = true;

        investigateLastTargetPosition =
            reachablePosition;

        investigatePathRecalculateTimer = 0f;

        investigateHasPathTarget = true;

        InvalidateAvoidanceCache();
    }

    // =========================================================
    // FIND REACHABLE POSITION
    // =========================================================

    private bool TryFindReachablePosition(
        Vector3 targetPosition,
        out Vector3 reachablePosition)
    {
        reachablePosition =
            transform.position;

        const float sampleRadius = 8f;

        if (TryGetReachableNavMeshPoint(
                targetPosition,
                sampleRadius,
                out reachablePosition))
        {
            return true;
        }

        for (int distanceIndex = 0;
             distanceIndex < reachableSearchDistances.Length;
             distanceIndex++)
        {
            float distance =
                reachableSearchDistances[
                    distanceIndex
                ];

            for (int directionIndex = 0;
                 directionIndex < reachableSearchDirections.Length;
                 directionIndex++)
            {
                Vector3 testPosition =
                    targetPosition +
                    reachableSearchDirections[
                        directionIndex
                    ] *
                    distance;

                if (TryGetReachableNavMeshPoint(
                        testPosition,
                        2f,
                        out reachablePosition))
                {
                    return true;
                }
            }
        }

        return false;
    }

    // =========================================================
    // REACHABLE NAVMESH POINT
    // =========================================================

    private bool TryGetReachableNavMeshPoint(
        Vector3 position,
        float sampleRadius,
        out Vector3 reachablePosition)
    {
        reachablePosition =
            transform.position;

        if (!agent.isOnNavMesh)
            return false;

        if (!NavMesh.SamplePosition(
                position,
                out NavMeshHit hit,
                sampleRadius,
                NavMesh.AllAreas))
        {
            return false;
        }

        reusableNavMeshPath.ClearCorners();

        bool pathCalculated =
            NavMesh.CalculatePath(
                transform.position,
                hit.position,
                NavMesh.AllAreas,
                reusableNavMeshPath
            );

        if (!pathCalculated)
            return false;

        if (reusableNavMeshPath.status ==
            NavMeshPathStatus.PathInvalid)
        {
            return false;
        }

        if (reusableNavMeshPath.corners == null ||
            reusableNavMeshPath.corners.Length == 0)
        {
            return false;
        }

        reachablePosition =
            hit.position;

        return true;
    }

    // =========================================================
    // MOVEMENT
    // =========================================================

    private void HandleMovement()
    {
        switch (botAI.currentState)
        {
            case BotAI.AIState.Combat:
                HandleCombat();
                break;

            case BotAI.AIState.Investigate:
                HandleInvestigate();
                break;

            case BotAI.AIState.Idle:
                HandleStrategicMovement();
                break;
        }
    }

    // =========================================================
    // STRATEGIC MOVEMENT
    // =========================================================

    private void HandleStrategicMovement()
    {
        if (!agent.isOnNavMesh)
            return;

        if (teamStrategy == null)
        {
            FindTeamStrategy();

            if (teamStrategy == null)
            {
                StopMovement();
                return;
            }
        }

        switch (botAI.currentTask)
        {
            case BotTask.TaskType.GoToOwnA:

                MoveToArea(
                    teamStrategy.GetOwnArea(
                        BotTask.TaskType.GoToOwnA
                    )
                );

                break;

            case BotTask.TaskType.GoToOwnB:

                MoveToArea(
                    teamStrategy.GetOwnArea(
                        BotTask.TaskType.GoToOwnB
                    )
                );

                break;

            case BotTask.TaskType.GoToEnemyA:

                MoveToArea(
                    teamStrategy.GetEnemyArea(
                        BotTask.TaskType.GoToEnemyA
                    )
                );

                break;

            case BotTask.TaskType.GoToEnemyB:

                MoveToArea(
                    teamStrategy.GetEnemyArea(
                        BotTask.TaskType.GoToEnemyB
                    )
                );

                break;

            default:

                StopMovement();

                break;
        }
    }

    // =========================================================
    // COMBAT SIDE CHECK
    // =========================================================

    private bool IsCombatSideBlocked(
        float direction)
    {
        Transform sensor =
            direction < 0f
                ? combatLeftSensor
                : combatRightSensor;

        if (sensor == null)
            return false;

        return Physics.CheckSphere(
            sensor.position,
            combatSensorRadius,
            combatObstacleLayers,
            QueryTriggerInteraction.Ignore
        );
    }

    // =========================================================
    // MOVE TO AREA
    // =========================================================

    private void MoveToArea(
        StrategicArea targetArea)
    {
        if (targetArea == null)
        {
            StopMovement();
            return;
        }

        if (!agent.isOnNavMesh)
            return;

        Vector3 targetPosition =
            targetArea.transform.position;

        if (!hasApproachTargetPosition ||
            Vector3.Distance(
                approachTargetPosition,
                targetPosition
            ) > 0.1f)
        {
            approachTargetPosition =
                targetPosition;

            hasApproachTargetPosition =
                true;

            InvalidateTemporaryWaypoint();

            InvalidateAvoidanceCache();

            WaypointDebugLog(
                "Gerçek hedef değişti. Eski waypoint geçersiz."
            );
        }

        Vector3 distributedPosition =
            GetDistributedApproachPosition(
                approachTargetPosition
            );

        Vector3 avoidedPosition =
            GetBotAvoidedTargetPosition(
                distributedPosition
            );

        Vector3 directionToTarget =
            avoidedPosition -
            transform.position;

        directionToTarget.y = 0f;

        float distanceToTarget =
            directionToTarget.magnitude;

        if (distanceToTarget <=
            strategicApproachDistance)
        {
            agent.isStopped = true;

            agent.ResetPath();

            InvalidateTemporaryWaypoint();

            hasApproachTargetPosition = false;

            InvalidateAvoidanceCache();

            botAI.ReportReachedArea(
                targetArea
            );

            return;
        }

        if (hasTemporaryWaypoint &&
            !agent.pathPending &&
            agent.hasPath &&
            agent.pathStatus !=
                NavMeshPathStatus.PathComplete)
        {
            WaypointDebugLog(
                "Mevcut waypoint path geçersiz hale geldi: " +
                agent.pathStatus
            );

            if (usingFallbackWaypoint)
            {
                InvalidateTemporaryWaypoint();

                if (!TryCreateFallbackWaypoint(
                        avoidedPosition))
                {
                    return;
                }

                StartWaypointMovementTracking();
            }
            else
            {
                InvalidateTemporaryWaypoint();
            }
        }

        bool waypointStuck =
            CheckTemporaryWaypointStuck();

        if (!hasTemporaryWaypoint ||
            Vector3.Distance(
                transform.position,
                temporaryWaypoint
            ) <= waypointReachDistance ||
            waypointStuck)
        {
            if (waypointStuck)
            {
                WaypointDebugLog(
                    "Stuck tespit edildi. Yeni waypoint aranıyor."
                );
            }

            InvalidateTemporaryWaypoint();

            bool normalWaypointCreated =
                CreateTemporaryWaypoint(
                    avoidedPosition
                );

            if (!normalWaypointCreated)
            {
                WaypointDebugLog(
                    "Normal waypoint sistemi başarısız. " +
                    "Fallback waypoint sistemi devreye giriyor."
                );

                if (!TryCreateFallbackWaypoint(
                        avoidedPosition))
                {
                    WaypointDebugLog(
                        "Fallback waypoint de bulunamadı. " +
                        "Bot durdurulmadan bir sonraki frame tekrar denenecek."
                    );

                    hasTemporaryWaypoint = false;

                    return;
                }

                WaypointDebugLog(
                    "Fallback waypoint kabul edildi: " +
                    temporaryWaypoint
                );
            }

            StartWaypointMovementTracking();

            WaypointDebugLog(
                "Yeni waypoint oluşturuldu. " +
                "Waypoint mesafesi: " +
                Vector3.Distance(
                    transform.position,
                    temporaryWaypoint
                ).ToString("F2") +
                "m"
            );
        }

        agent.isStopped = false;

        bool destinationAccepted =
            agent.SetDestination(
                temporaryWaypoint
            );

        if (!destinationAccepted)
        {
            WaypointDebugLog(
                "SetDestination BAŞARISIZ."
            );

            bool wasFallback =
                usingFallbackWaypoint;

            InvalidateTemporaryWaypoint();

            if (!wasFallback &&
                TryCreateFallbackWaypoint(
                    avoidedPosition))
            {
                WaypointDebugLog(
                    "Normal SetDestination başarısız. " +
                    "Fallback waypoint deneniyor."
                );

                StartWaypointMovementTracking();

                agent.isStopped = false;

                if (agent.SetDestination(
                        temporaryWaypoint))
                {
                    return;
                }

                WaypointDebugLog(
                    "Fallback SetDestination de başarısız."
                );

                InvalidateTemporaryWaypoint();
            }

            return;
        }
    }

    // =========================================================
    // FALLBACK WAYPOINT
    // =========================================================

    private bool TryCreateFallbackWaypoint(
        Vector3 realTargetPosition)
    {
        if (!useFallbackWaypoints)
            return false;

        if (waypointManager == null)
        {
            waypointManager =
                FindFirstObjectByType<BotWaypointManager>();
        }

        if (waypointManager == null)
        {
            WaypointDebugLog(
                "BotWaypointManager bulunamadı."
            );

            return false;
        }

        if (!agent.isOnNavMesh)
            return false;

        if (!waypointManager.TryGetFallbackWaypoint(
                transform.position,
                realTargetPosition,
                agent,
                out Vector3 fallbackPosition))
        {
            return false;
        }

        temporaryWaypoint =
            fallbackPosition;

        hasTemporaryWaypoint = true;

        usingFallbackWaypoint = true;

        return true;
    }

    // =========================================================
    // NORMAL TEMPORARY WAYPOINT
    // =========================================================

    private bool CreateTemporaryWaypoint(
        Vector3 targetPosition)
    {
        Vector3 toTarget =
            targetPosition -
            transform.position;

        toTarget.y = 0f;

        if (toTarget.sqrMagnitude < 0.01f)
        {
            WaypointDebugLog(
                "CreateTemporaryWaypoint: Hedef yönü çok kısa."
            );

            return false;
        }

        toTarget.Normalize();

        Vector3 right =
            Vector3.Cross(
                Vector3.up,
                toTarget
            ).normalized;

        Vector3 left =
            -right;

        bool obstacleForward =
            Physics.Raycast(
                transform.position,
                toTarget,
                waypointObstacleCheckDistance,
                waypointObstacleLayers,
                QueryTriggerInteraction.Ignore
            );

        bool obstacleRight =
            Physics.Raycast(
                transform.position,
                right,
                waypointWallCheckDistance,
                waypointObstacleLayers,
                QueryTriggerInteraction.Ignore
            );

        bool obstacleLeft =
            Physics.Raycast(
                transform.position,
                left,
                waypointWallCheckDistance,
                waypointObstacleLayers,
                QueryTriggerInteraction.Ignore
            );

        if (!obstacleForward)
        {
            Vector3 forwardWaypoint =
                transform.position +
                toTarget *
                waypointDistance;

            if (TrySetWaypoint(
                    forwardWaypoint,
                    targetPosition))
            {
                usingFallbackWaypoint = false;

                WaypointDebugLog(
                    "Forward waypoint kabul edildi."
                );

                return true;
            }
        }

        if (!obstacleRight)
        {
            Vector3 rightWaypoint =
                transform.position +
                toTarget *
                waypointDistance +
                right *
                waypointSideOffset;

            if (TrySetWaypoint(
                    rightWaypoint,
                    targetPosition))
            {
                usingFallbackWaypoint = false;

                WaypointDebugLog(
                    "Right waypoint kabul edildi."
                );

                return true;
            }
        }

        if (!obstacleLeft)
        {
            Vector3 leftWaypoint =
                transform.position +
                toTarget *
                waypointDistance +
                left *
                waypointSideOffset;

            if (TrySetWaypoint(
                    leftWaypoint,
                    targetPosition))
            {
                usingFallbackWaypoint = false;

                WaypointDebugLog(
                    "Left waypoint kabul edildi."
                );

                return true;
            }
        }

        Vector3 rightAlternative =
            transform.position +
            toTarget *
            (waypointDistance * 0.7f) +
            right *
            (waypointSideOffset * 1.5f);

        if (TrySetWaypoint(
                rightAlternative,
                targetPosition))
        {
            usingFallbackWaypoint = false;

            WaypointDebugLog(
                "Right alternative waypoint kabul edildi."
            );

            return true;
        }

        Vector3 leftAlternative =
            transform.position +
            toTarget *
            (waypointDistance * 0.7f) +
            left *
            (waypointSideOffset * 1.5f);

        if (TrySetWaypoint(
                leftAlternative,
                targetPosition))
        {
            usingFallbackWaypoint = false;

            WaypointDebugLog(
                "Left alternative waypoint kabul edildi."
            );

            return true;
        }

        WaypointDebugLog(
            "TÜM NORMAL WAYPOINT ADAYLARI REDDEDİLDİ."
        );

        return false;
    }

    // =========================================================
    // SET WAYPOINT
    // =========================================================

    private bool TrySetWaypoint(
        Vector3 desiredPosition,
        Vector3 realTargetPosition)
    {
        if (!agent.isOnNavMesh)
        {
            WaypointDebugLog(
                "TrySetWaypoint: Agent NavMesh üzerinde değil."
            );

            return false;
        }

        if (!NavMesh.SamplePosition(
                desiredPosition,
                out NavMeshHit hit,
                waypointSampleRadius,
                NavMesh.AllAreas))
        {
            WaypointDebugLog(
                "SamplePosition BAŞARISIZ. " +
                "Desired: " +
                desiredPosition
            );

            return false;
        }

        float sampleDistance =
            Vector3.Distance(
                desiredPosition,
                hit.position
            );

        if (sampleDistance >
            waypointSampleRadius)
        {
            WaypointDebugLog(
                "SamplePosition noktadan fazla uzak. " +
                "Mesafe: " +
                sampleDistance.ToString("F2")
            );

            return false;
        }

        reusableNavMeshPath.ClearCorners();

        bool pathCalculated =
            NavMesh.CalculatePath(
                transform.position,
                hit.position,
                NavMesh.AllAreas,
                reusableNavMeshPath
            );

        if (!pathCalculated)
        {
            WaypointDebugLog(
                "NavMesh.CalculatePath BAŞARISIZ."
            );

            return false;
        }

        if (reusableNavMeshPath.status !=
            NavMeshPathStatus.PathComplete)
        {
            WaypointDebugLog(
                "Waypoint PathComplete değil. " +
                "Status: " +
                reusableNavMeshPath.status
            );

            return false;
        }

        if (reusableNavMeshPath.corners == null ||
            reusableNavMeshPath.corners.Length == 0)
        {
            WaypointDebugLog(
                "Waypoint path corner bulunamadı."
            );

            return false;
        }

        if (!IsWaypointClear(
                hit.position))
        {
            WaypointDebugLog(
                "Waypoint CLEARANCE kontrolünde elendi."
            );

            return false;
        }

        Vector3 currentToTarget =
            realTargetPosition -
            transform.position;

        Vector3 waypointToTarget =
            realTargetPosition -
            hit.position;

        currentToTarget.y = 0f;
        waypointToTarget.y = 0f;

        if (currentToTarget.sqrMagnitude > 0.01f &&
            waypointToTarget.sqrMagnitude > 0.01f)
        {
            float currentDistance =
                currentToTarget.magnitude;

            float waypointDistanceToTarget =
                waypointToTarget.magnitude;

            float maximumAllowedDistance =
                currentDistance + 1.5f;

            if (waypointDistanceToTarget >
                maximumAllowedDistance)
            {
                WaypointDebugLog(
                    "Waypoint gerçek hedeften fazla uzak. " +
                    "Mevcut hedef mesafesi: " +
                    currentDistance.ToString("F2") +
                    " | Waypoint hedef mesafesi: " +
                    waypointDistanceToTarget.ToString("F2")
                );

                return false;
            }
        }

        temporaryWaypoint =
            hit.position;

        hasTemporaryWaypoint = true;

        usingFallbackWaypoint = false;

        return true;
    }

    // =========================================================
    // WAYPOINT CLEARANCE
    // =========================================================

    private bool IsWaypointClear(
        Vector3 waypoint)
    {
        float radius =
            agent.radius +
            waypointMinimumClearance;

        float height =
            Mathf.Max(
                agent.height,
                radius * 2f
            );

        Vector3 bottom =
            waypoint +
            Vector3.up *
            radius;

        Vector3 top =
            waypoint +
            Vector3.up *
            (height - radius);

        int colliderCount =
            Physics.OverlapCapsuleNonAlloc(
                bottom,
                top,
                radius,
                waypointClearanceResults,
                waypointObstacleLayers,
                QueryTriggerInteraction.Ignore
            );

        for (int i = 0;
             i < colliderCount;
             i++)
        {
            Collider col =
                waypointClearanceResults[i];

            if (col == null)
                continue;

            if (col.transform.root ==
                transform.root)
            {
                continue;
            }

            BotMovement otherBot =
                col.GetComponentInParent<BotMovement>();

            if (otherBot != null)
                continue;

            WaypointDebugLog(
                "Clearance engeli bulundu: " +
                col.name +
                " | Layer: " +
                LayerMask.LayerToName(
                    col.gameObject.layer
                ) +
                " | Tag: " +
                col.tag +
                " | Position: " +
                col.transform.position
            );

            return false;
        }

        return true;
    }

    // =========================================================
    // WAYPOINT MOVEMENT TRACKING
    // =========================================================

    private void StartWaypointMovementTracking()
    {
        waypointStuckTimer = 0f;

        waypointLastPosition =
            transform.position;

        waypointHasLastPosition = true;
    }

    // =========================================================
    // WAYPOINT STUCK
    // =========================================================

    private bool CheckTemporaryWaypointStuck()
    {
        if (!hasTemporaryWaypoint)
            return false;

        if (!agent.isOnNavMesh)
            return false;

        if (agent.pathPending)
            return false;

        if (!waypointHasLastPosition)
        {
            StartWaypointMovementTracking();

            return false;
        }

        Vector3 currentPosition =
            transform.position;

        float movement =
            Vector3.Distance(
                currentPosition,
                waypointLastPosition
            );

        if (movement >=
            waypointMinimumMovement)
        {
            waypointLastPosition =
                currentPosition;

            waypointStuckTimer = 0f;

            return false;
        }

        waypointStuckTimer +=
            Time.deltaTime;

        if (waypointStuckTimer >=
            waypointStuckCheckTime)
        {
            WaypointDebugLog(
                "================ STUCK ================" +
                "\nPosition: " +
                transform.position +
                "\nWaypoint: " +
                temporaryWaypoint +
                "\nFallback: " +
                usingFallbackWaypoint +
                "\nVelocity: " +
                agent.velocity +
                "\nVelocity Magnitude: " +
                agent.velocity.magnitude.ToString("F3") +
                "\nRemaining Distance: " +
                agent.remainingDistance.ToString("F3") +
                "\nStopping Distance: " +
                agent.stoppingDistance.ToString("F3") +
                "\nHas Path: " +
                agent.hasPath +
                "\nPath Pending: " +
                agent.pathPending +
                "\nPath Status: " +
                agent.pathStatus +
                "\nIs Stopped: " +
                agent.isStopped +
                "\nIs On NavMesh: " +
                agent.isOnNavMesh +
                "\n========================================"
            );

            waypointHasLastPosition = false;

            return true;
        }

        return false;
    }

    // =========================================================
    // INVALIDATE WAYPOINT
    // =========================================================

    private void InvalidateTemporaryWaypoint()
    {
        hasTemporaryWaypoint = false;

        usingFallbackWaypoint = false;

        waypointStuckTimer = 0f;

        waypointHasLastPosition = false;
    }

    // =========================================================
    // WAYPOINT DEBUG
    // =========================================================

    private void WaypointDebugLog(
        string message)
    {
        if (!waypointDebug)
            return;

        if (message ==
                lastWaypointDebugMessage &&
            Time.time -
                lastWaypointDebugTime < 1f)
        {
            return;
        }

        lastWaypointDebugMessage =
            message;

        lastWaypointDebugTime =
            Time.time;

        Debug.Log(
            "[Waypoint] " +
            gameObject.name +
            " → " +
            message
        );
    }

    // =========================================================
    // DISTRIBUTED APPROACH POSITION
    // =========================================================

    private Vector3 GetDistributedApproachPosition(
        Vector3 targetPosition)
    {
        nearbyBotsCache.Clear();

        float maxDistance =
            approachOffsetDistance * 4f;

        float maxDistanceSqr =
            maxDistance *
            maxDistance;

        foreach (BotMovement bot in allBots)
        {
            if (bot == null)
                continue;

            if (!bot.CompareTag("Bot"))
                continue;

            if (bot.agent == null ||
                !bot.agent.enabled ||
                !bot.agent.isOnNavMesh)
            {
                continue;
            }

            Vector3 difference =
                bot.transform.position -
                targetPosition;

            difference.y = 0f;

            if (difference.sqrMagnitude >
                maxDistanceSqr)
            {
                continue;
            }

            nearbyBotsCache.Add(bot);
        }

        if (!nearbyBotsCache.Contains(this))
        {
            nearbyBotsCache.Add(this);
        }

        nearbyBotsCache.Sort(
            CompareBotNames
        );

        int index =
            nearbyBotsCache.IndexOf(this);

        if (index < 0)
            index = 0;

        int slot;

        if (index == 0)
        {
            slot = 0;
        }
        else
        {
            int distanceFromCenter =
                (index + 1) / 2;

            if (index % 2 == 1)
            {
                slot =
                    distanceFromCenter;
            }
            else
            {
                slot =
                    -distanceFromCenter;
            }
        }

        Vector3 direction =
            transform.position -
            targetPosition;

        direction.y = 0f;

        if (direction.sqrMagnitude < 0.01f)
        {
            direction =
                -transform.forward;
        }

        direction.Normalize();

        Vector3 side =
            Vector3.Cross(
                Vector3.up,
                direction
            ).normalized;

        Vector3 offset =
            side *
            slot *
            approachOffsetDistance;

        Vector3 desiredPosition =
            targetPosition +
            offset;

        if (NavMesh.SamplePosition(
                desiredPosition,
                out NavMeshHit hit,
                approachOffsetDistance + 1f,
                NavMesh.AllAreas))
        {
            return hit.position;
        }

        return targetPosition;
    }

    // =========================================================
    // BOT COMPARISON
    // =========================================================

    private static int CompareBotNames(
        BotMovement a,
        BotMovement b)
    {
        if (a == null)
            return b == null ? 0 : -1;

        if (b == null)
            return 1;

        return string.Compare(
            a.gameObject.name,
            b.gameObject.name,
            System.StringComparison.Ordinal
        );
    }

    // =========================================================
    // BOT AVOIDANCE
    // =========================================================

    private Vector3 GetBotAvoidedTargetPosition(
        Vector3 targetPosition)
    {
        if (avoidanceCacheValid)
        {
            Vector3 targetDifference =
                targetPosition -
                cachedAvoidanceTargetPosition;

            targetDifference.y = 0f;

            if (targetDifference.sqrMagnitude >
                1f)
            {
                avoidanceCacheValid = false;
            }
        }

        if (avoidanceCacheValid &&
            Time.time < nextAvoidanceCheckTime)
        {
            return cachedAvoidedTargetPosition;
        }

        nextAvoidanceCheckTime =
            Time.time +
            avoidanceCheckInterval;

        cachedAvoidanceTargetPosition =
            targetPosition;

        int colliderCount =
            Physics.OverlapSphereNonAlloc(
                transform.position,
                botAvoidanceDistance,
                avoidanceResults,
                Physics.DefaultRaycastLayers,
                QueryTriggerInteraction.Ignore
            );

        Vector3 avoidance =
            Vector3.zero;

        Vector3 forward =
            transform.forward;

        forward.y = 0f;

        if (forward.sqrMagnitude >= 0.01f)
        {
            forward.Normalize();

            for (int i = 0;
                 i < colliderCount;
                 i++)
            {
                Collider col =
                    avoidanceResults[i];

                if (col == null)
                    continue;

                BotMovement otherBot =
                    col.GetComponentInParent<BotMovement>();

                if (otherBot == null)
                    continue;

                if (otherBot == this)
                    continue;

                if (!otherBot.CompareTag("Bot"))
                    continue;

                Vector3 toOther =
                    otherBot.transform.position -
                    transform.position;

                toOther.y = 0f;

                float distanceSqr =
                    toOther.sqrMagnitude;

                if (distanceSqr < 0.0001f)
                    continue;

                float distance =
                    Mathf.Sqrt(
                        distanceSqr
                    );

                toOther /= distance;

                float dot =
                    Vector3.Dot(
                        forward,
                        toOther
                    );

                if (dot < 0.4f)
                    continue;

                float strength =
                    1f -
                    Mathf.Clamp01(
                        distance /
                        botAvoidanceDistance
                    );

                Vector3 side =
                    Vector3.Cross(
                        Vector3.up,
                        toOther
                    ).normalized;

                float sideDot =
                    Vector3.Dot(
                        side,
                        transform.right
                    );

                if (sideDot < 0f)
                {
                    side = -side;
                }

                avoidance +=
                    side *
                    strength;
            }
        }

        if (avoidance.sqrMagnitude >
            0.001f)
        {
            avoidance.Normalize();

            targetPosition +=
                avoidance *
                botAvoidanceStrength;
        }

        cachedAvoidedTargetPosition =
            targetPosition;

        avoidanceCacheValid = true;

        return cachedAvoidedTargetPosition;
    }

    // =========================================================
    // INVALIDATE BOT AVOIDANCE CACHE
    // =========================================================

    private void InvalidateAvoidanceCache()
    {
        avoidanceCacheValid = false;

        nextAvoidanceCheckTime = 0f;

        cachedAvoidedTargetPosition =
            transform.position;

        cachedAvoidanceTargetPosition =
            transform.position;
    }

    // =========================================================
    // COMBAT
    // =========================================================

    private void HandleCombat()
    {
        if (!agent.isOnNavMesh)
            return;

        Transform target = perception.activeTarget;

        if (target == null)
            return;

        Vector3 toTarget = target.position - transform.position;
        toTarget.y = 0f;

        if (toTarget.sqrMagnitude < 0.01f)
            return;

        toTarget.Normalize();

        // Düşmana göre sağ ve sol hareket yönleri.
        Vector3 right = Vector3.Cross(Vector3.up, toTarget).normalized;

        // Kaçınma sistemi aktif mi?
        bool dodging =
            botReaction != null &&
            botReaction.IsDodging();

        Vector3 movementDirection = Vector3.zero;
        float movementSpeed = combatMoveSpeed;

        if (dodging)
        {
            // Mermiden kaçınma, normal savaş hareketinden önceliklidir.
            switch (botReaction.CurrentDodgeDirection)
            {
                case BotReaction.DodgeDirection.Left:
                    movementDirection = -transform.right;
                    break;

                case BotReaction.DodgeDirection.Right:
                    movementDirection = transform.right;
                    break;

                case BotReaction.DodgeDirection.Forward:
                    movementDirection = transform.forward;
                    break;

                case BotReaction.DodgeDirection.Backward:
                    movementDirection = -transform.forward;
                    break;
            }

            movementDirection.y = 0f;

            if (movementDirection.sqrMagnitude > 0.001f)
            {
                movementDirection.Normalize();

                // Kaçınma hızını normal savaş hareketinden biraz yüksek tut.
                movementSpeed = combatMoveSpeed * 1.5f;
            }
            else
            {
                dodging = false;
            }
        }

        if (!dodging)
        {
            // Mevcut sağ-sol savaş hareketi korunuyor.
            combatDecisionTimer -= Time.deltaTime;

            if (combatDecisionTimer <= 0f)
            {
                combatDecisionTimer = Random.Range(
                    combatMoveDurationMin,
                    combatMoveDurationMax
                );

                combatDirection = Random.value < 0.5f ? -1f : 1f;

                if (IsCombatSideBlocked(combatDirection))
                {
                    float oppositeDirection = -combatDirection;

                    if (!IsCombatSideBlocked(oppositeDirection))
                    {
                        combatDirection = oppositeDirection;
                    }
                }
            }

            movementDirection = right * combatDirection;
            movementSpeed = combatMoveSpeed;
        }

        // Duvar kontrolü: bot engelin içine doğru hareket etmesin.
        if (movementDirection.sqrMagnitude > 0.001f)
        {
            Vector3 checkOrigin = transform.position + Vector3.up * 0.8f;

            bool blocked = Physics.Raycast(
                checkOrigin,
                movementDirection,
                0.65f,
                combatObstacleLayers,
                QueryTriggerInteraction.Ignore
            );

            if (blocked)
            {
                if (dodging)
                {
                    // Kaçınma yönü kapalıysa mevcut savaş hareketine geri dön.
                    movementDirection = right * combatDirection;
                    movementSpeed = combatMoveSpeed;
                }
                else
                {
                    movementDirection = Vector3.zero;
                }
            }
        }

        // NavMeshAgent rotasyonu otomatik değiştirmiyor.
        // Böylece hareket yönü değişirken nişan alma rotasyonunu
        // BotRotation yönetmeye devam edebilir.
        agent.isStopped = true;
        agent.ResetPath();

        Vector3 movement =
            movementDirection * movementSpeed * Time.deltaTime;

        if (characterAnimator != null)
        {
            Vector3 localMovement =
                transform.InverseTransformDirection(movement);

            characterAnimator.SetMovementDirection(
                localMovement.normalized
            );
        }

        agent.isStopped = false;
        agent.Move(movement);
    }

    // =========================================================
    // GIZMOS
    // =========================================================

    private void OnDrawGizmosSelected()
    {
        if (combatLeftSensor != null)
        {
            Gizmos.DrawWireSphere(
                combatLeftSensor.position,
                combatSensorRadius
            );
        }

        if (combatRightSensor != null)
        {
            Gizmos.DrawWireSphere(
                combatRightSensor.position,
                combatSensorRadius
            );
        }
    }

    // =========================================================
    // OLD COMBAT SYSTEM
    // =========================================================

    private float GetCombatSideDistance(
        Vector3 direction)
    {
        direction.y = 0f;

        if (direction.sqrMagnitude < 0.01f)
            return combatSideCheckDistance;

        direction.Normalize();

        Vector3 origin =
            transform.position +
            Vector3.up * 1f;

        if (Physics.Raycast(
                origin,
                direction,
                out RaycastHit hit,
                combatSideCheckDistance,
                combatObstacleLayers,
                QueryTriggerInteraction.Ignore))
        {
            return hit.distance;
        }

        return combatSideCheckDistance;
    }

    private bool IsCombatMovementBlocked(
        Vector3 direction)
    {
        if (direction.sqrMagnitude < 0.01f)
            return true;

        direction.y = 0f;

        direction.Normalize();

        Vector3 origin =
            transform.position +
            Vector3.up * 0.8f;

        return Physics.Raycast(
            origin,
            direction,
            combatSideCheckDistance,
            combatObstacleLayers,
            QueryTriggerInteraction.Ignore
        );
    }

    // =========================================================
    // INVESTIGATE
    // =========================================================

    private void HandleInvestigate()
    {
        if (!agent.isOnNavMesh)
            return;

        if (!botAI.hasLastSeenPosition)
        {
            botAI.currentState =
                BotAI.AIState.Idle;

            StopMovement();

            return;
        }

        if (!hasInvestigateApproachPosition)
        {
            CreateInvestigateApproachPosition();

            if (!hasInvestigateApproachPosition)
                return;
        }

        investigatePathRecalculateTimer -=
            Time.deltaTime;

        Vector3 targetPosition =
            GetBotAvoidedTargetPosition(
                investigateApproachPosition
            );

        Vector3 difference =
            targetPosition -
            investigateLastTargetPosition;

        difference.y = 0f;

        bool targetChanged =
            difference.sqrMagnitude >
            0.25f;

        bool pathNeedsRecalculation =
            !investigateHasPathTarget ||
            targetChanged ||
            investigatePathRecalculateTimer <= 0f ||
            !agent.hasPath ||
            agent.pathStatus !=
                NavMeshPathStatus.PathComplete;

        if (pathNeedsRecalculation)
        {
            if (!TryGetReachableNavMeshPoint(
                    targetPosition,
                    2f,
                    out Vector3 reachableTarget))
            {
                reachableTarget =
                    investigateApproachPosition;

                if (!TryGetReachableNavMeshPoint(
                        reachableTarget,
                        2f,
                        out reachableTarget))
                {
                    botAI.hasLastSeenPosition =
                        false;

                    hasInvestigateApproachPosition =
                        false;

                    hasApproachTargetPosition =
                        false;

                    ResetInvestigatePathCache();

                    botAI.currentState =
                        BotAI.AIState.Idle;

                    StopMovement();

                    return;
                }
            }

            agent.isStopped = false;

            bool destinationAccepted =
                agent.SetDestination(
                    reachableTarget
                );

            if (!destinationAccepted)
            {
                botAI.hasLastSeenPosition =
                    false;

                hasInvestigateApproachPosition =
                    false;

                hasApproachTargetPosition =
                    false;

                ResetInvestigatePathCache();

                botAI.currentState =
                    BotAI.AIState.Idle;

                StopMovement();

                return;
            }

            investigateLastTargetPosition =
                targetPosition;

            investigatePathRecalculateTimer =
                investigatePathRecalculateInterval;

            investigateHasPathTarget =
                true;
        }

        if (!agent.pathPending &&
            agent.remainingDistance <=
            agent.stoppingDistance + 0.2f)
        {
            agent.isStopped = true;

            agent.ResetPath();

            botAI.hasLastSeenPosition =
                false;

            hasInvestigateApproachPosition =
                false;

            hasApproachTargetPosition =
                false;

            ResetInvestigatePathCache();

            botAI.currentState =
                BotAI.AIState.Idle;
        }
    }

    // =========================================================
    // INVESTIGATE CACHE RESET
    // =========================================================

    private void ResetInvestigatePathCache()
    {
        investigatePathRecalculateTimer = 0f;

        investigateLastTargetPosition =
            Vector3.zero;

        investigateHasPathTarget = false;

        InvalidateAvoidanceCache();
    }

    // =========================================================
    // STOP MOVEMENT
    // =========================================================

    private void StopMovement()
    {
        if (!agent.isOnNavMesh)
            return;

        agent.isStopped = true;

        agent.ResetPath();

        InvalidateTemporaryWaypoint();

        InvalidateAvoidanceCache();
    }

    // =========================================================
    // TEAM STRATEGY
    // =========================================================

    private void FindTeamStrategy()
    {
        TeamStrategy[] strategies =
            FindObjectsByType<TeamStrategy>(
                FindObjectsSortMode.None
            );

        foreach (TeamStrategy strategy in strategies)
        {
            if (strategy == null)
                continue;

            if (strategy.team !=
                perception.currentTeam)
            {
                continue;
            }

            teamStrategy =
                strategy;

            return;
        }

        Debug.LogWarning(
            gameObject.name +
            " için TeamStrategy bulunamadı!"
        );
    }
}
Editor is loading...
Leave a Comment