A bit wonky but working switching
This commit is contained in:
@@ -13,7 +13,7 @@ GameObject:
|
|||||||
- component: {fileID: 7813271480623895155}
|
- component: {fileID: 7813271480623895155}
|
||||||
- component: {fileID: 6196606079257550}
|
- component: {fileID: 6196606079257550}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: ControllerSwitchItem
|
m_Name: ControllerSwitchItem_To_pulver
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
|
|||||||
@@ -935,7 +935,7 @@ Transform:
|
|||||||
m_GameObject: {fileID: 258612751}
|
m_GameObject: {fileID: 258612751}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0.6, y: -0.6, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
@@ -363427,6 +363427,10 @@ PrefabInstance:
|
|||||||
serializedVersion: 3
|
serializedVersion: 3
|
||||||
m_TransformParent: {fileID: 0}
|
m_TransformParent: {fileID: 0}
|
||||||
m_Modifications:
|
m_Modifications:
|
||||||
|
- target: {fileID: 6196606079257550, guid: 67a60833f9f205940a2308bd74a2863e, type: 3}
|
||||||
|
propertyPath: m_AutoTiling
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 1136290793271151494, guid: 67a60833f9f205940a2308bd74a2863e, type: 3}
|
- target: {fileID: 1136290793271151494, guid: 67a60833f9f205940a2308bd74a2863e, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
value: 185.3
|
value: 185.3
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ namespace Input
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for player movement controllers.
|
/// Base class for player movement controllers.
|
||||||
/// Handles tap-to-move and hold-to-move input with pathfinding or direct movement.
|
/// Handles tap-to-move and hold-to-move input with pathfinding or direct movement.
|
||||||
|
/// Implements IInteractingCharacter to enable interaction with items.
|
||||||
/// Derived classes can override to add specialized behavior (e.g., shader updates).
|
/// Derived classes can override to add specialized behavior (e.g., shader updates).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BasePlayerMovementController : ManagedBehaviour, ITouchInputConsumer
|
public abstract class BasePlayerMovementController : ManagedBehaviour, ITouchInputConsumer, IInteractingCharacter
|
||||||
{
|
{
|
||||||
[Header("Movement")]
|
[Header("Movement")]
|
||||||
[SerializeField] protected float moveSpeed = 5f;
|
[SerializeField] protected float moveSpeed = 5f;
|
||||||
@@ -42,6 +43,12 @@ namespace Input
|
|||||||
public event System.Action OnMovementStarted;
|
public event System.Action OnMovementStarted;
|
||||||
public event System.Action OnMovementStopped;
|
public event System.Action OnMovementStopped;
|
||||||
|
|
||||||
|
// IInteractingCharacter implementation - scripted movement for interactions
|
||||||
|
private Coroutine _moveToCoroutine;
|
||||||
|
private bool _interruptMoveTo;
|
||||||
|
public event System.Action OnArrivedAtTarget;
|
||||||
|
public event System.Action OnMoveToCancelled;
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
protected AIPath _aiPath;
|
protected AIPath _aiPath;
|
||||||
protected Animator _animator;
|
protected Animator _animator;
|
||||||
@@ -95,6 +102,7 @@ namespace Input
|
|||||||
|
|
||||||
public virtual void OnTap(Vector2 worldPosition)
|
public virtual void OnTap(Vector2 worldPosition)
|
||||||
{
|
{
|
||||||
|
InterruptMoveTo(); // Cancel any scripted movement
|
||||||
Logging.Debug($"[{GetType().Name}] OnTap at {worldPosition}");
|
Logging.Debug($"[{GetType().Name}] OnTap at {worldPosition}");
|
||||||
if (_aiPath != null)
|
if (_aiPath != null)
|
||||||
{
|
{
|
||||||
@@ -109,6 +117,7 @@ namespace Input
|
|||||||
|
|
||||||
public virtual void OnHoldStart(Vector2 worldPosition)
|
public virtual void OnHoldStart(Vector2 worldPosition)
|
||||||
{
|
{
|
||||||
|
InterruptMoveTo(); // Cancel any scripted movement
|
||||||
Logging.Debug($"[{GetType().Name}] OnHoldStart at {worldPosition}");
|
Logging.Debug($"[{GetType().Name}] OnHoldStart at {worldPosition}");
|
||||||
_lastHoldPosition = worldPosition;
|
_lastHoldPosition = worldPosition;
|
||||||
_isHolding = true;
|
_isHolding = true;
|
||||||
@@ -318,6 +327,78 @@ namespace Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IInteractingCharacter Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves the character to a specific target position and notifies via events when arrived or cancelled.
|
||||||
|
/// This is used by systems like interactions to orchestrate scripted movement.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void MoveToAndNotify(Vector3 target)
|
||||||
|
{
|
||||||
|
// Cancel any previous move-to coroutine
|
||||||
|
if (_moveToCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_moveToCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
_interruptMoveTo = false;
|
||||||
|
// Ensure pathfinding is enabled for MoveToAndNotify
|
||||||
|
if (_aiPath != null)
|
||||||
|
{
|
||||||
|
_aiPath.enabled = true;
|
||||||
|
_aiPath.canMove = true;
|
||||||
|
_aiPath.isStopped = false;
|
||||||
|
}
|
||||||
|
_moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels any in-progress MoveToAndNotify operation and fires the cancellation event.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void InterruptMoveTo()
|
||||||
|
{
|
||||||
|
_interruptMoveTo = true;
|
||||||
|
_isHolding = false;
|
||||||
|
_directMoveVelocity = Vector3.zero;
|
||||||
|
if (Settings != null && Settings.DefaultHoldMovementMode == HoldMovementMode.Direct && _aiPath != null)
|
||||||
|
_aiPath.enabled = false;
|
||||||
|
OnMoveToCancelled?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coroutine for moving the character to a target position and firing arrival/cancel events.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target)
|
||||||
|
{
|
||||||
|
if (_aiPath != null)
|
||||||
|
{
|
||||||
|
_aiPath.destination = target;
|
||||||
|
_aiPath.maxSpeed = Settings.MoveSpeed;
|
||||||
|
_aiPath.maxAcceleration = Settings.MaxAcceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!_interruptMoveTo)
|
||||||
|
{
|
||||||
|
Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
|
||||||
|
Vector2 target2D = new Vector2(target.x, target.y);
|
||||||
|
float dist = Vector2.Distance(current2D, target2D);
|
||||||
|
if (dist <= Settings.StopDistance + 0.2f)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_moveToCoroutine = null;
|
||||||
|
if (!_interruptMoveTo)
|
||||||
|
{
|
||||||
|
OnArrivedAtTarget?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
Assets/Scripts/Input/IInteractingCharacter.cs
Normal file
38
Assets/Scripts/Input/IInteractingCharacter.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Input
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for characters that can participate in scripted interactions.
|
||||||
|
/// Provides movement-to-target with arrival/cancellation notifications.
|
||||||
|
/// Implemented by BasePlayerMovementController to enable all controllers to interact with items.
|
||||||
|
/// </summary>
|
||||||
|
public interface IInteractingCharacter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Moves character to target position and notifies when arrived/cancelled
|
||||||
|
/// </summary>
|
||||||
|
void MoveToAndNotify(Vector3 target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interrupts any in-progress MoveToAndNotify operation
|
||||||
|
/// </summary>
|
||||||
|
void InterruptMoveTo();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when character arrives at MoveToAndNotify target
|
||||||
|
/// </summary>
|
||||||
|
event System.Action OnArrivedAtTarget;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when MoveToAndNotify is cancelled/interrupted
|
||||||
|
/// </summary>
|
||||||
|
event System.Action OnMoveToCancelled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Character's transform (for position queries)
|
||||||
|
/// </summary>
|
||||||
|
Transform transform { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Scripts/Input/IInteractingCharacter.cs.meta
Normal file
3
Assets/Scripts/Input/IInteractingCharacter.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0d1b1be281334b9390cc96d7a8ff3132
|
||||||
|
timeCreated: 1765754798
|
||||||
@@ -521,6 +521,16 @@ namespace Input
|
|||||||
return !string.IsNullOrEmpty(controllerName) && _registeredControllers.ContainsKey(controllerName);
|
return !string.IsNullOrEmpty(controllerName) && _registeredControllers.ContainsKey(controllerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently active controller (the default consumer).
|
||||||
|
/// This is the controller that currently has input control.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The active controller, or null if no default consumer is set</returns>
|
||||||
|
public ITouchInputConsumer GetActiveController()
|
||||||
|
{
|
||||||
|
return defaultConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,12 @@ namespace Input
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles player movement in response to tap and hold input events.
|
/// Handles player movement in response to tap and hold input events.
|
||||||
/// Supports both direct and pathfinding movement modes, and provides event/callbacks for arrival/cancellation.
|
/// Supports both direct and pathfinding movement modes.
|
||||||
/// Extends BasePlayerMovementController with save/load and MoveToAndNotify functionality.
|
/// Extends BasePlayerMovementController with save/load functionality.
|
||||||
|
/// Interaction capability (MoveToAndNotify) is provided by base class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlayerTouchController : BasePlayerMovementController
|
public class PlayerTouchController : BasePlayerMovementController
|
||||||
{
|
{
|
||||||
// --- PlayerTouchController-specific features (MoveToAndNotify) ---
|
|
||||||
public delegate void ArrivedAtTargetHandler();
|
|
||||||
private Coroutine _moveToCoroutine;
|
|
||||||
public event ArrivedAtTargetHandler OnArrivedAtTarget;
|
|
||||||
public event System.Action OnMoveToCancelled;
|
|
||||||
private bool _interruptMoveTo;
|
|
||||||
|
|
||||||
// Save system configuration
|
// Save system configuration
|
||||||
public override bool AutoRegisterForSave => true;
|
public override bool AutoRegisterForSave => true;
|
||||||
@@ -51,89 +46,6 @@ namespace Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region ITouchInputConsumer Overrides (Add InterruptMoveTo)
|
|
||||||
|
|
||||||
public override void OnTap(Vector2 worldPosition)
|
|
||||||
{
|
|
||||||
InterruptMoveTo();
|
|
||||||
base.OnTap(worldPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnHoldStart(Vector2 worldPosition)
|
|
||||||
{
|
|
||||||
InterruptMoveTo();
|
|
||||||
base.OnHoldStart(worldPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Moves the player to a specific target position and notifies via events when arrived or cancelled.
|
|
||||||
/// This is used by systems like Pickup.cs to orchestrate movement.
|
|
||||||
/// </summary>
|
|
||||||
public void MoveToAndNotify(Vector3 target)
|
|
||||||
{
|
|
||||||
// Cancel any previous move-to coroutine
|
|
||||||
if (_moveToCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_moveToCoroutine);
|
|
||||||
}
|
|
||||||
|
|
||||||
_interruptMoveTo = false;
|
|
||||||
// Ensure pathfinding is enabled for MoveToAndNotify
|
|
||||||
if (_aiPath != null)
|
|
||||||
{
|
|
||||||
_aiPath.enabled = true;
|
|
||||||
_aiPath.canMove = true;
|
|
||||||
_aiPath.isStopped = false;
|
|
||||||
}
|
|
||||||
_moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cancels any in-progress MoveToAndNotify operation and fires the cancellation event.
|
|
||||||
/// </summary>
|
|
||||||
public void InterruptMoveTo()
|
|
||||||
{
|
|
||||||
_interruptMoveTo = true;
|
|
||||||
_isHolding = false;
|
|
||||||
_directMoveVelocity = Vector3.zero;
|
|
||||||
if (Settings.DefaultHoldMovementMode == HoldMovementMode.Direct && _aiPath != null)
|
|
||||||
_aiPath.enabled = false;
|
|
||||||
OnMoveToCancelled?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Coroutine for moving the player to a target position and firing arrival/cancel events.
|
|
||||||
/// </summary>
|
|
||||||
private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target)
|
|
||||||
{
|
|
||||||
if (_aiPath != null)
|
|
||||||
{
|
|
||||||
_aiPath.destination = target;
|
|
||||||
_aiPath.maxSpeed = Settings.MoveSpeed;
|
|
||||||
_aiPath.maxAcceleration = Settings.MaxAcceleration;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!_interruptMoveTo)
|
|
||||||
{
|
|
||||||
Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
|
|
||||||
Vector2 target2D = new Vector2(target.x, target.y);
|
|
||||||
float dist = Vector2.Distance(current2D, target2D);
|
|
||||||
if (dist <= Settings.StopDistance + 0.2f)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_moveToCoroutine = null;
|
|
||||||
if (!_interruptMoveTo)
|
|
||||||
{
|
|
||||||
OnArrivedAtTarget?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Save/Load Lifecycle Hooks
|
#region Save/Load Lifecycle Hooks
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using UnityEngine.Events;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Core;
|
using Core;
|
||||||
using Core.Lifecycle;
|
using Core.Lifecycle;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace Interactions
|
namespace Interactions
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,7 @@ namespace Interactions
|
|||||||
public UnityEvent characterArrived;
|
public UnityEvent characterArrived;
|
||||||
public UnityEvent<bool> interactionComplete;
|
public UnityEvent<bool> interactionComplete;
|
||||||
|
|
||||||
private PlayerTouchController playerRef;
|
private IInteractingCharacter _interactingCharacter;
|
||||||
protected FollowerController FollowerController;
|
protected FollowerController FollowerController;
|
||||||
private bool isActive = true;
|
private bool isActive = true;
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ namespace Interactions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispatch an interaction event to all registered actions and await their completion
|
/// Dispatch an interaction event to all registered actions and await their completion
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task DispatchEventAsync(InteractionEventType eventType)
|
private async Task DispatchEventAsync(InteractionEventType eventType, PlayerTouchController playerRef = null)
|
||||||
{
|
{
|
||||||
// Collect all tasks from actions that want to respond
|
// Collect all tasks from actions that want to respond
|
||||||
List<Task<bool>> tasks = new List<Task<bool>>();
|
List<Task<bool>> tasks = new List<Task<bool>>();
|
||||||
@@ -114,26 +115,45 @@ namespace Interactions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task StartInteractionFlowAsync()
|
private async Task StartInteractionFlowAsync()
|
||||||
{
|
{
|
||||||
// 2. Find characters
|
// 2. Find characters - get the ACTIVE controller from InputManager
|
||||||
playerRef = FindFirstObjectByType<PlayerTouchController>();
|
BasePlayerMovementController playerController = null;
|
||||||
|
|
||||||
|
if (InputManager.Instance != null)
|
||||||
|
{
|
||||||
|
// Get the controller that currently has input control
|
||||||
|
var activeController = InputManager.Instance.GetActiveController();
|
||||||
|
playerController = activeController as BasePlayerMovementController;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: if InputManager doesn't have an active controller, try to find PlayerTouchController specifically
|
||||||
|
if (playerController == null)
|
||||||
|
{
|
||||||
|
playerController = FindFirstObjectByType<PlayerTouchController>();
|
||||||
|
Logging.Warning("[Interactable] No active controller from InputManager, falling back to FindFirstObjectByType<PlayerTouchController>");
|
||||||
|
}
|
||||||
|
|
||||||
|
_interactingCharacter = playerController;
|
||||||
FollowerController = FindFirstObjectByType<FollowerController>();
|
FollowerController = FindFirstObjectByType<FollowerController>();
|
||||||
|
|
||||||
|
// For legacy event compatibility, try to get PlayerTouchController reference
|
||||||
|
var playerRef = playerController as PlayerTouchController;
|
||||||
|
|
||||||
// 3. Virtual hook: Setup
|
// 3. Virtual hook: Setup
|
||||||
OnInteractionStarted();
|
OnInteractionStarted();
|
||||||
|
|
||||||
// 4. Fire events
|
// 4. Fire events
|
||||||
interactionStarted?.Invoke(playerRef, FollowerController);
|
interactionStarted?.Invoke(playerRef, FollowerController);
|
||||||
await DispatchEventAsync(InteractionEventType.InteractionStarted);
|
await DispatchEventAsync(InteractionEventType.InteractionStarted, playerRef);
|
||||||
|
|
||||||
// 5. Orchestrate character movement
|
// 5. Orchestrate character movement
|
||||||
await MoveCharactersAsync();
|
await MoveCharactersAsync(playerRef);
|
||||||
|
|
||||||
// 6. Virtual hook: Arrival reaction
|
// 6. Virtual hook: Arrival reaction
|
||||||
OnInteractingCharacterArrived();
|
OnInteractingCharacterArrived();
|
||||||
|
|
||||||
// 7. Fire arrival events
|
// 7. Fire arrival events
|
||||||
characterArrived?.Invoke();
|
characterArrived?.Invoke();
|
||||||
await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
|
await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived, playerRef);
|
||||||
|
|
||||||
// 8. Validation (base + child)
|
// 8. Validation (base + child)
|
||||||
var (canProceed, errorMessage) = ValidateInteraction();
|
var (canProceed, errorMessage) = ValidateInteraction();
|
||||||
@@ -143,7 +163,7 @@ namespace Interactions
|
|||||||
{
|
{
|
||||||
DebugUIMessage.Show(errorMessage, Color.yellow);
|
DebugUIMessage.Show(errorMessage, Color.yellow);
|
||||||
}
|
}
|
||||||
FinishInteraction(false);
|
FinishInteraction(false, playerRef);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +171,7 @@ namespace Interactions
|
|||||||
bool success = DoInteraction();
|
bool success = DoInteraction();
|
||||||
|
|
||||||
// 10. Finish up
|
// 10. Finish up
|
||||||
FinishInteraction(success);
|
FinishInteraction(success, playerRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Virtual Lifecycle Methods
|
#region Virtual Lifecycle Methods
|
||||||
@@ -262,13 +282,13 @@ namespace Interactions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Orchestrates character movement based on characterToInteract setting.
|
/// Orchestrates character movement based on characterToInteract setting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task MoveCharactersAsync()
|
private async Task MoveCharactersAsync(PlayerTouchController playerRef = null)
|
||||||
{
|
{
|
||||||
if (playerRef == null)
|
if (_interactingCharacter == null)
|
||||||
{
|
{
|
||||||
Logging.Debug($"[Interactable] Player character could not be found. Aborting interaction.");
|
Logging.Debug($"[Interactable] No interacting character found. Aborting interaction.");
|
||||||
interactionInterrupted.Invoke();
|
interactionInterrupted.Invoke();
|
||||||
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
|
await DispatchEventAsync(InteractionEventType.InteractionInterrupted, playerRef);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,31 +298,42 @@ namespace Interactions
|
|||||||
return; // Continue to arrival
|
return; // Continue to arrival
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move player and optionally follower based on characterToInteract setting
|
// Move the appropriate character based on characterToInteract setting
|
||||||
if (characterToInteract == CharacterToInteract.Trafalgar)
|
if (characterToInteract == CharacterToInteract.Trafalgar)
|
||||||
{
|
{
|
||||||
await MovePlayerAsync();
|
await MoveCharacterAsync(_interactingCharacter, CharacterToInteract.Trafalgar, playerRef);
|
||||||
}
|
}
|
||||||
else if (characterToInteract == CharacterToInteract.Pulver || characterToInteract == CharacterToInteract.Both)
|
else if (characterToInteract == CharacterToInteract.Pulver)
|
||||||
{
|
{
|
||||||
await MovePlayerAsync(); // Move player to range first
|
await MoveCharacterAsync(_interactingCharacter, CharacterToInteract.Pulver, playerRef);
|
||||||
await MoveFollowerAsync(); // Then move follower to interaction point
|
}
|
||||||
|
else if (characterToInteract == CharacterToInteract.Both)
|
||||||
|
{
|
||||||
|
await MoveCharacterAsync(_interactingCharacter, CharacterToInteract.Trafalgar, playerRef); // Move first character to range
|
||||||
|
await MoveFollowerAsync(playerRef); // Then move follower to interaction point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves the player to the interaction point or custom target.
|
/// Moves a character controller to the interaction point or custom target.
|
||||||
|
/// Works with any controller implementing IInteractingCharacter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task MovePlayerAsync()
|
private async Task MoveCharacterAsync(IInteractingCharacter character, CharacterToInteract targetCharacterType, PlayerTouchController playerRef = null)
|
||||||
{
|
{
|
||||||
|
if (character == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[Interactable] Cannot move null character");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 stopPoint = transform.position; // Default to interactable position
|
Vector3 stopPoint = transform.position; // Default to interactable position
|
||||||
bool customTargetFound = false;
|
bool customTargetFound = false;
|
||||||
|
|
||||||
// Check for a CharacterMoveToTarget component for Trafalgar or Both
|
// Check for a CharacterMoveToTarget component
|
||||||
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
||||||
foreach (var target in moveTargets)
|
foreach (var target in moveTargets)
|
||||||
{
|
{
|
||||||
if (target.characterType == CharacterToInteract.Trafalgar || target.characterType == CharacterToInteract.Both)
|
if (target.characterType == targetCharacterType || target.characterType == CharacterToInteract.Both)
|
||||||
{
|
{
|
||||||
stopPoint = target.GetTargetPosition();
|
stopPoint = target.GetTargetPosition();
|
||||||
customTargetFound = true;
|
customTargetFound = true;
|
||||||
@@ -314,49 +345,26 @@ namespace Interactions
|
|||||||
if (!customTargetFound)
|
if (!customTargetFound)
|
||||||
{
|
{
|
||||||
Vector3 interactablePos = transform.position;
|
Vector3 interactablePos = transform.position;
|
||||||
Vector3 playerPos = playerRef.transform.position;
|
Vector3 characterPos = character.transform.position;
|
||||||
float stopDistance = characterToInteract == CharacterToInteract.Pulver
|
float stopDistance = targetCharacterType == CharacterToInteract.Pulver
|
||||||
? GameManager.Instance.PlayerStopDistance
|
? GameManager.Instance.PlayerStopDistance
|
||||||
: GameManager.Instance.PlayerStopDistanceDirectInteraction;
|
: GameManager.Instance.PlayerStopDistanceDirectInteraction;
|
||||||
Vector3 toPlayer = (playerPos - interactablePos).normalized;
|
stopPoint = MovementUtilities.CalculateStopPosition(interactablePos, characterPos, stopDistance);
|
||||||
stopPoint = interactablePos + toPlayer * stopDistance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for player to arrive
|
// Use MovementUtilities to handle movement
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
bool arrived = await MovementUtilities.MoveToPositionAsync(character, stopPoint);
|
||||||
|
|
||||||
void OnPlayerArrivedLocal()
|
if (!arrived)
|
||||||
{
|
{
|
||||||
if (playerRef != null)
|
_ = HandleInteractionCancelledAsync(playerRef);
|
||||||
{
|
|
||||||
playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
|
||||||
playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
|
||||||
}
|
|
||||||
tcs.TrySetResult(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerMoveCancelledLocal()
|
|
||||||
{
|
|
||||||
if (playerRef != null)
|
|
||||||
{
|
|
||||||
playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
|
||||||
playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
|
||||||
}
|
|
||||||
_ = HandleInteractionCancelledAsync();
|
|
||||||
tcs.TrySetResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
|
|
||||||
playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
|
|
||||||
playerRef.MoveToAndNotify(stopPoint);
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves the follower to the interaction point or custom target.
|
/// Moves the follower to the interaction point or custom target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task MoveFollowerAsync()
|
private async Task MoveFollowerAsync(PlayerTouchController playerRef = null)
|
||||||
{
|
{
|
||||||
if (FollowerController == null)
|
if (FollowerController == null)
|
||||||
return;
|
return;
|
||||||
@@ -383,7 +391,7 @@ namespace Interactions
|
|||||||
FollowerController.OnPickupArrived -= OnFollowerArrivedLocal;
|
FollowerController.OnPickupArrived -= OnFollowerArrivedLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell follower to return to player
|
// Tell follower to return to player if we have a PlayerTouchController reference
|
||||||
if (FollowerController != null && playerRef != null)
|
if (FollowerController != null && playerRef != null)
|
||||||
{
|
{
|
||||||
FollowerController.ReturnToPlayer(playerRef.transform);
|
FollowerController.ReturnToPlayer(playerRef.transform);
|
||||||
@@ -401,10 +409,10 @@ namespace Interactions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles interaction being cancelled (player stopped moving).
|
/// Handles interaction being cancelled (player stopped moving).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task HandleInteractionCancelledAsync()
|
private async Task HandleInteractionCancelledAsync(PlayerTouchController playerRef = null)
|
||||||
{
|
{
|
||||||
interactionInterrupted?.Invoke();
|
interactionInterrupted?.Invoke();
|
||||||
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
|
await DispatchEventAsync(InteractionEventType.InteractionInterrupted, playerRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -414,14 +422,14 @@ namespace Interactions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finalizes the interaction after DoInteraction completes.
|
/// Finalizes the interaction after DoInteraction completes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async void FinishInteraction(bool success)
|
private async void FinishInteraction(bool success, PlayerTouchController playerRef = null)
|
||||||
{
|
{
|
||||||
// Virtual hook: Cleanup
|
// Virtual hook: Cleanup
|
||||||
OnInteractionFinished(success);
|
OnInteractionFinished(success);
|
||||||
|
|
||||||
// Fire completion events
|
// Fire completion events
|
||||||
interactionComplete?.Invoke(success);
|
interactionComplete?.Invoke(success);
|
||||||
await DispatchEventAsync(InteractionEventType.InteractionComplete);
|
await DispatchEventAsync(InteractionEventType.InteractionComplete, playerRef);
|
||||||
|
|
||||||
// Handle one-time / cooldown
|
// Handle one-time / cooldown
|
||||||
if (success)
|
if (success)
|
||||||
@@ -437,7 +445,7 @@ namespace Interactions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
playerRef = null;
|
_interactingCharacter = null;
|
||||||
FollowerController = null;
|
FollowerController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace Minigames.TrashMaze.Core
|
|||||||
/// Controls Pulver character movement in the Trash Maze.
|
/// Controls Pulver character movement in the Trash Maze.
|
||||||
/// Inherits from BasePlayerMovementController for tap-to-move and hold-to-move.
|
/// Inherits from BasePlayerMovementController for tap-to-move and hold-to-move.
|
||||||
/// Updates global shader properties for vision radius system.
|
/// Updates global shader properties for vision radius system.
|
||||||
|
/// Interaction capability (MoveToAndNotify) is provided by base class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PulverController : BasePlayerMovementController
|
public class PulverController : BasePlayerMovementController
|
||||||
{
|
{
|
||||||
|
|||||||
3
Assets/Scripts/Utilities.meta
Normal file
3
Assets/Scripts/Utilities.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 56c21fe8abef4887a819d02c0fbdb5d8
|
||||||
|
timeCreated: 1765753993
|
||||||
66
Assets/Scripts/Utils/MovementUtilities.cs
Normal file
66
Assets/Scripts/Utils/MovementUtilities.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Core;
|
||||||
|
using Input;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for character movement operations.
|
||||||
|
/// Extracted from interaction/controller code for reusability.
|
||||||
|
/// </summary>
|
||||||
|
public static class MovementUtilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Moves a character to a target position and waits for arrival.
|
||||||
|
/// Works with any controller implementing IInteractingCharacter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character">The character to move (must implement IInteractingCharacter)</param>
|
||||||
|
/// <param name="targetPosition">World position to move to</param>
|
||||||
|
/// <returns>Task that completes when the character arrives or movement is cancelled</returns>
|
||||||
|
public static async Task<bool> MoveToPositionAsync(IInteractingCharacter character, Vector3 targetPosition)
|
||||||
|
{
|
||||||
|
if (character == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[MovementUtilities] Cannot move null character");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
void OnArrivedLocal()
|
||||||
|
{
|
||||||
|
character.OnArrivedAtTarget -= OnArrivedLocal;
|
||||||
|
character.OnMoveToCancelled -= OnCancelledLocal;
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnCancelledLocal()
|
||||||
|
{
|
||||||
|
character.OnArrivedAtTarget -= OnArrivedLocal;
|
||||||
|
character.OnMoveToCancelled -= OnCancelledLocal;
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
character.OnArrivedAtTarget += OnArrivedLocal;
|
||||||
|
character.OnMoveToCancelled += OnCancelledLocal;
|
||||||
|
character.MoveToAndNotify(targetPosition);
|
||||||
|
|
||||||
|
return await tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates a stop position at a given distance from a target position towards a character.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetPosition">The target position</param>
|
||||||
|
/// <param name="characterPosition">The character's current position</param>
|
||||||
|
/// <param name="stopDistance">Distance from target to stop at</param>
|
||||||
|
/// <returns>The calculated stop position</returns>
|
||||||
|
public static Vector3 CalculateStopPosition(Vector3 targetPosition, Vector3 characterPosition, float stopDistance)
|
||||||
|
{
|
||||||
|
Vector3 toCharacter = (characterPosition - targetPosition).normalized;
|
||||||
|
return targetPosition + toCharacter * stopDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Scripts/Utils/MovementUtilities.cs.meta
Normal file
3
Assets/Scripts/Utils/MovementUtilities.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 29f4ca2c743f4890aab59e4ccdda2c79
|
||||||
|
timeCreated: 1765753993
|
||||||
Reference in New Issue
Block a user