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