diff --git a/Assets/Editor/ItemPrefabEditor.cs b/Assets/Editor/ItemPrefabEditor.cs index f67d6f4e..9ea59a25 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 @@ -13,6 +15,9 @@ namespace Editor private string _pickupSoFolderPath = "Assets/Data/Items"; private string _puzzleSoFolderPath = "Assets/Data/Puzzles"; + private enum ItemType { None, Pickup, ItemSlot } + private ItemType _itemType = ItemType.None; + [MenuItem("Tools/Item Prefab Editor")] public static void ShowWindow() { @@ -53,39 +58,28 @@ namespace Editor EditorGUILayout.LabelField("Editing: ", _selectedGameObject.name, EditorStyles.boldLabel); EditorGUILayout.Space(); - // Pickup + + // Determine current type bool hasPickup = _selectedGameObject.GetComponent() != null; - bool addPickup = EditorGUILayout.Toggle("Pickup", hasPickup); - if (addPickup && !hasPickup) - { - PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); - } - else if (!addPickup && hasPickup) + bool hasSlot = _selectedGameObject.GetComponent() != null; + if (hasSlot) _itemType = ItemType.ItemSlot; + else if (hasPickup) _itemType = ItemType.Pickup; + else _itemType = ItemType.None; + + // Item type selection + var newType = (ItemType)EditorGUILayout.EnumPopup("Item Type", _itemType); + if (newType != _itemType) { + // Remove both, then add selected PrefabEditorUtility.RemoveComponent(_selectedGameObject); + PrefabEditorUtility.RemoveComponent(_selectedGameObject); + if (newType == ItemType.Pickup) + PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); + else if (newType == ItemType.ItemSlot) + PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); + _itemType = newType; } - // 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 addSlot = EditorGUILayout.Toggle("SlotItemBehavior", hasSlot); - if (addSlot && !hasSlot) - { - PrefabEditorUtility.AddOrGetComponent(_selectedGameObject); - } - else if (!addSlot && hasSlot) - { - PrefabEditorUtility.RemoveComponent(_selectedGameObject); - } + // ObjectiveStepBehaviour bool hasObjective = _selectedGameObject.GetComponent() != null; bool addObjective = EditorGUILayout.Toggle("ObjectiveStepBehaviour", hasObjective); @@ -97,8 +91,9 @@ namespace Editor { PrefabEditorUtility.RemoveComponent(_selectedGameObject); } - // Pickup Data - if (addPickup) + + // Pickup Data (for Pickup or ItemSlot) + if (_itemType == ItemType.Pickup || _itemType == ItemType.ItemSlot) { var pickup = _selectedGameObject.GetComponent(); _pickupData = pickup.itemData; @@ -122,6 +117,7 @@ namespace Editor pickup.itemData = _pickupData; } } + // Objective Data if (addObjective) { diff --git a/Assets/Editor/PrefabCreatorWindow.cs b/Assets/Editor/PrefabCreatorWindow.cs index 7fb06838..5c8cf87e 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 { @@ -10,15 +12,17 @@ namespace Editor private string _saveFolderPath = "Assets/Prefabs/Items"; private string _pickupSoFolderPath = "Assets/Data/Items"; private string _puzzleSoFolderPath = "Assets/Data/Puzzles"; - private bool _addPickup; - private bool _addCombineWith; - private bool _addSlot; private bool _addObjective; private PickupItemData _pickupData; private PuzzleStepSO _objectiveData; private UnityEditor.Editor _soEditor; + private enum ItemType { None, Pickup, ItemSlot } + private ItemType _itemType = ItemType.None; + + private bool _createNext = false; + [MenuItem("Tools/Prefab Creator")] public static void ShowWindow() { @@ -48,13 +52,19 @@ namespace Editor EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.LabelField("Add Components:", EditorStyles.boldLabel); - _addPickup = EditorGUILayout.Toggle("Pickup", _addPickup); - _addCombineWith = EditorGUILayout.Toggle("CombineWithBehavior", _addCombineWith); - _addSlot = EditorGUILayout.Toggle("SlotItemBehavior", _addSlot); - _addObjective = EditorGUILayout.Toggle("ObjectiveStepBehaviour", _addObjective); + + // Item type selection + var newType = (ItemType)EditorGUILayout.EnumPopup("Item Type", _itemType); + if (newType != _itemType) + { + _itemType = newType; + } + bool addObjective = EditorGUILayout.Toggle("ObjectiveStepBehaviour", _addObjective); + _addObjective = addObjective; EditorGUILayout.Space(); - if (_addPickup) + // Pickup Data (for Pickup or ItemSlot) + if (_itemType == ItemType.Pickup || _itemType == ItemType.ItemSlot) { EditorGUILayout.LabelField("Pickup Data:", EditorStyles.boldLabel); _pickupData = (PickupItemData)EditorGUILayout.ObjectField("PickupItemData", _pickupData, typeof(PickupItemData), false); @@ -76,6 +86,7 @@ namespace Editor PrefabEditorUtility.DrawScriptableObjectEditor(ref _soEditor, _pickupData); } } + // Objective Data if (_addObjective) { EditorGUILayout.LabelField("Objective Data:", EditorStyles.boldLabel); @@ -99,12 +110,15 @@ namespace Editor } } GUILayout.FlexibleSpace(); + EditorGUILayout.BeginHorizontal(); GUI.enabled = !string.IsNullOrEmpty(_prefabName) && !string.IsNullOrEmpty(_saveFolderPath); - if (GUILayout.Button("Create Prefab")) + if (GUILayout.Button("Create Prefab", GUILayout.Height(28))) { CreatePrefab(); } + _createNext = GUILayout.Toggle(_createNext, "Create Next", GUILayout.Width(100), GUILayout.Height(28)); GUI.enabled = true; + EditorGUILayout.EndHorizontal(); } private void CreatePrefab() @@ -116,18 +130,15 @@ namespace Editor if (interactableLayer != -1) go.layer = interactableLayer; go.AddComponent(); - if (_addPickup) + if (_itemType == ItemType.Pickup) { var pickup = go.AddComponent(); pickup.itemData = _pickupData; } - if (_addCombineWith) + else if (_itemType == ItemType.ItemSlot) { - go.AddComponent(); - } - if (_addSlot) - { - go.AddComponent(); + var slot = go.AddComponent(); + slot.itemData = _pickupData; } if (_addObjective) { @@ -135,10 +146,23 @@ namespace Editor obj.stepData = _objectiveData; } string prefabPath = Path.Combine(_saveFolderPath, _prefabName + ".prefab").Replace("\\", "/"); - PrefabUtility.SaveAsPrefabAsset(go, prefabPath); + var prefab = PrefabUtility.SaveAsPrefabAsset(go, prefabPath); DestroyImmediate(go); AssetDatabase.Refresh(); + Selection.activeObject = prefab; + EditorGUIUtility.PingObject(prefab); EditorUtility.DisplayDialog("Prefab Created", $"Prefab saved to {prefabPath}", "OK"); + if (_createNext) + { + _prefabName = "NewPrefab"; + _pickupData = null; + _objectiveData = null; + _itemType = ItemType.None; + _addObjective = false; + _soEditor = null; + GUI.FocusControl(null); + Repaint(); + } } } } 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/Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta b/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta new file mode 100644 index 00000000..488f0fad --- /dev/null +++ b/Assets/Prefabs/Items/PrefabsPLACEHOLDER/Bunfflers.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 47ac9229b9b128041a770b193eb43b02 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Levels/AppleHillsOverworld.unity b/Assets/Scenes/Levels/AppleHillsOverworld.unity index dbdb43c9..655bfe5d 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: 0 + 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 @@ -868,7 +863,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c119ffb87b2a16d4f925ff5d5ffd7092, type: 3} m_Name: m_EditorClassIdentifier: - ShouldPlayIntro: 1 + shouldPlayIntro: 0 --- !u!95 &948124911 Animator: serializedVersion: 7 @@ -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,30 +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} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] @@ -2013,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/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index 3701cfa3..452e5938 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -50,7 +50,7 @@ public class GameManager : MonoBehaviour public float ManualMoveSmooth => gameSettings != null ? gameSettings.manualMoveSmooth : 8f; public float ThresholdFar => gameSettings != null ? gameSettings.thresholdFar : 2.5f; public float ThresholdNear => gameSettings != null ? gameSettings.thresholdNear : 0.5f; - public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.1f; + public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.5f; public float MoveSpeed => gameSettings != null ? gameSettings.moveSpeed : 5f; public float StopDistance => gameSettings != null ? gameSettings.stopDistance : 0.1f; public bool UseRigidbody => gameSettings != null ? gameSettings.useRigidbody : true; @@ -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/Data.meta b/Assets/Scripts/Data.meta new file mode 100644 index 00000000..85e71fb9 --- /dev/null +++ b/Assets/Scripts/Data.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e0b15b90103942c3b0e630462ecc5de1 +timeCreated: 1757518020 \ No newline at end of file diff --git a/Assets/Scripts/Data/ItemCombinationManager.cs b/Assets/Scripts/Data/ItemCombinationManager.cs new file mode 100644 index 00000000..e02abfc9 --- /dev/null +++ b/Assets/Scripts/Data/ItemCombinationManager.cs @@ -0,0 +1 @@ + diff --git a/Assets/Scripts/Data/ItemCombinationManager.cs.meta b/Assets/Scripts/Data/ItemCombinationManager.cs.meta new file mode 100644 index 00000000..8d07764a --- /dev/null +++ b/Assets/Scripts/Data/ItemCombinationManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a54fc1a3945f46c580a83e74f9436526 +timeCreated: 1757518020 \ No newline at end of file diff --git a/Assets/Scripts/Input/InputManager.cs b/Assets/Scripts/Input/InputManager.cs index eaffd4e3..41b32a0f 100644 --- a/Assets/Scripts/Input/InputManager.cs +++ b/Assets/Scripts/Input/InputManager.cs @@ -157,17 +157,14 @@ public class InputManager : MonoBehaviour Collider2D hit = Physics2D.OverlapPoint(worldPos, mask); if (hit != null) { - var interactable = hit.GetComponent(); - if (interactable != null) + var consumer = hit.GetComponent(); + if (consumer != null) { - Debug.unityLogger.Log("Interactable", $"[InputManager] Delegating tap to interactable at {worldPos} (GameObject: {hit.gameObject.name})"); - interactable.OnTap(worldPos); + 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/Interactions/CombineWithBehavior.cs b/Assets/Scripts/Interactions/CombineWithBehavior.cs deleted file mode 100644 index 70638d52..00000000 --- a/Assets/Scripts/Interactions/CombineWithBehavior.cs +++ /dev/null @@ -1,64 +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.CurrentlyHeldItem; - 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 rule = GameManager.Instance.GetCombinationRule(heldItem, pickup.itemData); - if (rule != null && rule.resultPrefab != null) - { - // Instantiate the result prefab at the pickup's position - var resultObj = GameObject.Instantiate(rule.resultPrefab, pickup.transform.position, Quaternion.identity); - var resultPickup = resultObj.GetComponent(); - if (resultPickup != null) - { - // Hide and parent to follower - resultObj.SetActive(false); - resultObj.transform.SetParent(follower.transform); - // Set held item and icon from the spawned prefab - follower.SetHeldItem(resultPickup.itemData, resultPickup.iconRenderer); - // Cache the spawned object as the held item - follower.CacheHeldPickupObject(resultObj); - follower.justCombined = true; - OnSuccess?.Invoke(); - return true; - } - else - { - Debug.LogWarning("Result prefab does not have a Pickup component."); - GameObject.Destroy(resultObj); - OnFailure?.Invoke(); - return false; - } - } - else - { - // DebugUIMessage.Show($"Cannot combine {heldItem.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 bab8a866..c6ed85c2 100644 --- a/Assets/Scripts/Interactions/Interactable.cs +++ b/Assets/Scripts/Interactions/Interactable.cs @@ -1,76 +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; - - private ObjectiveStepBehaviour stepBehaviour; - - void Awake() + public enum CharacterToInteract { - stepBehaviour = GetComponent(); + Trafalgar, + Pulver } - /// - /// Handles tap input. Triggers interaction logic. + /// Represents an interactable object that can respond to tap input events. /// - public void OnTap(Vector2 worldPosition) + public class Interactable : MonoBehaviour, ITouchInputConsumer { - Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}"); - StartedInteraction?.Invoke(); - } + [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; - /// - /// No hold behavior for interactables. - /// - public void OnHoldStart(Vector2 worldPosition) { } - public void OnHoldMove(Vector2 worldPosition) { } - public void OnHoldEnd(Vector2 worldPosition) { } + // Helpers for managing interaction state + private bool _interactionInProgress; + private PlayerTouchController _playerRef; + private FollowerController _followerController; - /// - /// Called when the follower arrives at this interactable. - /// - public bool OnFollowerArrived(FollowerController follower) - { - // Check if step is locked here - if (stepBehaviour != null && !stepBehaviour.IsStepUnlocked()) + private bool _isActive = true; + + private void Awake() { - DebugUIMessage.Show("Item is not unlocked yet"); - Debug.Log("[Puzzles] Tried to interact with locked step: " + gameObject.name); - InteractionComplete?.Invoke(false); - return false; + // Subscribe to interactionComplete event + interactionComplete.AddListener(OnInteractionComplete); } - var requirements = GetComponents(); - if (requirements.Length == 0) + + /// + /// Handles tap input. Triggers interaction logic. + /// + public void OnTap(Vector2 worldPosition) { - InteractionComplete?.Invoke(true); - return true; - } - bool anySuccess = false; - foreach (var req in requirements) - { - if (req.TryInteract(follower)) + if (!_isActive) { - anySuccess = true; - break; + 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(); } } - 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; - } - public void CompleteInteraction(bool success) - { - InteractionComplete?.Invoke(success); + 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/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 d974649f..864667f7 100644 --- a/Assets/Scripts/Interactions/Pickup.cs +++ b/Assets/Scripts/Interactions/Pickup.cs @@ -1,182 +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; - - private bool pickupInProgress = false; - - /// - /// 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; - } - gameObject.name = itemData.itemName; - } - } + if (iconRenderer != null && itemData.mapSprite != null) + { + iconRenderer.sprite = itemData.mapSprite; + } - /// - /// Handles the start of an interaction (player approaches, then follower picks up). - /// - 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; + gameObject.name = itemData.itemName; + } } - var playerController = playerObj.GetComponent(); - var followerController = followerObj.GetComponent(); - if (playerController == null || followerController == null) + + /// + /// Handles the start of an interaction (for feedback/UI only). + /// + private void OnInteractionStarted(PlayerTouchController playerRef, FollowerController followerRef) { - Debug.LogWarning("Pickup: PlayerTouchController or FollowerController missing."); - return; + _playerRef = playerRef; + FollowerController = followerRef; } - float playerStopDistance = GameManager.Instance.PlayerStopDistance; - float followerPickupDelay = GameManager.Instance.FollowerPickupDelay; - // --- Local event/coroutine handlers --- - void OnPlayerArrived() + + protected virtual void OnCharacterArrived() { - 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) + var combinationResult = FollowerController.TryCombineItems(this, out var combinationResultItem); + if (combinationResultItem != null) { - interactionSuccess = interactable.OnFollowerArrived(followerController); + Interactable.BroadcastInteractionComplete(true); + return; } - 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); + + FollowerController?.TryPickupItem(gameObject, itemData); + Interactable.BroadcastInteractionComplete(combinationResult == FollowerController.CombinationResult.NotApplicable); } } - - /// - /// 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 84bfebc7..00000000 --- a/Assets/Scripts/Interactions/SlotItemBehavior.cs +++ /dev/null @@ -1,164 +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; - - // Helper for slotting an object, with option to skip destruction (for swap) - private void SetSlottedObject(GameObject obj) - { - _cachedSlottedObject = obj; - if (_cachedSlottedObject != null) - { - _cachedSlottedObject.SetActive(false); - } - } - - private void RemoveSlottedObject() - { - if (_cachedSlottedObject != null) - { - Destroy(_cachedSlottedObject); - _cachedSlottedObject = null; - } - } - - private void CacheSlottedObject(GameObject obj) - { - // Only destroy if not swapping - RemoveSlottedObject(); - SetSlottedObject(obj); - } - - private void RestoreSlottedObject(Vector3 position) - { - if (_cachedSlottedObject != null) - { - _cachedSlottedObject.transform.position = position; - _cachedSlottedObject.transform.SetParent(null); - _cachedSlottedObject.SetActive(true); - _cachedSlottedObject = null; - } - } - - /// - /// 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) - { - follower.SetHeldItemFromObject(_cachedSlottedObject); - RemoveSlottedObject(); - currentlySlottedItem = null; - UpdateSlottedSprite(); - return true; - } - // CASE 2: Held item, slot has item -> swap - if (heldItem != null && _cachedSlottedObject != null) - { - var followerHeldObj = heldObj; - var followerHeldItem = heldItem; - var slotObj = _cachedSlottedObject; - var slotItemData = currentlySlottedItem; - - // 1. Slot the follower's held object (do NOT destroy the old one) - SetSlottedObject(followerHeldObj); - currentlySlottedItem = followerHeldItem; - UpdateSlottedSprite(); - - // 2. Give the slot's object to the follower - follower.SetHeldItemFromObject(slotObj); - - 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; - } - CacheSlottedObject(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 fc26d6c6..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 : MonoBehaviour +public class FollowerController: MonoBehaviour { [Header("Follower Settings")] public bool debugDrawTarget = true; @@ -29,10 +30,13 @@ public class FollowerController : MonoBehaviour 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 : MonoBehaviour /// 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 : MonoBehaviour 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 : MonoBehaviour { 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 : MonoBehaviour } 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 : MonoBehaviour { 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 : MonoBehaviour _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,77 +269,7 @@ public class FollowerController : MonoBehaviour _aiPath.maxSpeed = _followerMaxSpeed; _pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform)); } - - /// - /// Set the item held by the follower, copying all visual properties from the Pickup's SpriteRenderer. - /// - /// The item data to set. - /// The SpriteRenderer from the Pickup to copy appearance from. - public void SetHeldItem(PickupItemData itemData, SpriteRenderer pickupRenderer = null) - { - _currentlyHeldItem = itemData; - if (heldObjectRenderer != null) - { - if (_currentlyHeldItem != null && pickupRenderer != null) - { - AppleHillsUtils.CopySpriteRendererProperties(pickupRenderer, heldObjectRenderer); - } - else - { - heldObjectRenderer.sprite = null; - heldObjectRenderer.enabled = false; - } - } - } - - /// - /// 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; - } - - public void SetHeldItemFromObject(GameObject obj) - { - if (obj == null) - { - ClearHeldItem(); - return; - } - var pickup = obj.GetComponent(); - if (pickup != null) - { - SetHeldItem(pickup.itemData, pickup.iconRenderer); - CacheHeldPickupObject(obj); - } - else - { - ClearHeldItem(); - } - } - - public void ClearHeldItem() - { - if (_cachedPickupObject != null) - { - // Destroy(_cachedPickupObject); - _cachedPickupObject = null; - } - _currentlyHeldItem = null; - if (heldObjectRenderer != null) - { - heldObjectRenderer.sprite = null; - heldObjectRenderer.enabled = false; - } - } - + private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform) { _isManualFollowing = false; @@ -364,42 +286,7 @@ public class FollowerController : MonoBehaviour 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: do not destroy or swap, just return to player - break; - } - if (justCombined) - { - GameObject.Destroy(pickup.gameObject); - justCombined = false; - break; - } - // Swap logic: if holding an item, drop it here - if (_currentlyHeldItem != null && _cachedPickupObject != null) - { - // Drop the cached object at the pickup's position - _cachedPickupObject.transform.position = pickup.transform.position; - _cachedPickupObject.transform.SetParent(null); - _cachedPickupObject.SetActive(true); - _cachedPickupObject = null; - } - SetHeldItem(pickup.itemData, pickup.iconRenderer); - CacheHeldPickupObject(pickup.gameObject); - break; - } - } - } + // Wait briefly, then return to player yield return new WaitForSeconds(0.2f); if (_aiPath != null && playerTransform != null) @@ -424,28 +311,134 @@ public class FollowerController : MonoBehaviour _aiPath.enabled = false; _pickupCoroutine = null; } - - /// - /// Drop the held item at the specified position, unparenting and activating it. - /// - /// The world position to drop the item at. - public void DropHeldItemAt(Vector3 position) +#endregion Movement + +#region ItemInteractions + public void TryPickupItem(GameObject itemObject, PickupItemData itemData) { - if (_cachedPickupObject != null) + if (_currentlyHeldItemData != null && _cachedPickupObject != null) { - _cachedPickupObject.transform.position = position; - _cachedPickupObject.transform.SetParent(null); - _cachedPickupObject.SetActive(true); - _cachedPickupObject = null; - _currentlyHeldItem = null; - if (heldObjectRenderer != 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. + /// + /// The item data to set. + /// The SpriteRenderer from the Pickup to copy appearance from. + public void SetHeldItem(PickupItemData itemData, SpriteRenderer pickupRenderer = null) + { + _currentlyHeldItemData = itemData; + if (heldObjectRenderer != null) + { + if (_currentlyHeldItemData != null && pickupRenderer != null) + { + AppleHillsUtils.CopySpriteRendererProperties(pickupRenderer, heldObjectRenderer); + } + else { heldObjectRenderer.sprite = null; heldObjectRenderer.enabled = false; } } } + + public GameObject GetHeldPickupObject() + { + return _cachedPickupObject; + } + public void SetHeldItemFromObject(GameObject obj) + { + if (obj == null) + { + ClearHeldItem(); + return; + } + var pickup = obj.GetComponent(); + if (pickup != null) + { + SetHeldItem(pickup.itemData, pickup.iconRenderer); + _cachedPickupObject = obj; + } + else + { + ClearHeldItem(); + } + } + + public void ClearHeldItem() + { + _cachedPickupObject = null; + _currentlyHeldItemData = null; + if (heldObjectRenderer != null) + { + heldObjectRenderer.sprite = null; + heldObjectRenderer.enabled = false; + } + } + + 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) + { + DropItem(this, position); + } + + + #endregion ItemInteractions + +#if UNITY_EDITOR void OnDrawGizmos() { if (debugDrawTarget && Application.isPlaying) @@ -456,6 +449,5 @@ public class FollowerController : MonoBehaviour 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.