[Player][Interactions] Pulver moves to item and goes back. Item disappears, but in wrong order

This commit is contained in:
Michal Pikulski
2025-09-04 00:00:46 +02:00
parent 97c5edf619
commit 0c930d09a4
18 changed files with 455 additions and 43 deletions

View File

@@ -27,6 +27,9 @@ public class FollowerController : MonoBehaviour
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;
@@ -72,6 +75,8 @@ public class FollowerController : MonoBehaviour
if (playerAIPath != null)
{
playerMaxSpeed = playerAIPath.maxSpeed;
defaultFollowerMaxSpeed = playerMaxSpeed;
followerMaxSpeed = playerMaxSpeed * followerSpeedMultiplier;
}
}
else
@@ -95,27 +100,29 @@ public class FollowerController : MonoBehaviour
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)
if (isManualFollowing)
{
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
// 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;
// Only disable aiPath if in manual mode
if (aiPath != null)
{
aiPath.enabled = false;
}
}
}
@@ -139,14 +146,17 @@ public class FollowerController : MonoBehaviour
// Manual movement logic with acceleration/deceleration and stop threshold
if (isManualFollowing)
{
float dist = Vector3.Distance(transform.position, targetPoint);
// 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 desiredSpeed = playerMaxSpeed;
float desiredSpeed = followerMaxSpeed;
if (dist > thresholdFar)
{
// Accelerate to player's max speed
desiredSpeed = Mathf.Min(currentSpeed + acceleration * Time.deltaTime, playerMaxSpeed);
// Accelerate to follower's max speed
desiredSpeed = Mathf.Min(currentSpeed + acceleration * Time.deltaTime, followerMaxSpeed);
}
else if (dist <= thresholdNear && dist > stopThreshold)
{
@@ -155,8 +165,8 @@ public class FollowerController : MonoBehaviour
}
else
{
// Maintain player's max speed
desiredSpeed = playerMaxSpeed;
// Maintain follower's max speed
desiredSpeed = followerMaxSpeed;
}
currentSpeed = desiredSpeed;
Vector3 dir = (targetPoint - transform.position).normalized;
@@ -176,12 +186,12 @@ public class FollowerController : MonoBehaviour
if (isManualFollowing)
{
// Use currentSpeed for manual following
normalizedSpeed = currentSpeed / playerMaxSpeed;
normalizedSpeed = currentSpeed / followerMaxSpeed;
}
else if (aiPath != null)
{
// Use aiPath velocity for pathfinding mode
normalizedSpeed = aiPath.velocity.magnitude / playerMaxSpeed;
normalizedSpeed = aiPath.velocity.magnitude / followerMaxSpeed;
}
animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed));
}
@@ -194,10 +204,64 @@ public class FollowerController : MonoBehaviour
if (aiPath != null)
{
aiPath.enabled = true;
aiPath.maxSpeed = followerMaxSpeed;
aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0);
}
}
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)
{
if (pickupCoroutine != null)
StopCoroutine(pickupCoroutine);
if (aiPath != null)
aiPath.maxSpeed = followerMaxSpeed;
pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform));
}
private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform)
{
isManualFollowing = 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)) > stopThreshold)
{
yield return null;
}
OnPickupArrived?.Invoke();
// Wait briefly, then return to player
yield return new WaitForSeconds(0.2f);
if (aiPath != null && playerTransform != null)
{
aiPath.maxSpeed = followerMaxSpeed;
aiPath.destination = playerTransform.position;
}
// 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)
{
yield return null;
}
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)

View File

@@ -0,0 +1,36 @@
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager _instance;
public static GameManager Instance
{
get
{
if (_instance == null)
{
_instance = FindAnyObjectByType<GameManager>();
if (_instance == null)
{
var go = new GameObject("GameManager");
_instance = go.AddComponent<GameManager>();
DontDestroyOnLoad(go);
}
}
return _instance;
}
}
[Header("Game Settings")]
public GameSettings gameSettings;
void Awake()
{
_instance = this;
DontDestroyOnLoad(gameObject);
}
public float PlayerStopDistance => gameSettings != null ? gameSettings.playerStopDistance : 1.0f;
public float FollowerPickupDelay => gameSettings != null ? gameSettings.followerPickupDelay : 0.2f;
// Add more accessors as needed
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b9333cd9ca0e44769ef1913d10231047
timeCreated: 1756933142

View File

