[Player][Interactions] Refactor common settings to be in the Game Settings SO. Update follow paramters and pathfinding for Pulver

This commit is contained in:
Michal Pikulski
2025-09-04 11:12:19 +02:00
parent 65d8be6cf2
commit 5d395ba4f4
8 changed files with 185 additions and 103 deletions

View File

@@ -5,7 +5,6 @@ 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
@@ -15,28 +14,30 @@ public class FollowerController : MonoBehaviour
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 const float followerSpeedMultiplier = 1.2f;
private float followerMaxSpeed = 6f; // Default, will be set from player
private float defaultFollowerMaxSpeed = 6f; // Default fallback value
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()
{
@@ -69,60 +70,30 @@ public class FollowerController : MonoBehaviour
FindPlayerReference();
}
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 * followerSpeedMultiplier;
}
}
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
return;
}
if (isManualFollowing)
{
// 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
lastMoveDir = moveDir;
}
else
{
moveDir = lastMoveDir; // Use last direction if stationary
moveDir = lastMoveDir;
}
// Calculate target point behind player
targetPoint = playerPos - moveDir * followDistance;
targetPoint.z = 0; // For 2D
// Only disable aiPath if in manual mode
// Use GameSettings for followDistance
targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance;
targetPoint.z = 0;
if (aiPath != null)
{
aiPath.enabled = false;
@@ -134,73 +105,93 @@ public class FollowerController : MonoBehaviour
{
if (playerTransform == null)
{
// Try to reacquire reference if lost
FindPlayerReference();
if (playerTransform == null)
return; // Still missing, skip update
return;
}
timer += Time.deltaTime;
if (timer >= followUpdateInterval)
if (timer >= GameManager.Instance.FollowUpdateInterval)
{
timer = 0f;
UpdateFollowTarget();
}
// Manual movement logic with acceleration/deceleration and stop threshold
if (isManualFollowing)
{
// 2D distance calculation (ignore z)
Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
Vector2 target2D = new Vector2(targetPoint.x, targetPoint.y);
float dist = Vector2.Distance(current2D, target2D);
if (dist > stopThreshold)
float minSpeed = followerMaxSpeed * 0.3f;
float lerpFactor = GameManager.Instance.ManualMoveSmooth * Time.deltaTime;
float targetSpeed = 0f;
if (dist > GameManager.Instance.StopThreshold)
{
float desiredSpeed = followerMaxSpeed;
if (dist > thresholdFar)
if (dist > GameManager.Instance.ThresholdFar)
{
// Accelerate to follower's max speed
desiredSpeed = Mathf.Min(currentSpeed + acceleration * Time.deltaTime, followerMaxSpeed);
targetSpeed = followerMaxSpeed;
}
else if (dist <= thresholdNear && dist > stopThreshold)
else if (dist > GameManager.Instance.ThresholdNear && dist <= GameManager.Instance.ThresholdFar)
{
// Decelerate as approaching target, but keep moving until stopThreshold
desiredSpeed = Mathf.Max(currentSpeed - deceleration * Time.deltaTime, 0.5f);
targetSpeed = followerMaxSpeed;
}
else
else if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
{
// Maintain follower's max speed
desiredSpeed = followerMaxSpeed;
targetSpeed = minSpeed;
}
currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, lerpFactor);
if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
{
currentSpeed = Mathf.Max(currentSpeed, minSpeed);
}
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 (isReturningToPlayer && aiPath != null && aiPath.enabled && playerTransform != null)
{
aiPath.destination = playerTransform.position;
}
if (animator != null)
{
float normalizedSpeed = 0f;
if (isManualFollowing)
{
// Use currentSpeed for manual following
normalizedSpeed = currentSpeed / followerMaxSpeed;
}
else if (aiPath != null)
{
// Use aiPath velocity for pathfinding mode
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)
{
@@ -213,11 +204,6 @@ public class FollowerController : MonoBehaviour
}
}
public delegate void FollowerPickupHandler();
public event FollowerPickupHandler OnPickupArrived;
public event FollowerPickupHandler OnPickupReturned;
private Coroutine pickupCoroutine;
// Command follower to go to a specific point and return to player
public void GoToPointAndReturn(Vector2 itemPosition, Transform playerTransform)
{
@@ -237,6 +223,16 @@ public class FollowerController : MonoBehaviour
{
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
{
@@ -249,6 +245,7 @@ public class FollowerController : MonoBehaviour
private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform)
{
isManualFollowing = false;
isReturningToPlayer = false;
if (aiPath != null)
{
aiPath.enabled = true;
@@ -256,7 +253,7 @@ public class FollowerController : MonoBehaviour
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)) > stopThreshold)
while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(itemPosition.x, itemPosition.y)) > GameManager.Instance.StopThreshold)
{
yield return null;
}
@@ -264,7 +261,6 @@ public class FollowerController : MonoBehaviour
// Set held item and destroy pickup
if (heldObjectRenderer != null)
{
// Find Pickup object at itemPosition
Collider2D[] hits = Physics2D.OverlapCircleAll(itemPosition, 0.2f);
foreach (var hit in hits)
{
@@ -284,11 +280,13 @@ public class FollowerController : MonoBehaviour
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)) > stopThreshold)
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;