From 0ef25f265c9cbbcf05d3ac8a3ae09e7fd779aa4f Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Wed, 10 Sep 2025 16:42:43 +0200 Subject: [PATCH] Semi-working Interactables rework --- .../Runtime/CustomBootSettings_Runtime.asset | 1 + .../Managers/InteractionManager.prefab | 46 ++++++ .../Managers/InteractionManager.prefab.meta | 7 + .../Scenes/Levels/AppleHillsOverworld.unity | 6 +- Assets/Scripts/Characters.meta | 3 + Assets/Scripts/Characters/Character.cs | 11 ++ Assets/Scripts/Characters/Character.cs.meta | 3 + Assets/Scripts/Input/InputManager.cs | 21 ++- Assets/Scripts/Input/PlayerTouchController.cs | 2 +- Assets/Scripts/Interactions/Interactable.cs | 49 +----- .../Interactions/InteractionOrchestrator.cs | 154 ++++++++++++++++++ .../InteractionOrchestrator.cs.meta | 3 + Assets/Scripts/Interactions/Pickup.cs | 71 +------- .../Scripts/Interactions/SlotItemBehavior.cs | 14 +- Assets/Scripts/Movement/FollowerController.cs | 4 +- 15 files changed, 271 insertions(+), 124 deletions(-) create mode 100644 Assets/Prefabs/Managers/InteractionManager.prefab create mode 100644 Assets/Prefabs/Managers/InteractionManager.prefab.meta create mode 100644 Assets/Scripts/Characters.meta create mode 100644 Assets/Scripts/Characters/Character.cs create mode 100644 Assets/Scripts/Characters/Character.cs.meta create mode 100644 Assets/Scripts/Interactions/InteractionOrchestrator.cs create mode 100644 Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta diff --git a/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset b/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset index 2f934632..a13c9da9 100644 --- a/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset +++ b/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset @@ -18,3 +18,4 @@ MonoBehaviour: - {fileID: 458265635552197097, guid: a77d1e8b2fa8aa945a6f39b312536e0d, type: 3} - {fileID: 552225285624929822, guid: e39992796d5459442be9967c77e27066, type: 3} - {fileID: 7644433920135100480, guid: 12d242e44fe80ab44af852254b7cab0f, type: 3} + - {fileID: 5970756976527527001, guid: eaab28d7e21337b4baef062e2977e616, type: 3} diff --git a/Assets/Prefabs/Managers/InteractionManager.prefab b/Assets/Prefabs/Managers/InteractionManager.prefab new file mode 100644 index 00000000..dcb39eca --- /dev/null +++ b/Assets/Prefabs/Managers/InteractionManager.prefab @@ -0,0 +1,46 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &5970756976527527001 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2154246752606426586} + - component: {fileID: 5680731486320555959} + m_Layer: 0 + m_Name: InteractionManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2154246752606426586 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5970756976527527001} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 20.57967, y: 22.03297, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5680731486320555959 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5970756976527527001} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 705c4ee7f8204cc68aacd79e2a4a506d, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/Prefabs/Managers/InteractionManager.prefab.meta b/Assets/Prefabs/Managers/InteractionManager.prefab.meta new file mode 100644 index 00000000..14b39a04 --- /dev/null +++ b/Assets/Prefabs/Managers/InteractionManager.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: eaab28d7e21337b4baef062e2977e616 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Levels/AppleHillsOverworld.unity b/Assets/Scenes/Levels/AppleHillsOverworld.unity index dbdb43c9..e8f6a468 100644 --- a/Assets/Scenes/Levels/AppleHillsOverworld.unity +++ b/Assets/Scenes/Levels/AppleHillsOverworld.unity @@ -868,7 +868,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c119ffb87b2a16d4f925ff5d5ffd7092, type: 3} m_Name: m_EditorClassIdentifier: - ShouldPlayIntro: 1 + shouldPlayIntro: 0 --- !u!95 &948124911 Animator: serializedVersion: 7 @@ -1998,6 +1998,10 @@ PrefabInstance: propertyPath: heldIconDisplayHeight value: 2 objectReference: {fileID: 0} + - target: {fileID: 7852204877518954380, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3} + propertyPath: endReachedDistance + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] diff --git a/Assets/Scripts/Characters.meta b/Assets/Scripts/Characters.meta new file mode 100644 index 00000000..ca2412db --- /dev/null +++ b/Assets/Scripts/Characters.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 01ee82c489314d60bf27aa9a405f2633 +timeCreated: 1757513074 \ No newline at end of file diff --git a/Assets/Scripts/Characters/Character.cs b/Assets/Scripts/Characters/Character.cs new file mode 100644 index 00000000..1c3a97c5 --- /dev/null +++ b/Assets/Scripts/Characters/Character.cs @@ -0,0 +1,11 @@ +using UnityEngine; + +/// +/// Base class for all characters that can interact in the world (e.g., player, follower). +/// +public abstract class Character : MonoBehaviour +{ + // Placeholder for shared character logic or properties. + // For now, this is intentionally minimal. +} + diff --git a/Assets/Scripts/Characters/Character.cs.meta b/Assets/Scripts/Characters/Character.cs.meta new file mode 100644 index 00000000..90416577 --- /dev/null +++ b/Assets/Scripts/Characters/Character.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9253c7c4ca6946b1b31196c4bf8d685e +timeCreated: 1757513074 \ No newline at end of file diff --git a/Assets/Scripts/Input/InputManager.cs b/Assets/Scripts/Input/InputManager.cs index eaffd4e3..1c92b883 100644 --- a/Assets/Scripts/Input/InputManager.cs +++ b/Assets/Scripts/Input/InputManager.cs @@ -157,11 +157,28 @@ public class InputManager : MonoBehaviour Collider2D hit = Physics2D.OverlapPoint(worldPos, mask); if (hit != null) { - var interactable = hit.GetComponent(); + var interactable = hit.GetComponent(); if (interactable != null) { Debug.unityLogger.Log("Interactable", $"[InputManager] Delegating tap to interactable at {worldPos} (GameObject: {hit.gameObject.name})"); - interactable.OnTap(worldPos); + // Find the player Character (by tag) + var playerObj = GameObject.FindGameObjectWithTag("Player"); + var playerCharacter = playerObj != null ? playerObj.GetComponent() : null; + if (playerCharacter != null) + { + InteractionOrchestrator.Instance.RequestInteraction(interactable, playerCharacter); + } + else + { + Debug.LogWarning("[InputManager] Player Character not found for interaction delegation."); + } + return true; + } + // Fallback: support other ITouchInputConsumer implementations + var consumer = hit.GetComponent(); + if (consumer != null) + { + consumer.OnTap(worldPos); return true; } else diff --git a/Assets/Scripts/Input/PlayerTouchController.cs b/Assets/Scripts/Input/PlayerTouchController.cs index 8a853e77..49674c1e 100644 --- a/Assets/Scripts/Input/PlayerTouchController.cs +++ b/Assets/Scripts/Input/PlayerTouchController.cs @@ -7,7 +7,7 @@ namespace Input /// Handles player movement in response to tap and hold input events. /// Supports both direct and pathfinding movement modes, and provides event/callbacks for arrival/cancellation. /// - public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer + public class PlayerTouchController : Character, ITouchInputConsumer { // --- Movement State --- private Vector3 targetPosition; diff --git a/Assets/Scripts/Interactions/Interactable.cs b/Assets/Scripts/Interactions/Interactable.cs index bab8a866..517ad3fa 100644 --- a/Assets/Scripts/Interactions/Interactable.cs +++ b/Assets/Scripts/Interactions/Interactable.cs @@ -9,13 +9,6 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer public event Action StartedInteraction; public event Action InteractionComplete; - private ObjectiveStepBehaviour stepBehaviour; - - void Awake() - { - stepBehaviour = GetComponent(); - } - /// /// Handles tap input. Triggers interaction logic. /// @@ -25,48 +18,20 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer StartedInteraction?.Invoke(); } - /// - /// No hold behavior for interactables. - /// + // No hold behavior for interactables. public void OnHoldStart(Vector2 worldPosition) { } public void OnHoldMove(Vector2 worldPosition) { } public void OnHoldEnd(Vector2 worldPosition) { } /// - /// Called when the follower arrives at this interactable. + /// Called to interact with this object by a character (player, follower, etc). /// - public bool OnFollowerArrived(FollowerController follower) + public virtual void OnInteract(Character character) { - // Check if step is locked here - if (stepBehaviour != null && !stepBehaviour.IsStepUnlocked()) - { - DebugUIMessage.Show("Item is not unlocked yet"); - Debug.Log("[Puzzles] Tried to interact with locked step: " + gameObject.name); - InteractionComplete?.Invoke(false); - return false; - } - var requirements = GetComponents(); - if (requirements.Length == 0) - { - InteractionComplete?.Invoke(true); - return true; - } - bool anySuccess = false; - foreach (var req in requirements) - { - if (req.TryInteract(follower)) - { - anySuccess = true; - break; - } - } - InteractionComplete?.Invoke(anySuccess); - if (!anySuccess) - { - Debug.Log($"[Interactable] No interaction requirements succeeded for {gameObject.name}"); - // Optionally trigger a default failure event or feedback here - } - return anySuccess; + // In the new architecture, requirements and step checks will be handled by orchestrator. + StartedInteraction?.Invoke(); + // For now, immediately complete interaction as success (can be extended later). + InteractionComplete?.Invoke(true); } public void CompleteInteraction(bool success) diff --git a/Assets/Scripts/Interactions/InteractionOrchestrator.cs b/Assets/Scripts/Interactions/InteractionOrchestrator.cs new file mode 100644 index 00000000..3621e793 --- /dev/null +++ b/Assets/Scripts/Interactions/InteractionOrchestrator.cs @@ -0,0 +1,154 @@ +using System; +using UnityEngine; + +/// +/// Handles the process of moving characters to interactables and orchestrating interactions. +/// +public class InteractionOrchestrator : MonoBehaviour +{ + private static InteractionOrchestrator _instance; + private static bool _isQuitting = false; + + // Singleton for easy access (optional, can be replaced with DI or scene reference) + public static InteractionOrchestrator Instance + { + get + { + if (_instance == null && Application.isPlaying && !_isQuitting) + { + _instance = FindAnyObjectByType(); + if (_instance == null) + { + var go = new GameObject("InteractionOrchestrator"); + _instance = go.AddComponent(); + // DontDestroyOnLoad(go); + } + } + return _instance; + } + } + + void Awake() + { + _instance = this; + // DontDestroyOnLoad(gameObject); + } + + void OnApplicationQuit() + { + _isQuitting = true; + } + + // Store pending interaction state + private Interactable _pendingInteractable; + private Input.PlayerTouchController _pendingPlayer; + private FollowerController _pendingFollower; + private bool _interactionInProgress; + + /// + /// Request an interaction between a character and an interactable. + /// + public void RequestInteraction(Interactable interactable, Character character) + { + // Only support player-initiated interactions for now + if (character is Input.PlayerTouchController player) + { + // Compute closest point on the interaction radius + Vector3 interactablePos = interactable.transform.position; + Vector3 playerPos = player.transform.position; + float stopDistance = GameManager.Instance.PlayerStopDistance; + Vector3 toPlayer = (playerPos - interactablePos).normalized; + Vector3 stopPoint = interactablePos + toPlayer * stopDistance; + + // Unsubscribe previous to avoid duplicate calls + player.OnArrivedAtTarget -= OnPlayerArrived; + player.OnArrivedAtTarget += OnPlayerArrived; + _pendingInteractable = interactable; + _pendingPlayer = player; + _pendingFollower = FindFollower(); + _interactionInProgress = true; + player.MoveToAndNotify(stopPoint); + } + else + { + // Fallback: immediately interact + OnCharacterArrived(interactable, character); + } + } + + // Helper to find the follower in the scene + private FollowerController FindFollower() + { + // Use the recommended Unity API for finding objects + return GameObject.FindFirstObjectByType(); + } + + private void OnPlayerArrived() + { + if (!_interactionInProgress || _pendingInteractable == null || _pendingPlayer == null) + return; + // Unsubscribe to avoid memory leaks + _pendingPlayer.OnArrivedAtTarget -= OnPlayerArrived; + // Now dispatch the follower to the interactable + if (_pendingFollower != null) + { + _pendingFollower.OnPickupArrived -= OnFollowerArrived; + _pendingFollower.OnPickupArrived += OnFollowerArrived; + _pendingFollower.GoToPointAndReturn(_pendingInteractable.transform.position, _pendingPlayer.transform); + } + else + { + // No follower found, just interact as player + OnCharacterArrived(_pendingInteractable, _pendingPlayer); + _interactionInProgress = false; + _pendingInteractable = null; + _pendingPlayer = null; + _pendingFollower = null; + } + } + + private void OnFollowerArrived() + { + if (!_interactionInProgress || _pendingInteractable == null || _pendingFollower == null) + return; + _pendingFollower.OnPickupArrived -= OnFollowerArrived; + // Now check requirements and interact as follower + OnCharacterArrived(_pendingInteractable, _pendingFollower); + _interactionInProgress = false; + _pendingInteractable = null; + _pendingPlayer = null; + _pendingFollower = null; + } + + /// + /// Called when the character has arrived at the interactable. + /// + private void OnCharacterArrived(Interactable interactable, Character character) + { + // Check all requirements + var requirements = interactable.GetComponents(); + bool allMet = true; + foreach (var req in requirements) + { + // For now, only FollowerController is supported for requirements + // (can be extended to support Player, etc.) + if (character is FollowerController follower) + { + if (!req.TryInteract(follower)) + { + allMet = false; + break; + } + } + // Add more character types as needed + } + if (allMet) + { + interactable.OnInteract(character); + } + else + { + interactable.CompleteInteraction(false); + } + } +} diff --git a/Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta b/Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta new file mode 100644 index 00000000..afd3e441 --- /dev/null +++ b/Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 705c4ee7f8204cc68aacd79e2a4a506d +timeCreated: 1757513080 \ No newline at end of file diff --git a/Assets/Scripts/Interactions/Pickup.cs b/Assets/Scripts/Interactions/Pickup.cs index d974649f..415ef5a6 100644 --- a/Assets/Scripts/Interactions/Pickup.cs +++ b/Assets/Scripts/Interactions/Pickup.cs @@ -13,8 +13,6 @@ public class Pickup : MonoBehaviour public SpriteRenderer iconRenderer; private Interactable interactable; - private bool pickupInProgress = false; - /// /// Unity Awake callback. Sets up icon, interactable, and event handlers. /// @@ -98,76 +96,11 @@ public class Pickup : MonoBehaviour } /// - /// Handles the start of an interaction (player approaches, then follower picks up). + /// Handles the start of an interaction (for feedback/UI only). /// private void OnStartedInteraction() { - if (pickupInProgress) return; - var playerObj = GameObject.FindGameObjectWithTag("Player"); - var followerObj = GameObject.FindGameObjectWithTag("Pulver"); - if (playerObj == null || followerObj == null) - { - Debug.LogWarning("Pickup: Player or Follower not found."); - return; - } - var playerController = playerObj.GetComponent(); - var followerController = followerObj.GetComponent(); - if (playerController == null || followerController == null) - { - Debug.LogWarning("Pickup: PlayerTouchController or FollowerController missing."); - return; - } - float playerStopDistance = GameManager.Instance.PlayerStopDistance; - float followerPickupDelay = GameManager.Instance.FollowerPickupDelay; - // --- Local event/coroutine handlers --- - void OnPlayerArrived() - { - playerController.OnArrivedAtTarget -= OnPlayerArrived; - playerController.OnMoveToCancelled -= OnPlayerMoveCancelled; - pickupInProgress = true; - StartCoroutine(DispatchFollower()); - } - void OnPlayerMoveCancelled() - { - playerController.OnArrivedAtTarget -= OnPlayerArrived; - playerController.OnMoveToCancelled -= OnPlayerMoveCancelled; - pickupInProgress = false; - } - System.Collections.IEnumerator DispatchFollower() - { - yield return new WaitForSeconds(followerPickupDelay); - followerController.OnPickupArrived += OnFollowerArrived; - followerController.OnPickupReturned += OnFollowerReturned; - followerController.GoToPointAndReturn(transform.position, playerObj.transform); - } - void OnFollowerArrived() - { - followerController.OnPickupArrived -= OnFollowerArrived; - bool interactionSuccess = true; - if (interactable != null) - { - interactionSuccess = interactable.OnFollowerArrived(followerController); - } - followerController.SetInteractionResult(interactionSuccess); - } - void OnFollowerReturned() - { - followerController.OnPickupReturned -= OnFollowerReturned; - pickupInProgress = false; - } - playerController.OnArrivedAtTarget += OnPlayerArrived; - playerController.OnMoveToCancelled += OnPlayerMoveCancelled; - Vector3 stopPoint = transform.position + (playerObj.transform.position - transform.position).normalized * playerStopDistance; - float distToPickup = Vector2.Distance(new Vector2(playerObj.transform.position.x, playerObj.transform.position.y), new Vector2(transform.position.x, transform.position.y)); - float dist = Vector2.Distance(new Vector2(playerObj.transform.position.x, playerObj.transform.position.y), new Vector2(stopPoint.x, stopPoint.y)); - if (distToPickup <= playerStopDistance || dist <= 0.2f) - { - OnPlayerArrived(); - } - else - { - playerController.MoveToAndNotify(stopPoint); - } + // Optionally, add pickup-specific feedback here (e.g., highlight, sound). } /// diff --git a/Assets/Scripts/Interactions/SlotItemBehavior.cs b/Assets/Scripts/Interactions/SlotItemBehavior.cs index 84bfebc7..e45b3012 100644 --- a/Assets/Scripts/Interactions/SlotItemBehavior.cs +++ b/Assets/Scripts/Interactions/SlotItemBehavior.cs @@ -47,14 +47,13 @@ public class SlotItemBehavior : InteractionRequirementBase SetSlottedObject(obj); } - private void RestoreSlottedObject(Vector3 position) + private void RestoreSlottedObject() { if (_cachedSlottedObject != null) { - _cachedSlottedObject.transform.position = position; _cachedSlottedObject.transform.SetParent(null); - _cachedSlottedObject.SetActive(true); - _cachedSlottedObject = null; + // Do NOT activate or move the object here; it stays hidden until dropped + // Activation is handled by the drop logic elsewhere } } @@ -76,8 +75,10 @@ public class SlotItemBehavior : InteractionRequirementBase // CASE 1: No held item, slot has item -> pick up slotted item if (heldItem == null && _cachedSlottedObject != null) { + // Give the hidden slotted object to the follower (do NOT activate or move it) + RestoreSlottedObject(); follower.SetHeldItemFromObject(_cachedSlottedObject); - RemoveSlottedObject(); + _cachedSlottedObject = null; currentlySlottedItem = null; UpdateSlottedSprite(); return true; @@ -95,7 +96,8 @@ public class SlotItemBehavior : InteractionRequirementBase currentlySlottedItem = followerHeldItem; UpdateSlottedSprite(); - // 2. Give the slot's object to the follower + // 2. Give the slot's object to the follower (do NOT activate or move it) + RestoreSlottedObject(); follower.SetHeldItemFromObject(slotObj); return true; diff --git a/Assets/Scripts/Movement/FollowerController.cs b/Assets/Scripts/Movement/FollowerController.cs index fc26d6c6..3c081ae9 100644 --- a/Assets/Scripts/Movement/FollowerController.cs +++ b/Assets/Scripts/Movement/FollowerController.cs @@ -6,7 +6,7 @@ using Utils; /// /// Controls the follower character, including following the player, handling pickups, and managing held items. /// -public class FollowerController : MonoBehaviour +public class FollowerController : Character { [Header("Follower Settings")] public bool debugDrawTarget = true; @@ -457,5 +457,3 @@ public class FollowerController : MonoBehaviour } } } - -