2025-09-02 13:20:17 +02:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using Pathfinding;
|
|
|
|
|
|
|
|
|
|
|
|
public class FollowerController : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
[Header("Follower Settings")]
|
|
|
|
|
|
public float followDistance = 1.5f; // Desired distance behind player
|
|
|
|
|
|
public bool debugDrawTarget = true;
|
|
|
|
|
|
public float followUpdateInterval = 0.1f; // How often to update follow logic
|
|
|
|
|
|
public float manualMoveSmooth = 8f; // Smoothing factor for manual movement
|
|
|
|
|
|
|
|
|
|
|
|
private Transform playerTransform;
|
|
|
|
|
|
private AIPath playerAIPath;
|
|
|
|
|
|
private AIPath aiPath;
|
|
|
|
|
|
private Vector3 targetPoint;
|
|
|
|
|
|
private float timer;
|
|
|
|
|
|
private bool usePathfinding = false;
|
|
|
|
|
|
private bool isManualFollowing = true; // Default to manual following
|
|
|
|
|
|
private Vector3 lastMoveDir = Vector3.right; // Default direction
|
|
|
|
|
|
private float currentSpeed = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
[Header("Speed Settings")]
|
|
|
|
|
|
public float acceleration = 10f;
|
|
|
|
|
|
public float deceleration = 12f;
|
|
|
|
|
|
public float thresholdFar = 2.5f;
|
|
|
|
|
|
public float thresholdNear = 0.5f;
|
|
|
|
|
|
public float stopThreshold = 0.1f; // Stop moving when within this distance
|
|
|
|
|
|
private float playerMaxSpeed = 5f;
|
|
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
aiPath = GetComponent<AIPath>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Find player by tag
|
|
|
|
|
|
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
|
|
|
|
|
if (playerObj != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
playerTransform = playerObj.transform;
|
|
|
|
|
|
playerAIPath = playerObj.GetComponent<AIPath>();
|
|
|
|
|
|
// Fetch player's max speed
|
|
|
|
|
|
if (playerAIPath != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
playerMaxSpeed = playerAIPath.maxSpeed;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("FollowerController: Player object with tag 'Player' not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UpdateFollowTarget()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Determine direction: use player's velocity if available, else lastMoveDir
|
|
|
|
|
|
Vector3 playerPos = playerTransform.position;
|
|
|
|
|
|
Vector3 moveDir = Vector3.zero;
|
|
|
|
|
|
if (playerAIPath != null && playerAIPath.velocity.magnitude > 0.01f)
|
|
|
|
|
|
{
|
|
|
|
|
|
moveDir = playerAIPath.velocity.normalized;
|
|
|
|
|
|
lastMoveDir = moveDir; // Update last valid direction
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
moveDir = lastMoveDir; // Use last direction if stationary
|
|
|
|
|
|
}
|
|
|
|
|
|
// Calculate target point behind player
|
|
|
|
|
|
targetPoint = playerPos - moveDir * followDistance;
|
|
|
|
|
|
targetPoint.z = 0; // For 2D
|
|
|
|
|
|
|
|
|
|
|
|
// Always use manual following unless GoToPoint is called
|
|
|
|
|
|
isManualFollowing = true;
|
|
|
|
|
|
if (aiPath != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
aiPath.enabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (playerTransform == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
timer += Time.deltaTime;
|
|
|
|
|
|
if (timer >= followUpdateInterval)
|
|
|
|
|
|
{
|
|
|
|
|
|
timer = 0f;
|
|
|
|
|
|
UpdateFollowTarget();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Manual movement logic with acceleration/deceleration and stop threshold
|
|
|
|
|
|
if (isManualFollowing)
|
|
|
|
|
|
{
|
|
|
|
|
|
float dist = Vector3.Distance(transform.position, targetPoint);
|
|
|
|
|
|
if (dist > stopThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
float desiredSpeed = playerMaxSpeed;
|
|
|
|
|
|
if (dist > thresholdFar)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Accelerate to player's max speed
|
|
|
|
|
|
desiredSpeed = Mathf.Min(currentSpeed + acceleration * Time.deltaTime, playerMaxSpeed);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (dist <= thresholdNear && dist > stopThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Decelerate as approaching target, but keep moving until stopThreshold
|
|
|
|
|
|
desiredSpeed = Mathf.Max(currentSpeed - deceleration * Time.deltaTime, 0.5f);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Maintain player's max speed
|
|
|
|
|
|
desiredSpeed = playerMaxSpeed;
|
|
|
|
|
|
}
|
|
|
|
|
|
currentSpeed = desiredSpeed;
|
|
|
|
|
|
Vector3 dir = (targetPoint - transform.position).normalized;
|
|
|
|
|
|
transform.position += dir * currentSpeed * Time.deltaTime;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Stop moving when close enough
|
|
|
|
|
|
currentSpeed = 0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Command follower to go to a specific point (pathfinding mode)
|
|
|
|
|
|
public void GoToPoint(Vector2 worldPosition)
|
|
|
|
|
|
{
|
|
|
|
|
|
isManualFollowing = false;
|
|
|
|
|
|
if (aiPath != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
aiPath.enabled = true;
|
|
|
|
|
|
aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnDrawGizmos()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (debugDrawTarget && Application.isPlaying)
|
|
|
|
|
|
{
|
|
|
|
|
|
Gizmos.color = Color.cyan;
|
|
|
|
|
|
Gizmos.DrawSphere(targetPoint, 0.2f);
|
|
|
|
|
|
Gizmos.color = Color.yellow;
|
|
|
|
|
|
Gizmos.DrawLine(transform.position, targetPoint);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|