@@ -0,0 +1,11 @@
using UnityEngine;
[CreateAssetMenu(fileName = "GameSettings", menuName = "AppleHills/GameSettings", order = 1)]
public class GameSettings : ScriptableObject
{
[Header("Interactions")]
public float playerStopDistance = 6.0f;
public float followerPickupDelay = 0.2f;
// Add other settings here as needed
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e4ec438b455a4044957501c2c66a6f4b
timeCreated: 1756933137

View File

@@ -7,8 +7,12 @@ public class LevelSwitch : MonoBehaviour
public SpriteRenderer iconRenderer;
private Interactable interactable;
private bool _isActive = true;
void Awake()
{
_isActive = true;
if (iconRenderer == null)
iconRenderer = GetComponent<SpriteRenderer>();
@@ -51,11 +55,12 @@ public class LevelSwitch : MonoBehaviour
private async void OnInteracted()
{
Debug.Log($"LevelSwitch.OnInteracted: Switching to level {switchData?.targetLevelSceneName}");
if (switchData != null && !string.IsNullOrEmpty(switchData.targetLevelSceneName))
if (switchData != null && !string.IsNullOrEmpty(switchData.targetLevelSceneName) && _isActive)
{
// Optionally: show loading UI here
var progress = new Progress<float>(p => Debug.Log($"Loading progress: {p * 100:F0}%"));
await SceneManagerService.Instance.SwitchSceneAsync(switchData.targetLevelSceneName, progress);
_isActive = false;
// Optionally: hide loading UI here
}
}

View File

@@ -6,6 +6,8 @@ public class Pickup : MonoBehaviour
public SpriteRenderer iconRenderer;
private Interactable interactable;
private bool pickupInProgress = false;
void Awake()
{
if (iconRenderer == null)
@@ -33,6 +35,23 @@ public class Pickup : MonoBehaviour
iconRenderer = GetComponent<SpriteRenderer>();
ApplyItemData();
}
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
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)
{
Vector3 stopPoint = transform.position + (playerObj.transform.position - transform.position).normalized * playerStopDistance;
Gizmos.color = Color.cyan;
Gizmos.DrawSphere(stopPoint, 0.15f);
}
}
#endif
public void ApplyItemData()
@@ -48,8 +67,56 @@ public class Pickup : MonoBehaviour
private void OnInteracted()
{
Debug.Log($"[Pickup] OnInteracted: Picked up {itemData?.itemName}");
// TODO: Add item to inventory manager here
Destroy(gameObject);
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)
{
Debug.LogWarning("Pickup: Player or Follower not found.");
pickupInProgress = false;
return;
}
var playerController = playerObj.GetComponent<PlayerTouchController>();
var followerController = followerObj.GetComponent<FollowerController>();
if (playerController == null || followerController == null)
{
Debug.LogWarning("Pickup: PlayerTouchController or FollowerController missing.");
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
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);
}
void OnFollowerArrived()
{
followerController.OnPickupArrived -= OnFollowerArrived;
// Optionally: play pickup animation, etc.
}
void OnFollowerReturned()
{
followerController.OnPickupReturned -= OnFollowerReturned;
pickupInProgress = false;
Destroy(gameObject);
}
playerController.OnArrivedAtTarget += OnPlayerArrived;
Vector3 stopPoint = transform.position + (playerObj.transform.position - transform.position).normalized * playerStopDistance;
playerController.MoveToAndNotify(stopPoint);
}
}

View File

@@ -21,6 +21,11 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
private Animator animator;
private Transform artTransform;
public delegate void ArrivedAtTargetHandler();
public event ArrivedAtTargetHandler OnArrivedAtTarget;
private Coroutine moveToCoroutine;
private bool interruptMoveTo = false;
void Awake()
{
rb3d = GetComponent<Rigidbody>();
@@ -56,6 +61,8 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
public void OnTouchPress(Vector2 worldPosition)
{
// If moving to pickup, interrupt
InterruptMoveTo();
Debug.Log($"PlayerTouchController.OnTouchPress received worldPosition: {worldPosition}");
SetTargetPosition(worldPosition);
}
@@ -93,4 +100,48 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
}
// Remove FixedUpdate and MoveTowardsTarget, as AIPath handles movement
// Move to a target position, notify when arrived
public void MoveToAndNotify(Vector3 target)
{
if (moveToCoroutine != null)
{
StopCoroutine(moveToCoroutine);
}
interruptMoveTo = false;
moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target));
}
public void InterruptMoveTo()
{
interruptMoveTo = true;
}
private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target)
{
hasTarget = true;
targetPosition = target;
if (aiPath != null)
{
aiPath.destination = target;
}
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)
{
break;
}
yield return null;
}
hasTarget = false;
moveToCoroutine = null;
if (!interruptMoveTo)
{
OnArrivedAtTarget?.Invoke();
}
}
}

View File

@@ -6,7 +6,24 @@ using UnityEngine.SceneManagement;
public class SceneManagerService : MonoBehaviour
{
public static SceneManagerService Instance { get; private set; }
private static SceneManagerService _instance;
public static SceneManagerService Instance
{
get
{
if (_instance == null)
{
_instance = FindAnyObjectByType<SceneManagerService>();
if (_instance == null)
{
var go = new GameObject("SceneManagerService");
_instance = go.AddComponent<SceneManagerService>();
DontDestroyOnLoad(go);
}
}
return _instance;
}
}
// Events for scene lifecycle
public event Action<string> SceneLoadStarted;
@@ -21,12 +38,7 @@ public class SceneManagerService : MonoBehaviour
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
_instance = this;
DontDestroyOnLoad(gameObject);
}