[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

@@ -14,3 +14,14 @@ MonoBehaviour:
m_EditorClassIdentifier:
playerStopDistance: 10
followerPickupDelay: 0.2
followDistance: 5
manualMoveSmooth: 2
thresholdFar: 10
thresholdNear: 7
stopThreshold: 0.5
moveSpeed: 15
stopDistance: 0.1
useRigidbody: 1
followUpdateInterval: 0.1
followerSpeedMultiplier: 1.2
heldIconDisplayHeight: 2

View File

@@ -10,15 +10,15 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 530025588511586483, guid: 68fe09242bd73f34bad304c509872d90, type: 3}
propertyPath: m_LocalScale.x
value: 5
value: 4.25
objectReference: {fileID: 0}
- target: {fileID: 530025588511586483, guid: 68fe09242bd73f34bad304c509872d90, type: 3}
propertyPath: m_LocalScale.y
value: 5
value: 4
objectReference: {fileID: 0}
- target: {fileID: 530025588511586483, guid: 68fe09242bd73f34bad304c509872d90, type: 3}
propertyPath: m_LocalScale.z
value: 5
value: 1
objectReference: {fileID: 0}
- target: {fileID: 530025588511586483, guid: 68fe09242bd73f34bad304c509872d90, type: 3}
propertyPath: m_LocalPosition.x

View File

