From e1ff13db30d0dea7d341cfcdbc8b55e167043024 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Thu, 11 Sep 2025 13:00:26 +0200 Subject: [PATCH] Rework interactables into a flatter hierarchy, reenable puzzles as well --- .../Runtime/CustomBootSettings_Runtime.asset | 1 - Assets/Editor/ItemPrefabEditor.cs | 21 +- Assets/Editor/PrefabCreatorWindow.cs | 8 +- Assets/Editor/SceneObjectLocatorWindow.cs | 6 +- .../Prefabs/Characters/PulverCharacter.prefab | 35 +- .../Items/PrefabsPLACEHOLDER/Bunfflers.prefab | 179 ++++++++++ .../PrefabsPLACEHOLDER/Bunfflers.prefab.meta} | 2 +- .../Managers/InteractionManager.prefab | 46 --- .../Scenes/Levels/AppleHillsOverworld.unity | 150 +++----- Assets/Scripts/Characters.meta | 3 - Assets/Scripts/Characters/Character.cs | 11 - Assets/Scripts/Characters/Character.cs.meta | 3 - Assets/Scripts/Core/GameManager.cs | 1 + Assets/Scripts/Core/GameSettings.cs | 1 + Assets/Scripts/Input/InputManager.cs | 24 +- Assets/Scripts/Input/PlayerTouchController.cs | 2 +- .../Interactions/CombineWithBehavior.cs | 45 --- .../Interactions/CombineWithBehavior.cs.meta | 3 - Assets/Scripts/Interactions/Interactable.cs | 249 ++++++++++++-- .../Interactions/InteractionOrchestrator.cs | 218 ------------ .../InteractionOrchestrator.cs.meta | 3 - Assets/Scripts/Interactions/ItemSlot.cs | 145 ++++++++ ...tItemBehavior.cs.meta => ItemSlot.cs.meta} | 0 .../Interactions/OneClickInteraction.cs | 10 +- Assets/Scripts/Interactions/Pickup.cs | 172 +++++----- .../Interactions/RequiresItemBehavior.cs | 41 --- .../Interactions/RequiresItemBehavior.cs.meta | 3 - .../Scripts/Interactions/SlotItemBehavior.cs | 131 ------- Assets/Scripts/LevelS/LevelSwitch.cs | 8 +- Assets/Scripts/Movement/FollowerController.cs | 321 ++++++++++-------- .../Scripts/PuzzleS/ObjectiveStepBehaviour.cs | 156 ++++----- Assets/Scripts/PuzzleS/PuzzleManager.cs | 1 + 32 files changed, 981 insertions(+), 1018 deletions(-) create mode 100644 Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab rename Assets/Prefabs/{Managers/InteractionManager.prefab.meta => Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta} (74%) delete mode 100644 Assets/Prefabs/Managers/InteractionManager.prefab delete mode 100644 Assets/Scripts/Characters.meta delete mode 100644 Assets/Scripts/Characters/Character.cs delete mode 100644 Assets/Scripts/Characters/Character.cs.meta delete mode 100644 Assets/Scripts/Interactions/CombineWithBehavior.cs delete mode 100644 Assets/Scripts/Interactions/CombineWithBehavior.cs.meta delete mode 100644 Assets/Scripts/Interactions/InteractionOrchestrator.cs delete mode 100644 Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta create mode 100644 Assets/Scripts/Interactions/ItemSlot.cs rename Assets/Scripts/Interactions/{SlotItemBehavior.cs.meta => ItemSlot.cs.meta} (100%) delete mode 100644 Assets/Scripts/Interactions/RequiresItemBehavior.cs delete mode 100644 Assets/Scripts/Interactions/RequiresItemBehavior.cs.meta delete mode 100644 Assets/Scripts/Interactions/SlotItemBehavior.cs diff --git a/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset b/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset index a13c9da9..2f934632 100644 --- a/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset +++ b/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset @@ -18,4 +18,3 @@ 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/Editor/ItemPrefabEditor.cs b/Assets/Editor/ItemPrefabEditor.cs index f67d6f4e..f4926896 100644 --- a/Assets/Editor/ItemPrefabEditor.cs +++ b/Assets/Editor/ItemPrefabEditor.cs @@ -1,4 +1,6 @@ -using UnityEditor; +using Interactions; +using PuzzleS; +using UnityEditor; using UnityEngine; namespace Editor @@ -64,27 +66,16 @@ namespace Editor { PrefabEditorUtility.RemoveComponent(_selectedGameObject); } - // CombineWithBehavior - bool hasCombine = _selectedGameObject.GetComponent() != null; - bool addCombine = EditorGUILayout.Toggle("CombineWithBehavior", hasCombine); - if (addCombine && !hasCombine) - { - PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); - } - else if (!addCombine && hasCombine) - { - PrefabEditorUtility.RemoveComponent(_selectedGameObject); - } // SlotItemBehavior - bool hasSlot = _selectedGameObject.GetComponent() != null; + bool hasSlot = _selectedGameObject.GetComponent() != null; bool addSlot = EditorGUILayout.Toggle("SlotItemBehavior", hasSlot); if (addSlot && !hasSlot) { - PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); + PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); } else if (!addSlot && hasSlot) { - PrefabEditorUtility.RemoveComponent(_selectedGameObject); + PrefabEditorUtility.RemoveComponent(_selectedGameObject); } // ObjectiveStepBehaviour bool hasObjective = _selectedGameObject.GetComponent() != null; diff --git a/Assets/Editor/PrefabCreatorWindow.cs b/Assets/Editor/PrefabCreatorWindow.cs index 7fb06838..9887d67c 100644 --- a/Assets/Editor/PrefabCreatorWindow.cs +++ b/Assets/Editor/PrefabCreatorWindow.cs @@ -1,6 +1,8 @@ using UnityEditor; using UnityEngine; using System.IO; +using Interactions; +using PuzzleS; namespace Editor { @@ -121,13 +123,9 @@ namespace Editor var pickup = go.AddComponent(); pickup.itemData = _pickupData; } - if (_addCombineWith) - { - go.AddComponent(); - } if (_addSlot) { - go.AddComponent(); + go.AddComponent(); } if (_addObjective) { diff --git a/Assets/Editor/SceneObjectLocatorWindow.cs b/Assets/Editor/SceneObjectLocatorWindow.cs index 29b73450..cac362a1 100644 --- a/Assets/Editor/SceneObjectLocatorWindow.cs +++ b/Assets/Editor/SceneObjectLocatorWindow.cs @@ -3,6 +3,8 @@ using UnityEngine; using UnityEditor.SceneManagement; using System.Collections.Generic; using System.Linq; +using Interactions; +using PuzzleS; public class SceneObjectLocatorWindow : EditorWindow { @@ -43,12 +45,10 @@ public class SceneObjectLocatorWindow : EditorWindow foreach (var pickup in pickups) { var go = pickup.gameObject; - bool hasCombine = go.GetComponent() != null; - bool hasSlot = go.GetComponent() != null; + bool hasSlot = go.GetComponent() != null; pickupInfos.Add(new PickupInfo { pickup = pickup, - hasCombine = hasCombine, hasSlot = hasSlot }); } diff --git a/Assets/Prefabs/Characters/PulverCharacter.prefab b/Assets/Prefabs/Characters/PulverCharacter.prefab index 83a8f049..890f49ab 100644 --- a/Assets/Prefabs/Characters/PulverCharacter.prefab +++ b/Assets/Prefabs/Characters/PulverCharacter.prefab @@ -13,6 +13,7 @@ GameObject: - component: {fileID: 3435632802124758411} - component: {fileID: 8947209170748834035} - component: {fileID: 7852204877518954380} + - component: {fileID: 1621671461027776358} m_Layer: 8 m_Name: PulverCharacter m_TagString: Pulver @@ -86,16 +87,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f82afe7b57bd4e0b9f51a1cca06765f1, type: 3} m_Name: m_EditorClassIdentifier: - followDistance: 3 debugDrawTarget: 1 followUpdateInterval: 0.1 - manualMoveSmooth: 5 - acceleration: 10 - deceleration: 12 - thresholdFar: 7 - thresholdNear: 5 - stopThreshold: 0.5 - currentlyHeldItem: {fileID: 0} + manualMoveSmooth: 100 + justCombined: 0 heldObjectRenderer: {fileID: 2099200424669714683} --- !u!114 &8947209170748834035 MonoBehaviour: @@ -162,11 +157,33 @@ MonoBehaviour: rotationSpeed: 360 slowdownDistance: 3 pickNextWaypointDist: 2 - endReachedDistance: 0.5 + endReachedDistance: 1 alwaysDrawGizmos: 0 slowWhenNotFacingTarget: 1 whenCloseToDestination: 0 constrainInsideGraph: 0 +--- !u!114 &1621671461027776358 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1102400833121127473} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cb6a34d769a1e4ac7b0b30e433aa443c, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 1 + smoothType: 0 + subdivisions: 2 + iterations: 2 + strength: 0.5 + uniformLength: 1 + maxSegmentLength: 2 + bezierTangentLength: 0.4 + offset: 0.2 + factor: 0.1 --- !u!1 &5934518940303293264 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab b/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab new file mode 100644 index 00000000..8783117e --- /dev/null +++ b/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab @@ -0,0 +1,179 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &8631570451324008562 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8737943614546067711} + - component: {fileID: 4638897979003302452} + - component: {fileID: 2967522604765020532} + - component: {fileID: 6501709216426994228} + - component: {fileID: 7629925223318462841} + m_Layer: 0 + m_Name: Bunfflers + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8737943614546067711 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8631570451324008562} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -23.31, y: -12.06, z: 0} + m_LocalScale: {x: 2, y: 2, z: 2} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &4638897979003302452 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8631570451324008562} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: -8848419083337551572, guid: b285cfb6530624a44a95ec99a59d7215, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1.74, y: 1.59} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!61 &2967522604765020532 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8631570451324008562} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0.00000047683716, y: -0.06357646} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 1.74, y: 1.59} + newSize: {x: 1.74, y: 1.59} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 3.011527, y: 2.861527} + m_EdgeRadius: 0 +--- !u!114 &6501709216426994228 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8631570451324008562} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 73d6494a73174ffabc6a7d3089d51e73, type: 3} + m_Name: + m_EditorClassIdentifier: + isOneTime: 0 + cooldown: -1 + characterToInteract: 0 + interactionStarted: + m_PersistentCalls: + m_Calls: [] + interactionInterrupted: + m_PersistentCalls: + m_Calls: [] + characterArrived: + m_PersistentCalls: + m_Calls: [] + interactionComplete: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &7629925223318462841 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8631570451324008562} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7846448751da4bdbaaa5cb87890dca42, type: 3} + m_Name: + m_EditorClassIdentifier: + itemData: {fileID: 11400000, guid: 6934dcb56c610c44da228f7f24ca13c9, type: 2} + iconRenderer: {fileID: 4638897979003302452} diff --git a/Assets/Prefabs/Managers/InteractionManager.prefab.meta b/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta similarity index 74% rename from Assets/Prefabs/Managers/InteractionManager.prefab.meta rename to Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta index 14b39a04..488f0fad 100644 --- a/Assets/Prefabs/Managers/InteractionManager.prefab.meta +++ b/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eaab28d7e21337b4baef062e2977e616 +guid: 47ac9229b9b128041a770b193eb43b02 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Prefabs/Managers/InteractionManager.prefab b/Assets/Prefabs/Managers/InteractionManager.prefab deleted file mode 100644 index dcb39eca..00000000 --- a/Assets/Prefabs/Managers/InteractionManager.prefab +++ /dev/null @@ -1,46 +0,0 @@ -%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/Scenes/Levels/AppleHillsOverworld.unity b/Assets/Scenes/Levels/AppleHillsOverworld.unity index e8f6a468..f3daf791 100644 --- a/Assets/Scenes/Levels/AppleHillsOverworld.unity +++ b/Assets/Scenes/Levels/AppleHillsOverworld.unity @@ -190,7 +190,7 @@ Transform: m_GameObject: {fileID: 100481742} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalPosition: {x: -4, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -299,6 +299,10 @@ PrefabInstance: propertyPath: stepData value: objectReference: {fileID: 11400000, guid: 13b0c411066f85a41ba40c3bbbc281ed, type: 2} + - target: {fileID: 6254953093500072797, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} - target: {fileID: 6303063351359542479, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} propertyPath: m_Sprite value: @@ -494,6 +498,10 @@ PrefabInstance: propertyPath: m_Name value: Quarry objectReference: {fileID: 0} + - target: {fileID: 8086761134767870039, guid: 539b408cd1191614abdcd99506f1157d, type: 3} + propertyPath: characterToInteract + value: 0 + objectReference: {fileID: 0} - target: {fileID: 9191656170436146298, guid: 539b408cd1191614abdcd99506f1157d, type: 3} propertyPath: m_Sprite value: @@ -567,17 +575,23 @@ PrefabInstance: propertyPath: m_Enabled value: 1 objectReference: {fileID: 0} + - target: {fileID: 6254953093500072797, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} + propertyPath: isOneTime + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6254953093500072797, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} - target: {fileID: 6350287859698694726, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} propertyPath: m_Name value: TestAss objectReference: {fileID: 0} - m_RemovedComponents: [] + m_RemovedComponents: + - {fileID: 4778083634590203921, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: - - targetCorrespondingSourceObject: {fileID: 6350287859698694726, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} - insertIndex: -1 - addedObject: {fileID: 800207724} + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} --- !u!1 &384576743 GameObject: @@ -737,6 +751,10 @@ PrefabInstance: propertyPath: stepData value: objectReference: {fileID: 11400000, guid: 9de0c57af6191384e96e2ba7c04a3d0d, type: 2} + - target: {fileID: 6254953093500072797, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} - target: {fileID: 6303063351359542479, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} propertyPath: m_Sprite value: @@ -755,29 +773,6 @@ Transform: m_CorrespondingSourceObject: {fileID: 2844046668579196942, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} m_PrefabInstance: {fileID: 368640488} m_PrefabAsset: {fileID: 0} ---- !u!1 &800207718 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 6350287859698694726, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} - m_PrefabInstance: {fileID: 368640488} - m_PrefabAsset: {fileID: 0} ---- !u!114 &800207724 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 800207718} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 21401a3b30134380bb205964d9e5c67d, type: 3} - m_Name: - m_EditorClassIdentifier: - OnSuccess: - m_PersistentCalls: - m_Calls: [] - OnFailure: - m_PersistentCalls: - m_Calls: [] --- !u!1 &948124904 GameObject: m_ObjectHideFlags: 0 @@ -982,6 +977,10 @@ PrefabInstance: propertyPath: stepData value: objectReference: {fileID: 11400000, guid: a84cbe9804e13f74e857c55d90cc10d1, type: 2} + - target: {fileID: 6254953093500072797, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} - target: {fileID: 6303063351359542479, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} propertyPath: m_Sprite value: @@ -1103,6 +1102,10 @@ PrefabInstance: propertyPath: m_WasSpriteAssigned value: 1 objectReference: {fileID: 0} + - target: {fileID: 7616859841301711022, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] @@ -1446,7 +1449,16 @@ PrefabInstance: propertyPath: m_WasSpriteAssigned value: 1 objectReference: {fileID: 0} - m_RemovedComponents: [] + - target: {fileID: 7616859841301711022, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} + propertyPath: isOneTime + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7616859841301711022, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} + m_RemovedComponents: + - {fileID: 592045584872845087, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} m_RemovedGameObjects: [] m_AddedGameObjects: - targetCorrespondingSourceObject: {fileID: 1730119453103664125, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} @@ -1562,37 +1574,15 @@ PrefabInstance: propertyPath: m_WasSpriteAssigned value: 1 objectReference: {fileID: 0} + - target: {fileID: 7616859841301711022, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} + propertyPath: characterToInteract + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: - - targetCorrespondingSourceObject: {fileID: 7447346505753002421, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} - insertIndex: -1 - addedObject: {fileID: 1578994557} + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} ---- !u!1 &1578994556 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 7447346505753002421, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} - m_PrefabInstance: {fileID: 1578994555} - m_PrefabAsset: {fileID: 0} ---- !u!114 &1578994557 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1578994556} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 21401a3b30134380bb205964d9e5c67d, type: 3} - m_Name: - m_EditorClassIdentifier: - OnSuccess: - m_PersistentCalls: - m_Calls: [] - OnFailure: - m_PersistentCalls: - m_Calls: [] --- !u!4 &1627665103 stripped Transform: m_CorrespondingSourceObject: {fileID: 2844046668579196942, guid: b5fc01af35233eb4cbeede05e50a7c34, type: 3} @@ -1620,14 +1610,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ec1a2e6e32f746c4990c579e13b79104, type: 3} m_Name: m_EditorClassIdentifier: - OnSuccess: - m_PersistentCalls: - m_Calls: [] - OnFailure: - m_PersistentCalls: - m_Calls: [] - currentlySlottedItem: {fileID: 0} + itemData: {fileID: 11400000, guid: e0fad48a84a6b6346ac17c84bc512500, type: 2} + iconRenderer: {fileID: 1631660128} slottedItemRenderer: {fileID: 124275613} +--- !u!212 &1631660128 stripped +SpriteRenderer: + m_CorrespondingSourceObject: {fileID: 7494677664706785084, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} + m_PrefabInstance: {fileID: 1336824707} + m_PrefabAsset: {fileID: 0} --- !u!1 &1741016587 GameObject: m_ObjectHideFlags: 0 @@ -1716,7 +1706,7 @@ Transform: m_GameObject: {fileID: 1741016587} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalPosition: {x: -4, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -1974,34 +1964,6 @@ 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} - - target: {fileID: 7852204877518954380, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3} - propertyPath: endReachedDistance - value: 1 - objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] @@ -2017,7 +1979,7 @@ PrefabInstance: m_Modifications: - target: {fileID: 3823830588451517910, guid: 301b4e0735896334f8f6fb9a68a7e419, type: 3} propertyPath: m_LocalPosition.x - value: 0 + value: -4 objectReference: {fileID: 0} - target: {fileID: 3823830588451517910, guid: 301b4e0735896334f8f6fb9a68a7e419, type: 3} propertyPath: m_LocalPosition.y diff --git a/Assets/Scripts/Characters.meta b/Assets/Scripts/Characters.meta deleted file mode 100644 index ca2412db..00000000 --- a/Assets/Scripts/Characters.meta +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 1c3a97c5..00000000 --- a/Assets/Scripts/Characters/Character.cs +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 90416577..00000000 --- a/Assets/Scripts/Characters/Character.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 9253c7c4ca6946b1b31196c4bf8d685e -timeCreated: 1757513074 \ No newline at end of file diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index 84f03721..452e5938 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -59,6 +59,7 @@ public class GameManager : MonoBehaviour public float HeldIconDisplayHeight => gameSettings != null ? gameSettings.heldIconDisplayHeight : 2.0f; public GameObject BasePickupPrefab => gameSettings != null ? gameSettings.basePickupPrefab : null; public LayerMask InteractableLayerMask => gameSettings != null ? gameSettings.interactableLayerMask : -1; + public float PlayerStopDistanceDirectInteraction => gameSettings != null ? gameSettings.playerStopDistanceDirectInteraction : 2.0f; /// /// Returns the combination rule for two items, if any. diff --git a/Assets/Scripts/Core/GameSettings.cs b/Assets/Scripts/Core/GameSettings.cs index 6e33400e..3d4aa9a7 100644 --- a/Assets/Scripts/Core/GameSettings.cs +++ b/Assets/Scripts/Core/GameSettings.cs @@ -8,6 +8,7 @@ public class GameSettings : ScriptableObject { [Header("Interactions")] public float playerStopDistance = 6.0f; + public float playerStopDistanceDirectInteraction = 2.0f; public float followerPickupDelay = 0.2f; [Header("Follower Settings")] diff --git a/Assets/Scripts/Input/InputManager.cs b/Assets/Scripts/Input/InputManager.cs index 1c92b883..41b32a0f 100644 --- a/Assets/Scripts/Input/InputManager.cs +++ b/Assets/Scripts/Input/InputManager.cs @@ -157,34 +157,14 @@ public class InputManager : MonoBehaviour Collider2D hit = Physics2D.OverlapPoint(worldPos, mask); if (hit != null) { - var interactable = hit.GetComponent(); - if (interactable != null) - { - Debug.unityLogger.Log("Interactable", $"[InputManager] Delegating tap to interactable at {worldPos} (GameObject: {hit.gameObject.name})"); - // 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) { + Debug.unityLogger.Log("Interactable", $"[InputManager] Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name})"); consumer.OnTap(worldPos); return true; } - else - { - Debug.unityLogger.Log("Interactable", $"[InputManager] Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found."); - } + Debug.unityLogger.Log("Interactable", $"[InputManager] Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found."); } else { diff --git a/Assets/Scripts/Input/PlayerTouchController.cs b/Assets/Scripts/Input/PlayerTouchController.cs index 49674c1e..8a853e77 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 : Character, ITouchInputConsumer + public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer { // --- Movement State --- private Vector3 targetPosition; diff --git a/Assets/Scripts/Interactions/CombineWithBehavior.cs b/Assets/Scripts/Interactions/CombineWithBehavior.cs deleted file mode 100644 index 8aa1247c..00000000 --- a/Assets/Scripts/Interactions/CombineWithBehavior.cs +++ /dev/null @@ -1,45 +0,0 @@ -using UnityEngine; - -/// -/// Interaction requirement that allows combining the follower's held item with this pickup if a valid combination rule exists. -/// -[RequireComponent(typeof(Pickup))] -public class CombineWithBehavior : InteractionRequirementBase -{ - /// - /// Attempts to combine the follower's held item with this pickup's item. - /// - /// The follower attempting the interaction. - /// True if the combination was successful, false otherwise. - public override bool TryInteract(FollowerController follower) - { - var heldItem = follower.GetHeldPickupObject(); - var pickup = GetComponent(); - if (heldItem == null) - { - // DebugUIMessage.Show("You need an item to combine."); - OnFailure?.Invoke(); - return true; - } - if (pickup == null || pickup.itemData == null) - { - DebugUIMessage.Show("Target item is missing or invalid."); - OnFailure?.Invoke(); - return false; - } - var combinedItem = InteractionOrchestrator.Instance.CombineItems(heldItem, pickup.gameObject); - if (combinedItem != null) - { - InteractionOrchestrator.Instance.PickupItem(follower, combinedItem); - follower.justCombined = true; - OnSuccess?.Invoke(); - return true; - } - else - { - // DebugUIMessage.Show($"Cannot combine {follower.CurrentlyHeldItem?.itemName ?? "an item"} with {pickup.itemData.itemName ?? "target item"}."); - OnFailure?.Invoke(); - return true; - } - } -} diff --git a/Assets/Scripts/Interactions/CombineWithBehavior.cs.meta b/Assets/Scripts/Interactions/CombineWithBehavior.cs.meta deleted file mode 100644 index 8670e44e..00000000 --- a/Assets/Scripts/Interactions/CombineWithBehavior.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 21401a3b30134380bb205964d9e5c67d -timeCreated: 1756981777 \ No newline at end of file diff --git a/Assets/Scripts/Interactions/Interactable.cs b/Assets/Scripts/Interactions/Interactable.cs index 517ad3fa..c6ed85c2 100644 --- a/Assets/Scripts/Interactions/Interactable.cs +++ b/Assets/Scripts/Interactions/Interactable.cs @@ -1,41 +1,230 @@ -using UnityEngine; +using Input; +using UnityEngine; using System; +using UnityEngine.Events; -/// -/// Represents an interactable object that can respond to tap input events. -/// -public class Interactable : MonoBehaviour, ITouchInputConsumer +namespace Interactions { - public event Action StartedInteraction; - public event Action InteractionComplete; - - /// - /// Handles tap input. Triggers interaction logic. - /// - public void OnTap(Vector2 worldPosition) + public enum CharacterToInteract { - Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}"); - StartedInteraction?.Invoke(); + Trafalgar, + Pulver } - - // No hold behavior for interactables. - public void OnHoldStart(Vector2 worldPosition) { } - public void OnHoldMove(Vector2 worldPosition) { } - public void OnHoldEnd(Vector2 worldPosition) { } - /// - /// Called to interact with this object by a character (player, follower, etc). + /// Represents an interactable object that can respond to tap input events. /// - public virtual void OnInteract(Character character) + public class Interactable : MonoBehaviour, ITouchInputConsumer { - // 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); - } + [Header("Interaction Settings")] + public bool isOneTime = false; + public float cooldown = -1f; + public CharacterToInteract characterToInteract = CharacterToInteract.Pulver; + + [Header("Interaction Events")] + public UnityEvent interactionStarted; + public UnityEvent interactionInterrupted; + public UnityEvent characterArrived; + public UnityEvent interactionComplete; - public void CompleteInteraction(bool success) - { - InteractionComplete?.Invoke(success); + // Helpers for managing interaction state + private bool _interactionInProgress; + private PlayerTouchController _playerRef; + private FollowerController _followerController; + + private bool _isActive = true; + + private void Awake() + { + // Subscribe to interactionComplete event + interactionComplete.AddListener(OnInteractionComplete); + } + + /// + /// Handles tap input. Triggers interaction logic. + /// + public void OnTap(Vector2 worldPosition) + { + if (!_isActive) + { + Debug.Log($"[Interactable] Is disabled!"); + return; + } + Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}"); + // Broadcast interaction started event + TryInteract(); + } + + public void TryInteract() + { + _interactionInProgress = true; + + _playerRef = FindFirstObjectByType(); + _followerController = FindFirstObjectByType(); + + interactionStarted?.Invoke(_playerRef, _followerController); + + if (_playerRef == null) + { + Debug.Log($"[Interactable] Player character could not be found. Aborting interaction."); + interactionInterrupted.Invoke(); + return; + } + + // Compute closest point on the interaction radius + Vector3 interactablePos = transform.position; + Vector3 playerPos = _playerRef.transform.position; + float stopDistance = characterToInteract == CharacterToInteract.Pulver + ? GameManager.Instance.PlayerStopDistance + : GameManager.Instance.PlayerStopDistanceDirectInteraction; + Vector3 toPlayer = (playerPos - interactablePos).normalized; + Vector3 stopPoint = interactablePos + toPlayer * stopDistance; + + // Unsubscribe previous to avoid duplicate calls + _playerRef.OnArrivedAtTarget -= OnPlayerArrived; + _playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled; + _playerRef.OnArrivedAtTarget += OnPlayerArrived; + _playerRef.OnMoveToCancelled += OnPlayerMoveCancelled; + _playerRef.MoveToAndNotify(stopPoint); + } + + private void OnPlayerMoveCancelled() + { + _interactionInProgress = false; + interactionInterrupted?.Invoke(); + } + + private void OnPlayerArrived() + { + if (!_interactionInProgress) + return; + + // Unsubscribe to avoid memory leaks + _playerRef.OnArrivedAtTarget -= OnPlayerArrived; + + if (characterToInteract == CharacterToInteract.Pulver) + { + _followerController.OnPickupArrived -= OnFollowerArrived; + _followerController.OnPickupArrived += OnFollowerArrived; + _followerController.GoToPointAndReturn(transform.position, _playerRef.transform); + } + else if (characterToInteract == CharacterToInteract.Trafalgar) + { + BroadcastCharacterArrived(); + } + } + + private void OnFollowerArrived() + { + if (!_interactionInProgress) + return; + + // Unsubscribe to avoid memory leaks + _followerController.OnPickupArrived -= OnFollowerArrived; + + BroadcastCharacterArrived(); + } + + private void BroadcastCharacterArrived() + { + // Check for ObjectiveStepBehaviour and lock state + var step = GetComponent(); + if (step != null && !step.IsStepUnlocked()) + { + DebugUIMessage.Show("This step is locked!", 2f); + BroadcastInteractionComplete(false); + // Reset variables for next time + _interactionInProgress = false; + _playerRef = null; + _followerController = null; + return; + } + // Broadcast appropriate event + characterArrived?.Invoke(); + // Reset variables for next time + _interactionInProgress = false; + _playerRef = null; + _followerController = null; + } + + private void OnInteractionComplete(bool success) + { + if (success) + { + if (isOneTime) + { + _isActive = false; + } + else if (cooldown >= 0f) + { + StartCoroutine(HandleCooldown()); + } + } + } + + private System.Collections.IEnumerator HandleCooldown() + { + _isActive = false; + yield return new WaitForSeconds(cooldown); + _isActive = true; + } + + public void OnHoldStart(Vector2 position) + { + throw new NotImplementedException(); + } + + public void OnHoldMove(Vector2 position) + { + throw new NotImplementedException(); + } + + public void OnHoldEnd(Vector2 position) + { + throw new NotImplementedException(); + } + + public void BroadcastInteractionComplete(bool success) + { + interactionComplete?.Invoke(success); + } + + #if UNITY_EDITOR + /// + /// Draws gizmos for pickup interaction range in the editor. + /// + void OnDrawGizmos() + { + float playerStopDistance; + if (Application.isPlaying) + { + playerStopDistance = characterToInteract == CharacterToInteract.Trafalgar + ? GameManager.Instance.PlayerStopDistanceDirectInteraction + : GameManager.Instance.PlayerStopDistance; + } + else + { + // Load settings directly from asset path in editor + var settings = + UnityEditor.AssetDatabase.LoadAssetAtPath( + "Assets/Data/Settings/DefaultSettings.asset"); + playerStopDistance = settings != null + ? (characterToInteract == CharacterToInteract.Trafalgar + ? settings.playerStopDistanceDirectInteraction + : settings.playerStopDistance) + : 1.0f; + } + + Gizmos.color = Color.yellow; + Gizmos.DrawWireSphere(transform.position, playerStopDistance); + 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 } } diff --git a/Assets/Scripts/Interactions/InteractionOrchestrator.cs b/Assets/Scripts/Interactions/InteractionOrchestrator.cs deleted file mode 100644 index 8899092e..00000000 --- a/Assets/Scripts/Interactions/InteractionOrchestrator.cs +++ /dev/null @@ -1,218 +0,0 @@ -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); - } - } - - // --- ITEM MANAGEMENT API --- - public void PickupItem(FollowerController follower, GameObject item) - { - if (item == null || follower == null) return; - item.SetActive(false); - item.transform.SetParent(null); - follower.SetHeldItemFromObject(item); - // Optionally: fire event, update UI, etc. - } - - public void DropItem(FollowerController follower, Vector3 position) - { - var item = follower.GetHeldPickupObject(); - if (item == null) return; - item.transform.position = position; - item.transform.SetParent(null); - item.SetActive(true); - follower.ClearHeldItem(); - // Optionally: fire event, update UI, etc. - } - - public void SlotItem(SlotItemBehavior slot, GameObject item) - { - if (slot == null || item == null) return; - item.SetActive(false); - item.transform.SetParent(null); - slot.SetSlottedObject(item); - // Optionally: update visuals, fire event, etc. - } - - public void SwapItems(FollowerController follower, SlotItemBehavior slot) - { - var heldObj = follower.GetHeldPickupObject(); - var slotObj = slot.GetSlottedObject(); - // 1. Slot the follower's held object - SlotItem(slot, heldObj); - // 2. Give the slot's object to the follower - PickupItem(follower, slotObj); - } - - public GameObject CombineItems(GameObject itemA, GameObject itemB) - { - if (itemA == null || itemB == null) return null; - var pickupA = itemA.GetComponent(); - var pickupB = itemB.GetComponent(); - if (pickupA == null || pickupB == null) { - if (itemA != null) Destroy(itemA); - if (itemB != null) Destroy(itemB); - return null; - } - var rule = GameManager.Instance.GetCombinationRule(pickupA.itemData, pickupB.itemData); - Vector3 spawnPos = itemA.transform.position; - if (rule != null && rule.resultPrefab != null) { - var newItem = Instantiate(rule.resultPrefab, spawnPos, Quaternion.identity); - Destroy(itemA); - Destroy(itemB); - return newItem; - } - // If no combination found, just destroy both - Destroy(itemA); - Destroy(itemB); - return null; - } -} diff --git a/Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta b/Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta deleted file mode 100644 index afd3e441..00000000 --- a/Assets/Scripts/Interactions/InteractionOrchestrator.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 705c4ee7f8204cc68aacd79e2a4a506d -timeCreated: 1757513080 \ No newline at end of file diff --git a/Assets/Scripts/Interactions/ItemSlot.cs b/Assets/Scripts/Interactions/ItemSlot.cs new file mode 100644 index 00000000..b8b3009e --- /dev/null +++ b/Assets/Scripts/Interactions/ItemSlot.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Interactions +{ + /// + /// Interaction requirement that allows slotting, swapping, or picking up items in a slot. + /// + [RequireComponent(typeof(Interactable))] + public class ItemSlot : Pickup + { + private PickupItemData _currentlySlottedItemData; + public SpriteRenderer slottedItemRenderer; + private GameObject _currentlySlottedItemObject = null; + + + public GameObject GetSlottedObject() + { + return _currentlySlottedItemObject; + } + + public void SetSlottedObject(GameObject obj) + { + _currentlySlottedItemObject = obj; + if (_currentlySlottedItemObject != null) + { + _currentlySlottedItemObject.SetActive(false); + } + } + + protected override void OnCharacterArrived() + { + var heldItemData = FollowerController.CurrentlyHeldItemData; + var heldItemObj = FollowerController.GetHeldPickupObject(); + var pickup = GetComponent(); + var slotItem = pickup != null ? pickup.itemData : null; + var config = GameManager.Instance.GetSlotItemConfig(slotItem); + var allowed = config?.allowedItems ?? new List(); + var forbidden = config?.forbiddenItems ?? new List(); + + if ((heldItemData == null && _currentlySlottedItemObject != null) + || (heldItemData != null && _currentlySlottedItemObject != null)) + { + FollowerController.TryPickupItem(_currentlySlottedItemObject, _currentlySlottedItemData); + _currentlySlottedItemObject = null; + _currentlySlottedItemData = null; + UpdateSlottedSprite(); + return; + } + + // // CASE 1: No held item, slot has item -> pick up slotted item + // if (heldItemData == null && _cachedSlottedObject != null) + // { + // InteractionOrchestrator.Instance.PickupItem(FollowerController, _cachedSlottedObject); + // _cachedSlottedObject = null; + // currentlySlottedItem = null; + // UpdateSlottedSprite(); + // Interactable.BroadcastInteractionComplete(false); + // return; + // } + // // CASE 2: Held item, slot has item -> swap + // if + // { + // InteractionOrchestrator.Instance.SwapItems(FollowerController, this); + // currentlySlottedItem = heldItemData; + // UpdateSlottedSprite(); + // return; + // } + // CASE 3: Held item, slot empty -> slot the held item + if (heldItemData != null && _currentlySlottedItemObject == null) + { + if (forbidden.Contains(heldItemData)) + { + DebugUIMessage.Show("Can't place that here."); + Interactable.BroadcastInteractionComplete(false); + return; + } + + SlotItem(heldItemObj, heldItemData); + if (allowed.Contains(heldItemData)) + { + Interactable.BroadcastInteractionComplete(true); + return; + } + else + { + DebugUIMessage.Show("I'm not sure this works."); + Interactable.BroadcastInteractionComplete(false); + return; + } + } + + // CASE 4: No held item, slot empty -> show warning + if (heldItemData == null && _currentlySlottedItemObject == null) + { + DebugUIMessage.Show("This requires an item."); + return; + } + return; + } + + /// + /// Updates the sprite and scale for the currently slotted item. + /// + private void UpdateSlottedSprite() + { + if (slottedItemRenderer != null && _currentlySlottedItemData != null && _currentlySlottedItemData.mapSprite != null) + { + slottedItemRenderer.sprite = _currentlySlottedItemData.mapSprite; + // Scale sprite to desired height, preserve aspect ratio, compensate for parent scale + float desiredHeight = GameManager.Instance.HeldIconDisplayHeight; + var sprite = _currentlySlottedItemData.mapSprite; + float spriteHeight = sprite.bounds.size.y; + float spriteWidth = sprite.bounds.size.x; + Vector3 parentScale = slottedItemRenderer.transform.parent != null + ? slottedItemRenderer.transform.parent.localScale + : Vector3.one; + if (spriteHeight > 0f) + { + float uniformScale = desiredHeight / spriteHeight; + float scale = uniformScale / Mathf.Max(parentScale.x, parentScale.y); + slottedItemRenderer.transform.localScale = new Vector3(scale, scale, 1f); + } + } + else if (slottedItemRenderer != null) + { + slottedItemRenderer.sprite = null; + } + } + + public void SlotItem(GameObject itemToSlot, PickupItemData itemToSlotData) + { + if (itemToSlot == null) + return; + + itemToSlot.SetActive(false); + itemToSlot.transform.SetParent(null); + SetSlottedObject(itemToSlot); + + _currentlySlottedItemData = itemToSlotData; + UpdateSlottedSprite(); + FollowerController.ClearHeldItem(); + } + } +} diff --git a/Assets/Scripts/Interactions/SlotItemBehavior.cs.meta b/Assets/Scripts/Interactions/ItemSlot.cs.meta similarity index 100% rename from Assets/Scripts/Interactions/SlotItemBehavior.cs.meta rename to Assets/Scripts/Interactions/ItemSlot.cs.meta diff --git a/Assets/Scripts/Interactions/OneClickInteraction.cs b/Assets/Scripts/Interactions/OneClickInteraction.cs index c53fa477..fbd879be 100644 --- a/Assets/Scripts/Interactions/OneClickInteraction.cs +++ b/Assets/Scripts/Interactions/OneClickInteraction.cs @@ -1,5 +1,7 @@ using UnityEngine; using System; +using Input; +using Interactions; /// /// MonoBehaviour that immediately completes an interaction when started. @@ -13,7 +15,7 @@ public class OneClickInteraction : MonoBehaviour interactable = GetComponent(); if (interactable != null) { - interactable.StartedInteraction += OnStartedInteraction; + interactable.interactionStarted.AddListener(OnInteractionStarted); } } @@ -21,15 +23,15 @@ public class OneClickInteraction : MonoBehaviour { if (interactable != null) { - interactable.StartedInteraction -= OnStartedInteraction; + interactable.interactionStarted.RemoveListener(OnInteractionStarted); } } - private void OnStartedInteraction() + private void OnInteractionStarted(PlayerTouchController playerRef, FollowerController followerRef) { if (interactable != null) { - interactable.CompleteInteraction(true); + interactable.BroadcastInteractionComplete(true); } } } diff --git a/Assets/Scripts/Interactions/Pickup.cs b/Assets/Scripts/Interactions/Pickup.cs index 415ef5a6..864667f7 100644 --- a/Assets/Scripts/Interactions/Pickup.cs +++ b/Assets/Scripts/Interactions/Pickup.cs @@ -1,115 +1,95 @@ using Input; using UnityEngine; -public class Pickup : MonoBehaviour +namespace Interactions { - /// - /// Data for the pickup item (icon, name, etc). - /// - public PickupItemData itemData; - /// - /// Renderer for the pickup icon. - /// - public SpriteRenderer iconRenderer; - private Interactable interactable; - - /// - /// Unity Awake callback. Sets up icon, interactable, and event handlers. - /// - void Awake() + [RequireComponent(typeof(Interactable))] + public class Pickup : MonoBehaviour { - if (iconRenderer == null) - iconRenderer = GetComponent(); - interactable = GetComponent(); - if (interactable != null) + public PickupItemData itemData; + public SpriteRenderer iconRenderer; + protected Interactable Interactable; + private PlayerTouchController _playerRef; + protected FollowerController FollowerController; + + /// + /// Unity Awake callback. Sets up icon, interactable, and event handlers. + /// + void Awake() { - interactable.StartedInteraction += OnStartedInteraction; - interactable.InteractionComplete += OnInteractionComplete; - } - ApplyItemData(); - } + if (iconRenderer == null) + iconRenderer = GetComponent(); + + Interactable = GetComponent(); + if (Interactable != null) + { + Interactable.interactionStarted.AddListener(OnInteractionStarted); + Interactable.characterArrived.AddListener(OnCharacterArrived); + } - /// - /// Unity OnDestroy callback. Cleans up event handlers. - /// - void OnDestroy() - { - if (interactable != null) - { - interactable.StartedInteraction -= OnStartedInteraction; - interactable.InteractionComplete -= OnInteractionComplete; + ApplyItemData(); + } + + /// + /// Unity OnDestroy callback. Cleans up event handlers. + /// + void OnDestroy() + { + if (Interactable != null) + { + Interactable.interactionStarted.RemoveListener(OnInteractionStarted); + Interactable.characterArrived.RemoveListener(OnCharacterArrived); + } } - } #if UNITY_EDITOR - /// - /// Unity OnValidate callback. Ensures icon and data are up to date in editor. - /// - void OnValidate() - { - if (iconRenderer == null) - iconRenderer = GetComponent(); - ApplyItemData(); - } - - /// - /// Draws gizmos for pickup interaction range in the editor. - /// - void OnDrawGizmos() - { - float playerStopDistance; - if (Application.isPlaying) + /// + /// Unity OnValidate callback. Ensures icon and data are up to date in editor. + /// + void OnValidate() { - playerStopDistance = GameManager.Instance.PlayerStopDistance; + if (iconRenderer == null) + iconRenderer = GetComponent(); + ApplyItemData(); } - else - { - // Load settings directly from asset path in editor - var settings = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/Data/Settings/DefaultSettings.asset"); - playerStopDistance = settings != null ? settings.playerStopDistance : 1.0f; - } - Gizmos.color = Color.yellow; - Gizmos.DrawWireSphere(transform.position, playerStopDistance); - 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 - /// - /// Applies the item data to the pickup (icon, name, etc). - /// - public void ApplyItemData() - { - if (itemData != null) + /// + /// Applies the item data to the pickup (icon, name, etc). + /// + public void ApplyItemData() { - if (iconRenderer != null && itemData.mapSprite != null) + if (itemData != null) { - iconRenderer.sprite = itemData.mapSprite; + if (iconRenderer != null && itemData.mapSprite != null) + { + iconRenderer.sprite = itemData.mapSprite; + } + + gameObject.name = itemData.itemName; } - gameObject.name = itemData.itemName; + } + + /// + /// Handles the start of an interaction (for feedback/UI only). + /// + private void OnInteractionStarted(PlayerTouchController playerRef, FollowerController followerRef) + { + _playerRef = playerRef; + FollowerController = followerRef; + } + + protected virtual void OnCharacterArrived() + { + var combinationResult = FollowerController.TryCombineItems(this, out var combinationResultItem); + if (combinationResultItem != null) + { + Interactable.BroadcastInteractionComplete(true); + return; + } + + FollowerController?.TryPickupItem(gameObject, itemData); + Interactable.BroadcastInteractionComplete(combinationResult == FollowerController.CombinationResult.NotApplicable); } } - - /// - /// Handles the start of an interaction (for feedback/UI only). - /// - private void OnStartedInteraction() - { - // Optionally, add pickup-specific feedback here (e.g., highlight, sound). - } - - /// - /// Handles completion of the interaction (e.g., after pickup is done). - /// - /// Whether the interaction was successful. - private void OnInteractionComplete(bool success) - { - if (!success) return; - // Optionally, add logic to disable the pickup or provide feedback - } -} +} \ No newline at end of file diff --git a/Assets/Scripts/Interactions/RequiresItemBehavior.cs b/Assets/Scripts/Interactions/RequiresItemBehavior.cs deleted file mode 100644 index 2368f3e4..00000000 --- a/Assets/Scripts/Interactions/RequiresItemBehavior.cs +++ /dev/null @@ -1,41 +0,0 @@ -using UnityEngine; - -/// -/// Interaction requirement that checks if the follower is holding a specific required item. -/// -[RequireComponent(typeof(Interactable))] -public class RequiresItemBehavior : InteractionRequirementBase -{ - [Header("Required Item")] - public PickupItemData requiredItem; - - /// - /// Attempts to interact, succeeds only if the follower is holding the required item. - /// - /// The follower attempting the interaction. - /// True if the interaction was successful, false otherwise. - public override bool TryInteract(FollowerController follower) - { - var heldItem = follower.CurrentlyHeldItem; - if (heldItem == requiredItem) - { - OnSuccess?.Invoke(); - return true; - } - else - { - string requiredName = requiredItem != null ? requiredItem.itemName : "required item"; - if (heldItem == null) - { - DebugUIMessage.Show($"You need {requiredName} to interact."); - } - else - { - string heldName = heldItem.itemName ?? "an item"; - DebugUIMessage.Show($"You need {requiredName}, but you are holding {heldName}."); - } - OnFailure?.Invoke(); - return false; - } - } -} diff --git a/Assets/Scripts/Interactions/RequiresItemBehavior.cs.meta b/Assets/Scripts/Interactions/RequiresItemBehavior.cs.meta deleted file mode 100644 index a3aa968d..00000000 --- a/Assets/Scripts/Interactions/RequiresItemBehavior.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 31103d67032c44a9b95ec014babe2c62 -timeCreated: 1756981777 \ No newline at end of file diff --git a/Assets/Scripts/Interactions/SlotItemBehavior.cs b/Assets/Scripts/Interactions/SlotItemBehavior.cs deleted file mode 100644 index 9189e40d..00000000 --- a/Assets/Scripts/Interactions/SlotItemBehavior.cs +++ /dev/null @@ -1,131 +0,0 @@ -using UnityEngine; -using UnityEngine.Events; -using System.Collections.Generic; - -/// -/// Interaction requirement that allows slotting, swapping, or picking up items in a slot. -/// -[RequireComponent(typeof(Interactable))] -[RequireComponent(typeof(Pickup))] -public class SlotItemBehavior : InteractionRequirementBase -{ - [Header("Slot State")] - /// - /// The item currently slotted in this slot. - /// - public PickupItemData currentlySlottedItem; - /// - /// The renderer for the slotted item's sprite. - /// - public SpriteRenderer slottedItemRenderer; - - private GameObject _cachedSlottedObject = null; - - public GameObject GetSlottedObject() - { - return _cachedSlottedObject; - } - - public void SetSlottedObject(GameObject obj) - { - _cachedSlottedObject = obj; - if (_cachedSlottedObject != null) - { - _cachedSlottedObject.SetActive(false); - } - } - - /// - /// Attempts to interact with the slot, handling slotting, swapping, or picking up items. - /// - /// The follower attempting the interaction. - /// True if the interaction was successful, false otherwise. - public override bool TryInteract(FollowerController follower) - { - var heldItem = follower.CurrentlyHeldItem; - var heldObj = follower.GetHeldPickupObject(); - var pickup = GetComponent(); - var slotItem = pickup != null ? pickup.itemData : null; - var config = GameManager.Instance.GetSlotItemConfig(slotItem); - var allowed = config?.allowedItems ?? new List(); - var forbidden = config?.forbiddenItems ?? new List(); - - // CASE 1: No held item, slot has item -> pick up slotted item - if (heldItem == null && _cachedSlottedObject != null) - { - InteractionOrchestrator.Instance.PickupItem(follower, _cachedSlottedObject); - _cachedSlottedObject = null; - currentlySlottedItem = null; - UpdateSlottedSprite(); - return true; - } - // CASE 2: Held item, slot has item -> swap - if (heldItem != null && _cachedSlottedObject != null) - { - InteractionOrchestrator.Instance.SwapItems(follower, this); - currentlySlottedItem = heldItem; - UpdateSlottedSprite(); - return true; - } - // CASE 3: Held item, slot empty -> slot the held item - if (heldItem != null && _cachedSlottedObject == null) - { - if (forbidden.Contains(heldItem)) - { - DebugUIMessage.Show("Can't place that here."); - return false; - } - InteractionOrchestrator.Instance.SlotItem(this, heldObj); - currentlySlottedItem = heldItem; - UpdateSlottedSprite(); - follower.ClearHeldItem(); - if (allowed.Contains(heldItem)) - { - OnSuccess?.Invoke(); - return true; - } - else - { - DebugUIMessage.Show("I'm not sure this works."); - OnFailure?.Invoke(); - return true; - } - } - // CASE 4: No held item, slot empty -> show warning - if (heldItem == null && _cachedSlottedObject == null) - { - DebugUIMessage.Show("This requires an item."); - return false; - } - return false; - } - - /// - /// Updates the sprite and scale for the currently slotted item. - /// - private void UpdateSlottedSprite() - { - if (slottedItemRenderer != null && currentlySlottedItem != null && currentlySlottedItem.mapSprite != null) - { - slottedItemRenderer.sprite = currentlySlottedItem.mapSprite; - // Scale sprite to desired height, preserve aspect ratio, compensate for parent scale - float desiredHeight = GameManager.Instance.HeldIconDisplayHeight; - var sprite = currentlySlottedItem.mapSprite; - float spriteHeight = sprite.bounds.size.y; - float spriteWidth = sprite.bounds.size.x; - Vector3 parentScale = slottedItemRenderer.transform.parent != null - ? slottedItemRenderer.transform.parent.localScale - : Vector3.one; - if (spriteHeight > 0f) - { - float uniformScale = desiredHeight / spriteHeight; - float scale = uniformScale / Mathf.Max(parentScale.x, parentScale.y); - slottedItemRenderer.transform.localScale = new Vector3(scale, scale, 1f); - } - } - else if (slottedItemRenderer != null) - { - slottedItemRenderer.sprite = null; - } - } -} diff --git a/Assets/Scripts/LevelS/LevelSwitch.cs b/Assets/Scripts/LevelS/LevelSwitch.cs index d122f16f..cbc29ff6 100644 --- a/Assets/Scripts/LevelS/LevelSwitch.cs +++ b/Assets/Scripts/LevelS/LevelSwitch.cs @@ -1,4 +1,6 @@ using System; +using Input; +using Interactions; using UnityEngine; /// @@ -26,7 +28,7 @@ public class LevelSwitch : MonoBehaviour _interactable = GetComponent(); if (_interactable != null) { - _interactable.StartedInteraction += OnStartedInteraction; + _interactable.characterArrived.AddListener(OnCharacterArrived); } ApplySwitchData(); } @@ -38,7 +40,7 @@ public class LevelSwitch : MonoBehaviour { if (_interactable != null) { - _interactable.StartedInteraction -= OnStartedInteraction; + _interactable.characterArrived.RemoveListener(OnCharacterArrived); } } @@ -71,7 +73,7 @@ public class LevelSwitch : MonoBehaviour /// /// Handles the start of an interaction (shows confirmation menu and switches the level if confirmed). /// - private void OnStartedInteraction() + private void OnCharacterArrived() { if (switchData == null || string.IsNullOrEmpty(switchData.targetLevelSceneName) || !_isActive) return; diff --git a/Assets/Scripts/Movement/FollowerController.cs b/Assets/Scripts/Movement/FollowerController.cs index f194bb81..ef3fe55b 100644 --- a/Assets/Scripts/Movement/FollowerController.cs +++ b/Assets/Scripts/Movement/FollowerController.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using Interactions; +using UnityEngine; using Pathfinding; using UnityEngine.SceneManagement; using Utils; @@ -6,7 +7,7 @@ using Utils; /// /// Controls the follower character, including following the player, handling pickups, and managing held items. /// -public class FollowerController : Character +public class FollowerController: MonoBehaviour { [Header("Follower Settings")] public bool debugDrawTarget = true; @@ -29,10 +30,13 @@ public class FollowerController : Character private float _currentSpeed = 0f; private Animator _animator; private Transform _artTransform; - private SpriteRenderer spriteRenderer; + private SpriteRenderer _spriteRenderer; + + private PickupItemData _currentlyHeldItemData; + public PickupItemData CurrentlyHeldItemData => _currentlyHeldItemData; + private GameObject _cachedPickupObject = null; + public bool justCombined = false; - private PickupItemData _currentlyHeldItem; - public PickupItemData CurrentlyHeldItem => _currentlyHeldItem; /// /// Renderer for the held item icon. /// @@ -54,27 +58,9 @@ public class FollowerController : Character /// public event FollowerPickupHandler OnPickupReturned; private Coroutine _pickupCoroutine; - private bool _lastInteractionSuccess = true; - /// - /// Cache for the currently picked-up GameObject (hidden while held). - /// - private GameObject _cachedPickupObject = null; - public bool justCombined = false; - - /// - /// Caches the given pickup object as the currently held item, hides it, and parents it to the follower. - /// - public void CacheHeldPickupObject(GameObject obj) - { - // Do not destroy the previous object; just replace and hide - _cachedPickupObject = obj; - if (_cachedPickupObject != null) - { - _cachedPickupObject.SetActive(false); - } - } + void Awake() { @@ -84,12 +70,12 @@ public class FollowerController : Character if (_artTransform != null) { _animator = _artTransform.GetComponent(); - spriteRenderer = _artTransform.GetComponent(); + _spriteRenderer = _artTransform.GetComponent(); } else { _animator = GetComponentInChildren(); // fallback - spriteRenderer = GetComponentInChildren(); + _spriteRenderer = GetComponentInChildren(); } } @@ -108,38 +94,7 @@ public class FollowerController : Character { FindPlayerReference(); } - - void UpdateFollowTarget() - { - if (_playerTransform == null) - { - FindPlayerReference(); - if (_playerTransform == null) - return; - } - if (_isManualFollowing) - { - Vector3 playerPos = _playerTransform.position; - Vector3 moveDir = Vector3.zero; - if (_playerAIPath != null && _playerAIPath.velocity.magnitude > 0.01f) - { - moveDir = _playerAIPath.velocity.normalized; - _lastMoveDir = moveDir; - } - else - { - moveDir = _lastMoveDir; - } - // Use GameSettings for followDistance - _targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance; - _targetPoint.z = 0; - if (_aiPath != null) - { - _aiPath.enabled = false; - } - } - } - + void Update() { if (_playerTransform == null) @@ -185,12 +140,12 @@ public class FollowerController : Character } Vector3 dir = (_targetPoint - transform.position).normalized; // Sprite flipping based on movement direction - if (spriteRenderer != null && dir.sqrMagnitude > 0.001f) + if (_spriteRenderer != null && dir.sqrMagnitude > 0.001f) { if (dir.x > 0.01f) - spriteRenderer.flipX = false; + _spriteRenderer.flipX = false; else if (dir.x < -0.01f) - spriteRenderer.flipX = true; + _spriteRenderer.flipX = true; } transform.position += dir * _currentSpeed * Time.deltaTime; } @@ -214,12 +169,12 @@ public class FollowerController : Character { normalizedSpeed = _aiPath.velocity.magnitude / _followerMaxSpeed; // Sprite flipping for pathfinding mode - if (spriteRenderer != null && _aiPath.velocity.sqrMagnitude > 0.001f) + if (_spriteRenderer != null && _aiPath.velocity.sqrMagnitude > 0.001f) { if (_aiPath.velocity.x > 0.01f) - spriteRenderer.flipX = false; + _spriteRenderer.flipX = false; else if (_aiPath.velocity.x < -0.01f) - spriteRenderer.flipX = true; + _spriteRenderer.flipX = true; } } _animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); @@ -246,7 +201,44 @@ public class FollowerController : Character _playerAIPath = null; } } - + +#region Movement + /// + /// Updates the follower's target point to follow the player at a specified distance, + /// using the player's current movement direction if available. Disables pathfinding + /// when in manual following mode. + /// + void UpdateFollowTarget() + { + if (_playerTransform == null) + { + FindPlayerReference(); + if (_playerTransform == null) + return; + } + if (_isManualFollowing) + { + Vector3 playerPos = _playerTransform.position; + Vector3 moveDir = Vector3.zero; + if (_playerAIPath != null && _playerAIPath.velocity.magnitude > 0.01f) + { + moveDir = _playerAIPath.velocity.normalized; + _lastMoveDir = moveDir; + } + else + { + moveDir = _lastMoveDir; + } + // Use GameSettings for followDistance + _targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance; + _targetPoint.z = 0; + if (_aiPath != null) + { + _aiPath.enabled = false; + } + } + } + // Command follower to go to a specific point (pathfinding mode) /// /// Command follower to go to a specific point (pathfinding mode). @@ -277,7 +269,99 @@ public class FollowerController : Character _aiPath.maxSpeed = _followerMaxSpeed; _pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform)); } + + private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform) + { + _isManualFollowing = false; + _isReturningToPlayer = 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)) > GameManager.Instance.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; + } + _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)) > GameManager.Instance.StopThreshold) + { + yield return null; + } + _isReturningToPlayer = false; + 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; + } +#endregion Movement + +#region ItemInteractions + public void TryPickupItem(GameObject itemObject, PickupItemData itemData) + { + if (_currentlyHeldItemData != null && _cachedPickupObject != null) + { + // Drop the currently held item at the current position + DropHeldItemAt(transform.position); + } + // Pick up the new item + SetHeldItem(itemData, itemObject.GetComponent()); + _cachedPickupObject = itemObject; + _cachedPickupObject.SetActive(false); + } + + public enum CombinationResult + { + Successful, + Unsuccessful, + NotApplicable + } + public CombinationResult TryCombineItems(Pickup pickupA, out GameObject newItem) + { + newItem = null; + if (_cachedPickupObject == null) + { + return CombinationResult.NotApplicable; + } + Pickup pickupB = _cachedPickupObject.GetComponent(); + if (pickupA == null || pickupB == null) + { + return CombinationResult.NotApplicable; + } + var rule = GameManager.Instance.GetCombinationRule(pickupA.itemData, pickupB.itemData); + Vector3 spawnPos = pickupA.gameObject.transform.position; + if (rule != null && rule.resultPrefab != null) + { + newItem = Instantiate(rule.resultPrefab, spawnPos, Quaternion.identity); + PickupItemData itemData = newItem.GetComponent().itemData; + Destroy(pickupA.gameObject); + Destroy(pickupB.gameObject); + TryPickupItem(newItem,itemData); + return CombinationResult.Successful; + } + + // If no combination found, return Unsuccessful + return CombinationResult.Unsuccessful; + } + /// /// Set the item held by the follower, copying all visual properties from the Pickup's SpriteRenderer. /// @@ -285,10 +369,10 @@ public class FollowerController : Character /// The SpriteRenderer from the Pickup to copy appearance from. public void SetHeldItem(PickupItemData itemData, SpriteRenderer pickupRenderer = null) { - _currentlyHeldItem = itemData; + _currentlyHeldItemData = itemData; if (heldObjectRenderer != null) { - if (_currentlyHeldItem != null && pickupRenderer != null) + if (_currentlyHeldItemData != null && pickupRenderer != null) { AppleHillsUtils.CopySpriteRendererProperties(pickupRenderer, heldObjectRenderer); } @@ -299,16 +383,7 @@ public class FollowerController : Character } } } - - /// - /// Set the result of the last interaction (success or failure). - /// - /// True if the last interaction was successful, false otherwise. - public void SetInteractionResult(bool success) - { - _lastInteractionSuccess = success; - } - + public GameObject GetHeldPickupObject() { return _cachedPickupObject; @@ -336,7 +411,7 @@ public class FollowerController : Character public void ClearHeldItem() { _cachedPickupObject = null; - _currentlyHeldItem = null; + _currentlyHeldItemData = null; if (heldObjectRenderer != null) { heldObjectRenderer.sprite = null; @@ -344,83 +419,26 @@ public class FollowerController : Character } } + public void DropItem(FollowerController follower, Vector3 position) + { + var item = follower.GetHeldPickupObject(); + if (item == null) return; + item.transform.position = position; + item.transform.SetParent(null); + item.SetActive(true); + follower.ClearHeldItem(); + // Optionally: fire event, update UI, etc. + } + public void DropHeldItemAt(Vector3 position) { - InteractionOrchestrator.Instance.DropItem(this, position); - } - - private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform) - { - _isManualFollowing = false; - _isReturningToPlayer = 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)) > GameManager.Instance.StopThreshold) - { - yield return null; - } - OnPickupArrived?.Invoke(); - // Only perform pickup/swap logic if interaction succeeded - if (_lastInteractionSuccess && heldObjectRenderer != null) - { - Collider2D[] hits = Physics2D.OverlapCircleAll(itemPosition, 0.2f); - foreach (var hit in hits) - { - var pickup = hit.GetComponent(); - if (pickup != null) - { - var slotBehavior = pickup.GetComponent(); - if (slotBehavior != null) - { - // Slot item: orchestrator handles slotting - break; - } - if (justCombined) - { - InteractionOrchestrator.Instance.CombineItems(pickup.gameObject, _cachedPickupObject); - justCombined = false; - break; - } - // Swap logic: if holding an item, drop it here - if (_currentlyHeldItem != null && _cachedPickupObject != null) - { - InteractionOrchestrator.Instance.DropItem(this, pickup.transform.position); - } - InteractionOrchestrator.Instance.PickupItem(this, pickup.gameObject); - break; - } - } - } - // Wait briefly, then return to player - yield return new WaitForSeconds(0.2f); - if (_aiPath != null && playerTransform != null) - { - _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)) > GameManager.Instance.StopThreshold) - { - yield return null; - } - _isReturningToPlayer = false; - 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; + DropItem(this, position); } + + #endregion ItemInteractions + +#if UNITY_EDITOR void OnDrawGizmos() { if (debugDrawTarget && Application.isPlaying) @@ -431,4 +449,5 @@ public class FollowerController : Character Gizmos.DrawLine(transform.position, _targetPoint); } } +#endif } diff --git a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs index e7edee9d..86b4be64 100644 --- a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs +++ b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs @@ -1,93 +1,97 @@ - +using Input; +using Interactions; using UnityEngine; -/// -/// Manages the state and interactions for a single puzzle step, including unlock/lock logic and event handling. -/// -[RequireComponent(typeof(Interactable))] -public class ObjectiveStepBehaviour : MonoBehaviour +namespace PuzzleS { /// - /// The data object representing this puzzle step. + /// Manages the state and interactions for a single puzzle step, including unlock/lock logic and event handling. /// - public PuzzleStepSO stepData; - private Interactable interactable; - private bool isUnlocked = false; - - void Awake() + [RequireComponent(typeof(Interactable))] + public class ObjectiveStepBehaviour : MonoBehaviour { - interactable = GetComponent(); - } + /// + /// The data object representing this puzzle step. + /// + public PuzzleStepSO stepData; + private Interactable _interactable; + private bool _isUnlocked = false; - void OnEnable() - { - if (interactable == null) - interactable = GetComponent(); - if (interactable != null) + void Awake() { - interactable.StartedInteraction += OnStartedInteraction; - interactable.InteractionComplete += OnInteractionComplete; + _interactable = GetComponent(); } - PuzzleManager.Instance?.RegisterStepBehaviour(this); - } - void OnDisable() - { - if (interactable != null) + void OnEnable() { - interactable.StartedInteraction -= OnStartedInteraction; - interactable.InteractionComplete -= OnInteractionComplete; + if (_interactable == null) + _interactable = GetComponent(); + if (_interactable != null) + { + _interactable.interactionStarted.AddListener(OnInteractionStarted); + _interactable.interactionComplete.AddListener(OnInteractionComplete); + } + PuzzleManager.Instance?.RegisterStepBehaviour(this); } - PuzzleManager.Instance?.UnregisterStepBehaviour(this); - } - /// - /// Unlocks this puzzle step, allowing interaction. - /// - public void UnlockStep() - { - isUnlocked = true; - Debug.Log($"[Puzzles] Step unlocked: {stepData?.stepId} on {gameObject.name}"); - // Optionally, show visual feedback for unlocked state - } - - /// - /// Locks this puzzle step, preventing interaction. - /// - public void LockStep() - { - isUnlocked = false; - Debug.Log($"[Puzzles] Step locked: {stepData?.stepId} on {gameObject.name}"); - // Optionally, show visual feedback for locked state - } - - /// - /// Returns whether this step is currently unlocked. - /// - public bool IsStepUnlocked() - { - return isUnlocked; - } - - /// - /// Handles the start of an interaction (can be used for visual feedback). - /// - private void OnStartedInteraction() - { - // Optionally handle started interaction (e.g. visual feedback) - } - - /// - /// Handles completion of the interaction, notifies PuzzleManager if successful and unlocked. - /// - /// Whether the interaction was successful. - private void OnInteractionComplete(bool success) - { - if (!isUnlocked) return; - if (success) + void OnDestroy() { - Debug.Log($"[Puzzles] Step interacted: {stepData?.stepId} on {gameObject.name}"); - PuzzleManager.Instance?.OnStepCompleted(stepData); + if (_interactable != null) + { + _interactable.interactionStarted.RemoveListener(OnInteractionStarted); + _interactable.interactionComplete.RemoveListener(OnInteractionComplete); + } + PuzzleManager.Instance?.UnregisterStepBehaviour(this); + } + + /// + /// Unlocks this puzzle step, allowing interaction. + /// + public void UnlockStep() + { + _isUnlocked = true; + Debug.Log($"[Puzzles] Step unlocked: {stepData?.stepId} on {gameObject.name}"); + // Optionally, show visual feedback for unlocked state + } + + /// + /// Locks this puzzle step, preventing interaction. + /// + public void LockStep() + { + _isUnlocked = false; + Debug.Log($"[Puzzles] Step locked: {stepData?.stepId} on {gameObject.name}"); + // Optionally, show visual feedback for locked state + } + + /// + /// Returns whether this step is currently unlocked. + /// + public bool IsStepUnlocked() + { + return _isUnlocked; + } + + /// + /// Handles the start of an interaction (can be used for visual feedback). + /// + private void OnInteractionStarted(PlayerTouchController playerRef, FollowerController followerRef) + { + // Optionally handle started interaction (e.g. visual feedback) + } + + /// + /// Handles completion of the interaction, notifies PuzzleManager if successful and unlocked. + /// + /// Whether the interaction was successful. + private void OnInteractionComplete(bool success) + { + if (!_isUnlocked) return; + if (success) + { + Debug.Log($"[Puzzles] Step interacted: {stepData?.stepId} on {gameObject.name}"); + PuzzleManager.Instance?.OnStepCompleted(stepData); + } } } } diff --git a/Assets/Scripts/PuzzleS/PuzzleManager.cs b/Assets/Scripts/PuzzleS/PuzzleManager.cs index b26e78e4..ae8c9f1b 100644 --- a/Assets/Scripts/PuzzleS/PuzzleManager.cs +++ b/Assets/Scripts/PuzzleS/PuzzleManager.cs @@ -1,5 +1,6 @@ using UnityEngine; using System.Collections.Generic; +using PuzzleS; /// /// Manages puzzle step registration, dependency management, and step completion for the puzzle system.