using UnityEngine; using Pathfinding; using UnityEngine.SceneManagement; 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; private Animator animator; private Transform artTransform; void Awake() { aiPath = GetComponent(); // Find art prefab and animator artTransform = transform.Find("CharacterArt"); if (artTransform != null) { animator = artTransform.GetComponent(); } else { animator = GetComponentInChildren(); // fallback } } void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; FindPlayerReference(); } void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; } void OnSceneLoaded(Scene scene, LoadSceneMode mode) { FindPlayerReference(); } void FindPlayerReference() { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) { playerTransform = playerObj.transform; playerAIPath = playerObj.GetComponent(); if (playerAIPath != null) { playerMaxSpeed = playerAIPath.maxSpeed; } } else { playerTransform = null; playerAIPath = null; } } void Start() { FindPlayerReference(); } void UpdateFollowTarget() { if (playerTransform == null) { // Try to reacquire reference if lost FindPlayerReference(); if (playerTransform == null) return; // Still missing, skip update } // 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) { // Try to reacquire reference if lost FindPlayerReference(); if (playerTransform == null) return; // Still missing, skip update } 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; } } // Update animator speed parameter if (animator != null) { float normalizedSpeed = 0f; if (isManualFollowing) { // Use currentSpeed for manual following normalizedSpeed = currentSpeed / playerMaxSpeed; } else if (aiPath != null) { // Use aiPath velocity for pathfinding mode normalizedSpeed = aiPath.velocity.magnitude / playerMaxSpeed; } animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); } } // 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); } } }