[Interactions] Correctly display items now

This commit is contained in:
Michal Pikulski
2025-09-08 15:23:31 +02:00
parent 2e24b343c3
commit 6bbb40b1c0
7 changed files with 160 additions and 141 deletions

View File

@@ -13,7 +13,7 @@ public class CombineWithBehavior : InteractionRequirementBase
/// <returns>True if the combination was successful, false otherwise.</returns> /// <returns>True if the combination was successful, false otherwise.</returns>
public override bool TryInteract(FollowerController follower) public override bool TryInteract(FollowerController follower)
{ {
var heldItem = follower.currentlyHeldItem; var heldItem = follower.CurrentlyHeldItem;
var pickup = GetComponent<Pickup>(); var pickup = GetComponent<Pickup>();
if (heldItem == null) if (heldItem == null)
{ {

View File

@@ -16,7 +16,7 @@ public class RequiresItemBehavior : InteractionRequirementBase
/// <returns>True if the interaction was successful, false otherwise.</returns> /// <returns>True if the interaction was successful, false otherwise.</returns>
public override bool TryInteract(FollowerController follower) public override bool TryInteract(FollowerController follower)
{ {
var heldItem = follower.currentlyHeldItem; var heldItem = follower.CurrentlyHeldItem;
if (heldItem == requiredItem) if (heldItem == requiredItem)
{ {
OnSuccess?.Invoke(); OnSuccess?.Invoke();

View File

@@ -26,7 +26,7 @@ public class SlotItemBehavior : InteractionRequirementBase
/// <returns>True if the interaction was successful, false otherwise.</returns> /// <returns>True if the interaction was successful, false otherwise.</returns>
public override bool TryInteract(FollowerController follower) public override bool TryInteract(FollowerController follower)
{ {
var heldItem = follower.currentlyHeldItem; var heldItem = follower.CurrentlyHeldItem;
var pickup = GetComponent<Pickup>(); var pickup = GetComponent<Pickup>();
var slotItem = pickup != null ? pickup.itemData : null; var slotItem = pickup != null ? pickup.itemData : null;
var config = GameManager.Instance.GetSlotItemConfig(slotItem); var config = GameManager.Instance.GetSlotItemConfig(slotItem);

View File

@@ -1,6 +1,7 @@
using UnityEngine; using UnityEngine;
using Pathfinding; using Pathfinding;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using Utils;
/// <summary> /// <summary>
/// Controls the follower character, including following the player, handling pickups, and managing held items. /// Controls the follower character, including following the player, handling pickups, and managing held items.
@@ -18,38 +19,28 @@ public class FollowerController : MonoBehaviour
/// </summary> /// </summary>
public float manualMoveSmooth = 8f; public float manualMoveSmooth = 8f;
private Transform playerTransform; private Transform _playerTransform;
private AIPath playerAIPath; private AIPath _playerAIPath;
private AIPath aiPath; private AIPath _aiPath;
private Vector3 targetPoint; private Vector3 _targetPoint;
private float timer; private float _timer;
private bool isManualFollowing = true; private bool _isManualFollowing = true;
private Vector3 lastMoveDir = Vector3.right; private Vector3 _lastMoveDir = Vector3.right;
private float currentSpeed = 0f; private float _currentSpeed = 0f;
private Animator _animator;
private Transform _artTransform;
private Animator animator; private PickupItemData _currentlyHeldItem;
private Transform artTransform; public PickupItemData CurrentlyHeldItem => _currentlyHeldItem;
[Header("Held Item")]
/// <summary>
/// The item currently held by the follower.
/// </summary>
public PickupItemData currentlyHeldItem;
/// <summary> /// <summary>
/// Renderer for the held item icon. /// Renderer for the held item icon.
/// </summary> /// </summary>
public SpriteRenderer heldObjectRenderer; public SpriteRenderer heldObjectRenderer;
/// <summary>
/// Desired height for held item icon.
/// </summary>
public float heldIconDisplayHeight = 2.0f;
private bool isReturningToPlayer = false; private bool _isReturningToPlayer = false;
private float _playerMaxSpeed = 5f;
// Speed fields for follower private float _followerMaxSpeed = 6f;
private float playerMaxSpeed = 5f; private float _defaultFollowerMaxSpeed = 6f;
private float followerMaxSpeed = 6f;
private float defaultFollowerMaxSpeed = 6f;
// Pickup events // Pickup events
public delegate void FollowerPickupHandler(); public delegate void FollowerPickupHandler();
@@ -61,14 +52,14 @@ public class FollowerController : MonoBehaviour
/// Event fired when the follower returns to the player after a pickup. /// Event fired when the follower returns to the player after a pickup.
/// </summary> /// </summary>
public event FollowerPickupHandler OnPickupReturned; public event FollowerPickupHandler OnPickupReturned;
private Coroutine pickupCoroutine; private Coroutine _pickupCoroutine;
private bool lastInteractionSuccess = true; private bool _lastInteractionSuccess = true;
/// <summary> /// <summary>
/// Cache for the currently picked-up GameObject (hidden while held). /// Cache for the currently picked-up GameObject (hidden while held).
/// </summary> /// </summary>
private GameObject cachedPickupObject = null; private GameObject _cachedPickupObject = null;
/// <summary> /// <summary>
/// Set to true if the follower just combined items. /// Set to true if the follower just combined items.
@@ -77,16 +68,16 @@ public class FollowerController : MonoBehaviour
void Awake() void Awake()
{ {
aiPath = GetComponent<AIPath>(); _aiPath = GetComponent<AIPath>();
// Find art prefab and animator // Find art prefab and animator
artTransform = transform.Find("CharacterArt"); _artTransform = transform.Find("CharacterArt");
if (artTransform != null) if (_artTransform != null)
{ {
animator = artTransform.GetComponent<Animator>(); _animator = _artTransform.GetComponent<Animator>();
} }
else else
{ {
animator = GetComponentInChildren<Animator>(); // fallback _animator = GetComponentInChildren<Animator>(); // fallback
} }
} }
@@ -108,102 +99,102 @@ public class FollowerController : MonoBehaviour
void UpdateFollowTarget() void UpdateFollowTarget()
{ {
if (playerTransform == null) if (_playerTransform == null)
{ {
FindPlayerReference(); FindPlayerReference();
if (playerTransform == null) if (_playerTransform == null)
return; return;
} }
if (isManualFollowing) if (_isManualFollowing)
{ {
Vector3 playerPos = playerTransform.position; Vector3 playerPos = _playerTransform.position;
Vector3 moveDir = Vector3.zero; Vector3 moveDir = Vector3.zero;
if (playerAIPath != null && playerAIPath.velocity.magnitude > 0.01f) if (_playerAIPath != null && _playerAIPath.velocity.magnitude > 0.01f)
{ {
moveDir = playerAIPath.velocity.normalized; moveDir = _playerAIPath.velocity.normalized;
lastMoveDir = moveDir; _lastMoveDir = moveDir;
} }
else else
{ {
moveDir = lastMoveDir; moveDir = _lastMoveDir;
} }
// Use GameSettings for followDistance // Use GameSettings for followDistance
targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance; _targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance;
targetPoint.z = 0; _targetPoint.z = 0;
if (aiPath != null) if (_aiPath != null)
{ {
aiPath.enabled = false; _aiPath.enabled = false;
} }
} }
} }
void Update() void Update()
{ {
if (playerTransform == null) if (_playerTransform == null)
{ {
FindPlayerReference(); FindPlayerReference();
if (playerTransform == null) if (_playerTransform == null)
return; return;
} }
timer += Time.deltaTime; _timer += Time.deltaTime;
if (timer >= GameManager.Instance.FollowUpdateInterval) if (_timer >= GameManager.Instance.FollowUpdateInterval)
{ {
timer = 0f; _timer = 0f;
UpdateFollowTarget(); UpdateFollowTarget();
} }
if (isManualFollowing) if (_isManualFollowing)
{ {
Vector2 current2D = new Vector2(transform.position.x, transform.position.y); Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
Vector2 target2D = new Vector2(targetPoint.x, targetPoint.y); Vector2 target2D = new Vector2(_targetPoint.x, _targetPoint.y);
float dist = Vector2.Distance(current2D, target2D); float dist = Vector2.Distance(current2D, target2D);
float minSpeed = followerMaxSpeed * 0.3f; float minSpeed = _followerMaxSpeed * 0.3f;
float lerpFactor = GameManager.Instance.ManualMoveSmooth * Time.deltaTime; float lerpFactor = GameManager.Instance.ManualMoveSmooth * Time.deltaTime;
float targetSpeed = 0f; float targetSpeed = 0f;
if (dist > GameManager.Instance.StopThreshold) if (dist > GameManager.Instance.StopThreshold)
{ {
if (dist > GameManager.Instance.ThresholdFar) if (dist > GameManager.Instance.ThresholdFar)
{ {
targetSpeed = followerMaxSpeed; targetSpeed = _followerMaxSpeed;
} }
else if (dist > GameManager.Instance.ThresholdNear && dist <= GameManager.Instance.ThresholdFar) else if (dist > GameManager.Instance.ThresholdNear && dist <= GameManager.Instance.ThresholdFar)
{ {
targetSpeed = followerMaxSpeed; targetSpeed = _followerMaxSpeed;
} }
else if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear) else if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
{ {
targetSpeed = minSpeed; targetSpeed = minSpeed;
} }
currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, lerpFactor); _currentSpeed = Mathf.Lerp(_currentSpeed, targetSpeed, lerpFactor);
if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear) if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
{ {
currentSpeed = Mathf.Max(currentSpeed, minSpeed); _currentSpeed = Mathf.Max(_currentSpeed, minSpeed);
} }
Vector3 dir = (targetPoint - transform.position).normalized; Vector3 dir = (_targetPoint - transform.position).normalized;
transform.position += dir * currentSpeed * Time.deltaTime; transform.position += dir * _currentSpeed * Time.deltaTime;
} }
else else
{ {
currentSpeed = 0f; _currentSpeed = 0f;
} }
} }
if (isReturningToPlayer && aiPath != null && aiPath.enabled && playerTransform != null) if (_isReturningToPlayer && _aiPath != null && _aiPath.enabled && _playerTransform != null)
{ {
aiPath.destination = playerTransform.position; _aiPath.destination = _playerTransform.position;
} }
if (animator != null) if (_animator != null)
{ {
float normalizedSpeed = 0f; float normalizedSpeed = 0f;
if (isManualFollowing) if (_isManualFollowing)
{ {
normalizedSpeed = currentSpeed / followerMaxSpeed; normalizedSpeed = _currentSpeed / _followerMaxSpeed;
} }
else if (aiPath != null) else if (_aiPath != null)
{ {
normalizedSpeed = aiPath.velocity.magnitude / followerMaxSpeed; normalizedSpeed = _aiPath.velocity.magnitude / _followerMaxSpeed;
} }
animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); _animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed));
} }
} }
@@ -212,19 +203,19 @@ public class FollowerController : MonoBehaviour
GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null) if (playerObj != null)
{ {
playerTransform = playerObj.transform; _playerTransform = playerObj.transform;
playerAIPath = playerObj.GetComponent<AIPath>(); _playerAIPath = playerObj.GetComponent<AIPath>();
if (playerAIPath != null) if (_playerAIPath != null)
{ {
playerMaxSpeed = playerAIPath.maxSpeed; _playerMaxSpeed = _playerAIPath.maxSpeed;
defaultFollowerMaxSpeed = playerMaxSpeed; _defaultFollowerMaxSpeed = _playerMaxSpeed;
followerMaxSpeed = playerMaxSpeed * GameManager.Instance.FollowerSpeedMultiplier; _followerMaxSpeed = _playerMaxSpeed * GameManager.Instance.FollowerSpeedMultiplier;
} }
} }
else else
{ {
playerTransform = null; _playerTransform = null;
playerAIPath = null; _playerAIPath = null;
} }
} }
@@ -235,12 +226,12 @@ public class FollowerController : MonoBehaviour
/// <param name="worldPosition">The world position to move to.</param> /// <param name="worldPosition">The world position to move to.</param>
public void GoToPoint(Vector2 worldPosition) public void GoToPoint(Vector2 worldPosition)
{ {
isManualFollowing = false; _isManualFollowing = false;
if (aiPath != null) if (_aiPath != null)
{ {
aiPath.enabled = true; _aiPath.enabled = true;
aiPath.maxSpeed = followerMaxSpeed; _aiPath.maxSpeed = _followerMaxSpeed;
aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0); _aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0);
} }
} }
@@ -252,36 +243,26 @@ public class FollowerController : MonoBehaviour
/// <param name="playerTransform">The transform of the player.</param> /// <param name="playerTransform">The transform of the player.</param>
public void GoToPointAndReturn(Vector2 itemPosition, Transform playerTransform) public void GoToPointAndReturn(Vector2 itemPosition, Transform playerTransform)
{ {
if (pickupCoroutine != null) if (_pickupCoroutine != null)
StopCoroutine(pickupCoroutine); StopCoroutine(_pickupCoroutine);
if (aiPath != null) if (_aiPath != null)
aiPath.maxSpeed = followerMaxSpeed; _aiPath.maxSpeed = _followerMaxSpeed;
pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform)); _pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform));
} }
/// <summary> /// <summary>
/// Set the item held by the follower. /// Set the item held by the follower, copying all visual properties from the Pickup's SpriteRenderer.
/// </summary> /// </summary>
/// <param name="itemData">The item data to set.</param> /// <param name="itemData">The item data to set.</param>
public void SetHeldItem(PickupItemData itemData) /// <param name="pickupRenderer">The SpriteRenderer from the Pickup to copy appearance from.</param>
public void SetHeldItem(PickupItemData itemData, SpriteRenderer pickupRenderer = null)
{ {
currentlyHeldItem = itemData; _currentlyHeldItem = itemData;
if (heldObjectRenderer != null) if (heldObjectRenderer != null)
{ {
if (currentlyHeldItem != null && currentlyHeldItem.mapSprite != null) if (_currentlyHeldItem != null && pickupRenderer != null)
{ {
heldObjectRenderer.sprite = currentlyHeldItem.mapSprite; AppleHillsUtils.CopySpriteRendererProperties(pickupRenderer, heldObjectRenderer);
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 else
{ {
@@ -297,18 +278,18 @@ public class FollowerController : MonoBehaviour
/// <param name="success">True if the last interaction was successful, false otherwise.</param> /// <param name="success">True if the last interaction was successful, false otherwise.</param>
public void SetInteractionResult(bool success) public void SetInteractionResult(bool success)
{ {
lastInteractionSuccess = success; _lastInteractionSuccess = success;
} }
private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform) private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform)
{ {
isManualFollowing = false; _isManualFollowing = false;
isReturningToPlayer = false; _isReturningToPlayer = false;
if (aiPath != null) if (_aiPath != null)
{ {
aiPath.enabled = true; _aiPath.enabled = true;
aiPath.maxSpeed = followerMaxSpeed; _aiPath.maxSpeed = _followerMaxSpeed;
aiPath.destination = new Vector3(itemPosition.x, itemPosition.y, 0); _aiPath.destination = new Vector3(itemPosition.x, itemPosition.y, 0);
} }
// Wait until follower reaches item (2D distance) // 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) while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(itemPosition.x, itemPosition.y)) > GameManager.Instance.StopThreshold)
@@ -317,7 +298,7 @@ public class FollowerController : MonoBehaviour
} }
OnPickupArrived?.Invoke(); OnPickupArrived?.Invoke();
// Only perform pickup/swap logic if interaction succeeded // Only perform pickup/swap logic if interaction succeeded
if (lastInteractionSuccess && heldObjectRenderer != null) if (_lastInteractionSuccess && heldObjectRenderer != null)
{ {
Collider2D[] hits = Physics2D.OverlapCircleAll(itemPosition, 0.2f); Collider2D[] hits = Physics2D.OverlapCircleAll(itemPosition, 0.2f);
foreach (var hit in hits) foreach (var hit in hits)
@@ -334,56 +315,56 @@ public class FollowerController : MonoBehaviour
if (justCombined) if (justCombined)
{ {
// Combination: just destroy the pickup, don't spawn anything // Combination: just destroy the pickup, don't spawn anything
if (cachedPickupObject != null) if (_cachedPickupObject != null)
{ {
Destroy(cachedPickupObject); Destroy(_cachedPickupObject);
cachedPickupObject = null; _cachedPickupObject = null;
} }
GameObject.Destroy(pickup.gameObject); GameObject.Destroy(pickup.gameObject);
justCombined = false; justCombined = false;
break; break;
} }
// Swap logic: if holding an item, drop it here // Swap logic: if holding an item, drop it here
if (currentlyHeldItem != null && cachedPickupObject != null) if (_currentlyHeldItem != null && _cachedPickupObject != null)
{ {
// Drop the cached object at the pickup's position // Drop the cached object at the pickup's position
cachedPickupObject.transform.position = pickup.transform.position; _cachedPickupObject.transform.position = pickup.transform.position;
cachedPickupObject.transform.SetParent(null); _cachedPickupObject.transform.SetParent(null);
cachedPickupObject.SetActive(true); _cachedPickupObject.SetActive(true);
cachedPickupObject = null; _cachedPickupObject = null;
} }
SetHeldItem(pickup.itemData); SetHeldItem(pickup.itemData, pickup.iconRenderer);
// Cache and hide the picked up object // Cache and hide the picked up object
cachedPickupObject = pickup.gameObject; _cachedPickupObject = pickup.gameObject;
cachedPickupObject.SetActive(false); _cachedPickupObject.SetActive(false);
cachedPickupObject.transform.SetParent(this.transform); _cachedPickupObject.transform.SetParent(this.transform);
break; break;
} }
} }
} }
// Wait briefly, then return to player // Wait briefly, then return to player
yield return new WaitForSeconds(0.2f); yield return new WaitForSeconds(0.2f);
if (aiPath != null && playerTransform != null) if (_aiPath != null && playerTransform != null)
{ {
aiPath.maxSpeed = followerMaxSpeed; _aiPath.maxSpeed = _followerMaxSpeed;
aiPath.destination = playerTransform.position; _aiPath.destination = playerTransform.position;
} }
isReturningToPlayer = true; _isReturningToPlayer = true;
// Wait until follower returns to player (2D distance) // 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) 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; yield return null;
} }
isReturningToPlayer = false; _isReturningToPlayer = false;
OnPickupReturned?.Invoke(); OnPickupReturned?.Invoke();
// Reset follower speed to normal after pickup // Reset follower speed to normal after pickup
followerMaxSpeed = defaultFollowerMaxSpeed; _followerMaxSpeed = _defaultFollowerMaxSpeed;
if (aiPath != null) if (_aiPath != null)
aiPath.maxSpeed = followerMaxSpeed; _aiPath.maxSpeed = _followerMaxSpeed;
isManualFollowing = true; _isManualFollowing = true;
if (aiPath != null) if (_aiPath != null)
aiPath.enabled = false; _aiPath.enabled = false;
pickupCoroutine = null; _pickupCoroutine = null;
} }
void OnDrawGizmos() void OnDrawGizmos()
@@ -391,9 +372,9 @@ public class FollowerController : MonoBehaviour
if (debugDrawTarget && Application.isPlaying) if (debugDrawTarget && Application.isPlaying)
{ {
Gizmos.color = Color.cyan; Gizmos.color = Color.cyan;
Gizmos.DrawSphere(targetPoint, 0.2f); Gizmos.DrawSphere(_targetPoint, 0.2f);
Gizmos.color = Color.yellow; Gizmos.color = Color.yellow;
Gizmos.DrawLine(transform.position, targetPoint); Gizmos.DrawLine(transform.position, _targetPoint);
} }
} }
} }

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4c0a166b16da465792751b490744b095
timeCreated: 1757337202

View File

@@ -0,0 +1,32 @@
using UnityEngine;
namespace Utils
{
/// <summary>
/// Utility methods for working with SpriteRenderers.
/// </summary>
public static class AppleHillsUtils
{
/// <summary>
/// Copies all relevant visual properties from one SpriteRenderer to another.
/// </summary>
/// <param name="from">The source SpriteRenderer.</param>
/// <param name="to">The target SpriteRenderer.</param>
public static void CopySpriteRendererProperties(SpriteRenderer from, SpriteRenderer to)
{
if (from == null || to == null) return;
to.sprite = from.sprite;
to.color = from.color;
to.flipX = from.flipX;
to.flipY = from.flipY;
to.material = from.material;
to.drawMode = from.drawMode;
to.size = from.size;
to.maskInteraction = from.maskInteraction;
to.sortingLayerID = from.sortingLayerID;
to.sortingOrder = from.sortingOrder;
to.enabled = true;
to.transform.localScale = from.transform.localScale;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1bf54e2eb908492588a62995b99bd74a
timeCreated: 1757337202