Rework interactables into a flatter hierarchy, reenable puzzles as well
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction requirement that allows combining the follower's held item with this pickup if a valid combination rule exists.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Pickup))]
|
||||
public class CombineWithBehavior : InteractionRequirementBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to combine the follower's held item with this pickup's item.
|
||||
/// </summary>
|
||||
/// <param name="follower">The follower attempting the interaction.</param>
|
||||
/// <returns>True if the combination was successful, false otherwise.</returns>
|
||||
public override bool TryInteract(FollowerController follower)
|
||||
{
|
||||
var heldItem = follower.GetHeldPickupObject();
|
||||
var pickup = GetComponent<Pickup>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21401a3b30134380bb205964d9e5c67d
|
||||
timeCreated: 1756981777
|
||||
@@ -1,41 +1,230 @@
|
||||
using UnityEngine;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an interactable object that can respond to tap input events.
|
||||
/// </summary>
|
||||
public class Interactable : MonoBehaviour, ITouchInputConsumer
|
||||
namespace Interactions
|
||||
{
|
||||
public event Action StartedInteraction;
|
||||
public event Action<bool> InteractionComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Handles tap input. Triggers interaction logic.
|
||||
/// </summary>
|
||||
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) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called to interact with this object by a character (player, follower, etc).
|
||||
/// Represents an interactable object that can respond to tap input events.
|
||||
/// </summary>
|
||||
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<PlayerTouchController, FollowerController> interactionStarted;
|
||||
public UnityEvent interactionInterrupted;
|
||||
public UnityEvent characterArrived;
|
||||
public UnityEvent<bool> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles tap input. Triggers interaction logic.
|
||||
/// </summary>
|
||||
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<PlayerTouchController>();
|
||||
_followerController = FindFirstObjectByType<FollowerController>();
|
||||
|
||||
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<PuzzleS.ObjectiveStepBehaviour>();
|
||||
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
|
||||
/// <summary>
|
||||
/// Draws gizmos for pickup interaction range in the editor.
|
||||
/// </summary>
|
||||
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<GameSettings>(
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the process of moving characters to interactables and orchestrating interactions.
|
||||
/// </summary>
|
||||
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<InteractionOrchestrator>();
|
||||
if (_instance == null)
|
||||
{
|
||||
var go = new GameObject("InteractionOrchestrator");
|
||||
_instance = go.AddComponent<InteractionOrchestrator>();
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Request an interaction between a character and an interactable.
|
||||
/// </summary>
|
||||
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<FollowerController>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character has arrived at the interactable.
|
||||
/// </summary>
|
||||
private void OnCharacterArrived(Interactable interactable, Character character)
|
||||
{
|
||||
// Check all requirements
|
||||
var requirements = interactable.GetComponents<InteractionRequirementBase>();
|
||||
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<Pickup>();
|
||||
var pickupB = itemB.GetComponent<Pickup>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 705c4ee7f8204cc68aacd79e2a4a506d
|
||||
timeCreated: 1757513080
|
||||
145
Assets/Scripts/Interactions/ItemSlot.cs
Normal file
145
Assets/Scripts/Interactions/ItemSlot.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction requirement that allows slotting, swapping, or picking up items in a slot.
|
||||
/// </summary>
|
||||
[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<Pickup>();
|
||||
var slotItem = pickup != null ? pickup.itemData : null;
|
||||
var config = GameManager.Instance.GetSlotItemConfig(slotItem);
|
||||
var allowed = config?.allowedItems ?? new List<PickupItemData>();
|
||||
var forbidden = config?.forbiddenItems ?? new List<PickupItemData>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the sprite and scale for the currently slotted item.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using Input;
|
||||
using Interactions;
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour that immediately completes an interaction when started.
|
||||
@@ -13,7 +15,7 @@ public class OneClickInteraction : MonoBehaviour
|
||||
interactable = GetComponent<Interactable>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,95 @@
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
|
||||
public class Pickup : MonoBehaviour
|
||||
namespace Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for the pickup item (icon, name, etc).
|
||||
/// </summary>
|
||||
public PickupItemData itemData;
|
||||
/// <summary>
|
||||
/// Renderer for the pickup icon.
|
||||
/// </summary>
|
||||
public SpriteRenderer iconRenderer;
|
||||
private Interactable interactable;
|
||||
|
||||
/// <summary>
|
||||
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
[RequireComponent(typeof(Interactable))]
|
||||
public class Pickup : MonoBehaviour
|
||||
{
|
||||
if (iconRenderer == null)
|
||||
iconRenderer = GetComponent<SpriteRenderer>();
|
||||
interactable = GetComponent<Interactable>();
|
||||
if (interactable != null)
|
||||
public PickupItemData itemData;
|
||||
public SpriteRenderer iconRenderer;
|
||||
protected Interactable Interactable;
|
||||
private PlayerTouchController _playerRef;
|
||||
protected FollowerController FollowerController;
|
||||
|
||||
/// <summary>
|
||||
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
interactable.StartedInteraction += OnStartedInteraction;
|
||||
interactable.InteractionComplete += OnInteractionComplete;
|
||||
}
|
||||
ApplyItemData();
|
||||
}
|
||||
if (iconRenderer == null)
|
||||
iconRenderer = GetComponent<SpriteRenderer>();
|
||||
|
||||
Interactable = GetComponent<Interactable>();
|
||||
if (Interactable != null)
|
||||
{
|
||||
Interactable.interactionStarted.AddListener(OnInteractionStarted);
|
||||
Interactable.characterArrived.AddListener(OnCharacterArrived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity OnDestroy callback. Cleans up event handlers.
|
||||
/// </summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
if (interactable != null)
|
||||
{
|
||||
interactable.StartedInteraction -= OnStartedInteraction;
|
||||
interactable.InteractionComplete -= OnInteractionComplete;
|
||||
ApplyItemData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity OnDestroy callback. Cleans up event handlers.
|
||||
/// </summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
if (Interactable != null)
|
||||
{
|
||||
Interactable.interactionStarted.RemoveListener(OnInteractionStarted);
|
||||
Interactable.characterArrived.RemoveListener(OnCharacterArrived);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Unity OnValidate callback. Ensures icon and data are up to date in editor.
|
||||
/// </summary>
|
||||
void OnValidate()
|
||||
{
|
||||
if (iconRenderer == null)
|
||||
iconRenderer = GetComponent<SpriteRenderer>();
|
||||
ApplyItemData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws gizmos for pickup interaction range in the editor.
|
||||
/// </summary>
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
float playerStopDistance;
|
||||
if (Application.isPlaying)
|
||||
/// <summary>
|
||||
/// Unity OnValidate callback. Ensures icon and data are up to date in editor.
|
||||
/// </summary>
|
||||
void OnValidate()
|
||||
{
|
||||
playerStopDistance = GameManager.Instance.PlayerStopDistance;
|
||||
if (iconRenderer == null)
|
||||
iconRenderer = GetComponent<SpriteRenderer>();
|
||||
ApplyItemData();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load settings directly from asset path in editor
|
||||
var settings = UnityEditor.AssetDatabase.LoadAssetAtPath<GameSettings>("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
|
||||
|
||||
/// <summary>
|
||||
/// Applies the item data to the pickup (icon, name, etc).
|
||||
/// </summary>
|
||||
public void ApplyItemData()
|
||||
{
|
||||
if (itemData != null)
|
||||
/// <summary>
|
||||
/// Applies the item data to the pickup (icon, name, etc).
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the start of an interaction (for feedback/UI only).
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the start of an interaction (for feedback/UI only).
|
||||
/// </summary>
|
||||
private void OnStartedInteraction()
|
||||
{
|
||||
// Optionally, add pickup-specific feedback here (e.g., highlight, sound).
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles completion of the interaction (e.g., after pickup is done).
|
||||
/// </summary>
|
||||
/// <param name="success">Whether the interaction was successful.</param>
|
||||
private void OnInteractionComplete(bool success)
|
||||
{
|
||||
if (!success) return;
|
||||
// Optionally, add logic to disable the pickup or provide feedback
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction requirement that checks if the follower is holding a specific required item.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Interactable))]
|
||||
public class RequiresItemBehavior : InteractionRequirementBase
|
||||
{
|
||||
[Header("Required Item")]
|
||||
public PickupItemData requiredItem;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to interact, succeeds only if the follower is holding the required item.
|
||||
/// </summary>
|
||||
/// <param name="follower">The follower attempting the interaction.</param>
|
||||
/// <returns>True if the interaction was successful, false otherwise.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31103d67032c44a9b95ec014babe2c62
|
||||
timeCreated: 1756981777
|
||||
@@ -1,131 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction requirement that allows slotting, swapping, or picking up items in a slot.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Interactable))]
|
||||
[RequireComponent(typeof(Pickup))]
|
||||
public class SlotItemBehavior : InteractionRequirementBase
|
||||
{
|
||||
[Header("Slot State")]
|
||||
/// <summary>
|
||||
/// The item currently slotted in this slot.
|
||||
/// </summary>
|
||||
public PickupItemData currentlySlottedItem;
|
||||
/// <summary>
|
||||
/// The renderer for the slotted item's sprite.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to interact with the slot, handling slotting, swapping, or picking up items.
|
||||
/// </summary>
|
||||
/// <param name="follower">The follower attempting the interaction.</param>
|
||||
/// <returns>True if the interaction was successful, false otherwise.</returns>
|
||||
public override bool TryInteract(FollowerController follower)
|
||||
{
|
||||
var heldItem = follower.CurrentlyHeldItem;
|
||||
var heldObj = follower.GetHeldPickupObject();
|
||||
var pickup = GetComponent<Pickup>();
|
||||
var slotItem = pickup != null ? pickup.itemData : null;
|
||||
var config = GameManager.Instance.GetSlotItemConfig(slotItem);
|
||||
var allowed = config?.allowedItems ?? new List<PickupItemData>();
|
||||
var forbidden = config?.forbiddenItems ?? new List<PickupItemData>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the sprite and scale for the currently slotted item.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user