@@ -23045,6 +23045,30 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3435632802124758411, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: acceleration
value: 15
objectReference: {fileID: 0}
- target: {fileID: 3435632802124758411, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: thresholdFar
value: 12
objectReference: {fileID: 0}
- target: {fileID: 3435632802124758411, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: thresholdNear
value: 7
objectReference: {fileID: 0}
- target: {fileID: 3435632802124758411, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: followDistance
value: 5
objectReference: {fileID: 0}
- target: {fileID: 3435632802124758411, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: manualMoveSmooth
value: 100
objectReference: {fileID: 0}
- target: {fileID: 3435632802124758411, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: heldIconDisplayHeight
value: 2
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []

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;

View File

@@ -32,5 +32,16 @@ public class GameManager : MonoBehaviour
public float PlayerStopDistance => gameSettings != null ? gameSettings.playerStopDistance : 1.0f;
public float FollowerPickupDelay => gameSettings != null ? gameSettings.followerPickupDelay : 0.2f;
public float FollowDistance => gameSettings != null ? gameSettings.followDistance : 1.5f;
public float ManualMoveSmooth => gameSettings != null ? gameSettings.manualMoveSmooth : 8f;
public float ThresholdFar => gameSettings != null ? gameSettings.thresholdFar : 2.5f;
public float ThresholdNear => gameSettings != null ? gameSettings.thresholdNear : 0.5f;
public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.1f;
public float MoveSpeed => gameSettings != null ? gameSettings.moveSpeed : 5f;
public float StopDistance => gameSettings != null ? gameSettings.stopDistance : 0.1f;
public bool UseRigidbody => gameSettings != null ? gameSettings.useRigidbody : true;
public float FollowUpdateInterval => gameSettings != null ? gameSettings.followUpdateInterval : 0.1f;
public float FollowerSpeedMultiplier => gameSettings != null ? gameSettings.followerSpeedMultiplier : 1.2f;
public float HeldIconDisplayHeight => gameSettings != null ? gameSettings.heldIconDisplayHeight : 2.0f;
// Add more accessors as needed
}

View File

@@ -6,6 +6,51 @@ public class GameSettings : ScriptableObject
[Header("Interactions")]
public float playerStopDistance = 6.0f;
public float followerPickupDelay = 0.2f;
// Add other settings here as needed
}
[Header("Follower Settings")]
public float followDistance = 1.5f;
public float manualMoveSmooth = 8f;
public float thresholdFar = 2.5f;
public float thresholdNear = 0.5f;
public float stopThreshold = 0.1f;
[Header("Player Settings")]
public float moveSpeed = 5f;
public float stopDistance = 0.1f;
public bool useRigidbody = true;
[Header("Backend Settings")]
[Tooltip("Technical parameters, not for design tuning")]
public float followUpdateInterval = 0.1f;
public float followerSpeedMultiplier = 1.2f;
public float heldIconDisplayHeight = 2.0f;
// Singleton pattern
private static GameSettings _instance;
public static GameSettings Instance {
get {
if (_instance == null) {
_instance = Resources.Load<GameSettings>("GameSettings");
if (_instance == null) {
Debug.LogError("GameSettings asset not found in Resources folder!");
}
}
return _instance;
}
}
// Static property wrappers for easy access
public static float PlayerStopDistance => Instance.playerStopDistance;
public static float FollowerPickupDelay => Instance.followerPickupDelay;
public static float FollowDistance => Instance.followDistance;
public static float ManualMoveSmooth => Instance.manualMoveSmooth;
public static float ThresholdFar => Instance.thresholdFar;
public static float ThresholdNear => Instance.thresholdNear;
public static float StopThreshold => Instance.stopThreshold;
public static float MoveSpeed => Instance.moveSpeed;
public static float StopDistance => Instance.stopDistance;
public static bool UseRigidbody => Instance.useRigidbody;
public static float FollowUpdateInterval => Instance.followUpdateInterval;
public static float FollowerSpeedMultiplier => Instance.followerSpeedMultiplier;
public static float HeldIconDisplayHeight => Instance.heldIconDisplayHeight;
}

View File

@@ -38,12 +38,10 @@ public class Pickup : MonoBehaviour
void OnDrawGizmos()
{
// Get stop distance from GameManager or default
float playerStopDistance = GameManager.Instance != null ? GameManager.Instance.PlayerStopDistance : 1.0f;
// Draw stop distance circle around pickup
// Use GameManager for playerStopDistance
float playerStopDistance = GameManager.Instance.PlayerStopDistance;
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, playerStopDistance);
// Draw stop point (where player is told to move)
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null)
{
@@ -58,8 +56,11 @@ public class Pickup : MonoBehaviour
{
if (itemData != null)
{
if (iconRenderer != null)
if (iconRenderer != null && itemData.mapSprite != null)
{
iconRenderer.sprite = itemData.mapSprite;
// Removed scaling logic, just set sprite
}
gameObject.name = itemData.itemName;
// Optionally update other fields, e.g. description
}
@@ -69,7 +70,6 @@ public class Pickup : MonoBehaviour
{
if (pickupInProgress) return;
pickupInProgress = true;
// Find player and follower controllers
var playerObj = GameObject.FindGameObjectWithTag("Player");
var followerObj = GameObject.FindGameObjectWithTag("Pulver");
if (playerObj == null || followerObj == null)
@@ -86,20 +86,17 @@ public class Pickup : MonoBehaviour
pickupInProgress = false;
return;
}
// Get settings from GameManager
float playerStopDistance = GameManager.Instance != null ? GameManager.Instance.PlayerStopDistance : 1.0f;
float followerPickupDelay = GameManager.Instance != null ? GameManager.Instance.FollowerPickupDelay : 0.2f;
// Subscribe to player arrival event
// Use GameManager for playerStopDistance and followerPickupDelay
float playerStopDistance = GameManager.Instance.PlayerStopDistance;
float followerPickupDelay = GameManager.Instance.FollowerPickupDelay;
void OnPlayerArrived()
{
playerController.OnArrivedAtTarget -= OnPlayerArrived;
// After player arrives, dispatch follower after delay
StartCoroutine(DispatchFollower());
}
System.Collections.IEnumerator DispatchFollower()
{
yield return new WaitForSeconds(followerPickupDelay);
// Subscribe to follower events
followerController.OnPickupArrived += OnFollowerArrived;
followerController.OnPickupReturned += OnFollowerReturned;
followerController.GoToPointAndReturn(transform.position, playerObj.transform);
@@ -107,7 +104,6 @@ public class Pickup : MonoBehaviour
void OnFollowerArrived()
{
followerController.OnPickupArrived -= OnFollowerArrived;
// Optionally: play pickup animation, etc.
}
void OnFollowerReturned()
{

View File

@@ -8,10 +8,6 @@ using Pathfinding; // Add this at the top
// Attach to the player GameObject. Works with or without Rigidbody/Rigidbody2D.
public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
{
public float moveSpeed = 5f;
public float stopDistance = 0.1f;
public bool useRigidbody = true;
Vector3 targetPosition;
bool hasTarget = false;
@@ -81,6 +77,7 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
if (aiPath != null)
{
aiPath.destination = targetPosition;
aiPath.maxSpeed = GameManager.Instance.MoveSpeed;
Debug.Log($"AIPath destination set to {targetPosition}");
}
else
@@ -124,14 +121,14 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
if (aiPath != null)
{
aiPath.destination = target;
aiPath.maxSpeed = GameManager.Instance.MoveSpeed;
}
while (!interruptMoveTo)
{
// 2D distance calculation (ignore z)
Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
Vector2 target2D = new Vector2(target.x, target.y);
float dist = Vector2.Distance(current2D, target2D);
if (dist <= stopDistance + 0.2f)
if (dist <= GameManager.Instance.StopDistance + 0.2f)
{
break;
}