312 lines
10 KiB
C#
312 lines
10 KiB
C#
using UnityEngine;
|
|
using Pathfinding;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
public class FollowerController : MonoBehaviour
|
|
{
|
|
[Header("Follower Settings")]
|
|
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 isManualFollowing = true; // Default to manual following
|
|
private Vector3 lastMoveDir = Vector3.right; // Default direction
|
|
private float currentSpeed = 0f;
|
|
|
|
private Animator animator;
|
|
private Transform artTransform;
|
|
|
|
[Header("Held Item")]
|
|
public PickupItemData currentlyHeldItem;
|
|
public SpriteRenderer heldObjectRenderer;
|
|
public float heldIconDisplayHeight = 2.0f; // Desired height for held item icon
|
|
|
|
private bool isReturningToPlayer = false; // Track if follower is returning after pickup
|
|
|
|
// Speed fields for follower
|
|
private float playerMaxSpeed = 5f;
|
|
private float followerMaxSpeed = 6f;
|
|
private float defaultFollowerMaxSpeed = 6f;
|
|
|
|
// Pickup events
|
|
public delegate void FollowerPickupHandler();
|
|
public event FollowerPickupHandler OnPickupArrived;
|
|
public event FollowerPickupHandler OnPickupReturned;
|
|
private Coroutine pickupCoroutine;
|
|
|
|
void Awake()
|
|
{
|
|
aiPath = GetComponent<AIPath>();
|
|
// Find art prefab and animator
|
|
artTransform = transform.Find("CharacterArt");
|
|
if (artTransform != null)
|
|
{
|
|
animator = artTransform.GetComponent<Animator>();
|
|
}
|
|
else
|
|
{
|
|
animator = GetComponentInChildren<Animator>(); // fallback
|
|
}
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
SceneManager.sceneLoaded += OnSceneLoaded;
|
|
FindPlayerReference();
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
SceneManager.sceneLoaded -= OnSceneLoaded;
|
|
}
|
|
|
|
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
|
{
|
|
FindPlayerReference();
|
|
}
|
|
|
|
void UpdateFollowTarget()
|
|
{
|
|
if (playerTransform == null)
|
|
{
|
|
FindPlayerReference();
|
|
if (playerTransform == null)
|
|
return;
|
|
}
|
|
if (isManualFollowing)
|
|
{
|
|
Vector3 playerPos = playerTransform.position;
|
|
Vector3 moveDir = Vector3.zero;
|
|
if (playerAIPath != null && playerAIPath.velocity.magnitude > 0.01f)
|
|
{
|
|
moveDir = playerAIPath.velocity.normalized;
|
|
lastMoveDir = moveDir;
|
|
}
|
|
else
|
|
{
|
|
moveDir = lastMoveDir;
|
|
}
|
|
// Use GameSettings for followDistance
|
|
targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance;
|
|
targetPoint.z = 0;
|
|
if (aiPath != null)
|
|
{
|
|
aiPath.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (playerTransform == null)
|
|
{
|
|
FindPlayerReference();
|
|
if (playerTransform == null)
|
|
return;
|
|
}
|
|
|
|
timer += Time.deltaTime;
|
|
if (timer >= GameManager.Instance.FollowUpdateInterval)
|
|
{
|
|
timer = 0f;
|
|
UpdateFollowTarget();
|
|
}
|
|
|
|
if (isManualFollowing)
|
|
{
|
|
Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
|
|
Vector2 target2D = new Vector2(targetPoint.x, targetPoint.y);
|
|
float dist = Vector2.Distance(current2D, target2D);
|
|
float minSpeed = followerMaxSpeed * 0.3f;
|
|
float lerpFactor = GameManager.Instance.ManualMoveSmooth * Time.deltaTime;
|
|
float targetSpeed = 0f;
|
|
if (dist > GameManager.Instance.StopThreshold)
|
|
{
|
|
if (dist > GameManager.Instance.ThresholdFar)
|
|
{
|
|
targetSpeed = followerMaxSpeed;
|
|
}
|
|
else if (dist > GameManager.Instance.ThresholdNear && dist <= GameManager.Instance.ThresholdFar)
|
|
{
|
|
targetSpeed = followerMaxSpeed;
|
|
}
|
|
else if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
|
|
{
|
|
targetSpeed = minSpeed;
|
|
}
|
|
currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, lerpFactor);
|
|
if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
|
|
{
|
|
currentSpeed = Mathf.Max(currentSpeed, minSpeed);
|
|
}
|
|
Vector3 dir = (targetPoint - transform.position).normalized;
|
|
transform.position += dir * currentSpeed * Time.deltaTime;
|
|
}
|
|
else
|
|
{
|
|
currentSpeed = 0f;
|
|
}
|
|
}
|
|
if (isReturningToPlayer && aiPath != null && aiPath.enabled && playerTransform != null)
|
|
{
|
|
aiPath.destination = playerTransform.position;
|
|
}
|
|
if (animator != null)
|
|
{
|
|
float normalizedSpeed = 0f;
|
|
if (isManualFollowing)
|
|
{
|
|
normalizedSpeed = currentSpeed / followerMaxSpeed;
|
|
}
|
|
else if (aiPath != null)
|
|
{
|
|
normalizedSpeed = aiPath.velocity.magnitude / followerMaxSpeed;
|
|
}
|
|
animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed));
|
|
}
|
|
}
|
|
|
|
void FindPlayerReference()
|
|
{
|
|
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
|
if (playerObj != null)
|
|
{
|
|
playerTransform = playerObj.transform;
|
|
playerAIPath = playerObj.GetComponent<AIPath>();
|
|
if (playerAIPath != null)
|
|
{
|
|
playerMaxSpeed = playerAIPath.maxSpeed;
|
|
defaultFollowerMaxSpeed = playerMaxSpeed;
|
|
followerMaxSpeed = playerMaxSpeed * GameManager.Instance.FollowerSpeedMultiplier;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
playerTransform = null;
|
|
playerAIPath = null;
|
|
}
|
|
}
|
|
|
|
// Command follower to go to a specific point (pathfinding mode)
|
|
public void GoToPoint(Vector2 worldPosition)
|
|
{
|
|
isManualFollowing = false;
|
|
if (aiPath != null)
|
|
{
|
|
aiPath.enabled = true;
|
|
aiPath.maxSpeed = followerMaxSpeed;
|
|
aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0);
|
|
}
|
|
}
|
|
|
|
// Command follower to go to a specific point and return to player
|
|
public void GoToPointAndReturn(Vector2 itemPosition, Transform playerTransform)
|
|
{
|
|
if (pickupCoroutine != null)
|
|
StopCoroutine(pickupCoroutine);
|
|
if (aiPath != null)
|
|
aiPath.maxSpeed = followerMaxSpeed;
|
|
pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform));
|
|
}
|
|
|
|
public void SetHeldItem(PickupItemData itemData)
|
|
{
|
|
currentlyHeldItem = itemData;
|
|
if (heldObjectRenderer != null)
|
|
{
|
|
if (currentlyHeldItem != null && currentlyHeldItem.mapSprite != null)
|
|
{
|
|
heldObjectRenderer.sprite = currentlyHeldItem.mapSprite;
|
|
heldObjectRenderer.enabled = true;
|
|
// Scale held icon to fixed height, preserve aspect ratio
|
|
var sprite = currentlyHeldItem.mapSprite;
|
|
float spriteHeight = sprite.bounds.size.y;
|
|
float spriteWidth = sprite.bounds.size.x;
|
|
if (spriteHeight > 0f)
|
|
{
|
|
float scaleY = heldIconDisplayHeight / spriteHeight;
|
|
float scaleX = scaleY * (spriteWidth / spriteHeight);
|
|
heldObjectRenderer.transform.localScale = new Vector3(scaleX, scaleY, 1f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
heldObjectRenderer.sprite = null;
|
|
heldObjectRenderer.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform)
|
|
{
|
|
isManualFollowing = false;
|
|
isReturningToPlayer = false;
|
|
if (aiPath != null)
|
|
{
|
|
aiPath.enabled = true;
|
|
aiPath.maxSpeed = followerMaxSpeed;
|
|
aiPath.destination = new Vector3(itemPosition.x, itemPosition.y, 0);
|
|
}
|
|
// Wait until follower reaches item (2D distance)
|
|
while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(itemPosition.x, itemPosition.y)) > GameManager.Instance.StopThreshold)
|
|
{
|
|
yield return null;
|
|
}
|
|
OnPickupArrived?.Invoke();
|
|
// Set held item and destroy pickup
|
|
if (heldObjectRenderer != null)
|
|
{
|
|
Collider2D[] hits = Physics2D.OverlapCircleAll(itemPosition, 0.2f);
|
|
foreach (var hit in hits)
|
|
{
|
|
var pickup = hit.GetComponent<Pickup>();
|
|
if (pickup != null)
|
|
{
|
|
SetHeldItem(pickup.itemData);
|
|
GameObject.Destroy(pickup.gameObject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Wait briefly, then return to player
|
|
yield return new WaitForSeconds(0.2f);
|
|
if (aiPath != null && playerTransform != null)
|
|
{
|
|
aiPath.maxSpeed = followerMaxSpeed;
|
|
aiPath.destination = playerTransform.position;
|
|
}
|
|
isReturningToPlayer = true;
|
|
// Wait until follower returns to player (2D distance)
|
|
while (playerTransform != null && Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(playerTransform.position.x, playerTransform.position.y)) > GameManager.Instance.StopThreshold)
|
|
{
|
|
yield return null;
|
|
}
|
|
isReturningToPlayer = false;
|
|
OnPickupReturned?.Invoke();
|
|
// Reset follower speed to normal after pickup
|
|
followerMaxSpeed = defaultFollowerMaxSpeed;
|
|
if (aiPath != null)
|
|
aiPath.maxSpeed = followerMaxSpeed;
|
|
isManualFollowing = true;
|
|
if (aiPath != null)
|
|
aiPath.enabled = false;
|
|
pickupCoroutine = null;
|
|
}
|
|
|
|
void OnDrawGizmos()
|
|
{
|
|
if (debugDrawTarget && Application.isPlaying)
|
|
{
|
|
Gizmos.color = Color.cyan;
|
|
Gizmos.DrawSphere(targetPoint, 0.2f);
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawLine(transform.position, targetPoint);
|
|
}
|
|
}
|
|
}
|