using UnityEngine; using AppleHills.Core.Settings; using Core; namespace Input { /// /// Saveable data for PlayerTouchController state /// [System.Serializable] public class PlayerSaveData { public Vector3 worldPosition; public Quaternion worldRotation; } /// /// 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. /// Extends BasePlayerMovementController with save/load and MoveToAndNotify functionality. /// 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 public override bool AutoRegisterForSave => true; // Scene-specific SaveId - each level has its own player state public override string SaveId => $"{gameObject.scene.name}/PlayerController"; protected override void LoadSettings() { var configs = GameManager.GetSettingsObject(); _movementSettings = configs.DefaultPlayerMovement; } #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 /// /// 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. /// 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)); } /// /// Cancels any in-progress MoveToAndNotify operation and fires the cancellation event. /// public void InterruptMoveTo() { _interruptMoveTo = true; _isHolding = false; _directMoveVelocity = Vector3.zero; if (Settings.DefaultHoldMovementMode == HoldMovementMode.Direct && _aiPath != null) _aiPath.enabled = false; OnMoveToCancelled?.Invoke(); } /// /// Coroutine for moving the player to a target position and firing arrival/cancel events. /// 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 internal override string OnSceneSaveRequested() { var saveData = new PlayerSaveData { worldPosition = transform.position, worldRotation = transform.rotation }; return JsonUtility.ToJson(saveData); } internal override void OnSceneRestoreRequested(string serializedData) { if (string.IsNullOrEmpty(serializedData)) { Logging.Debug("[PlayerTouchController] No saved state to restore"); return; } try { var saveData = JsonUtility.FromJson(serializedData); if (saveData != null) { transform.position = saveData.worldPosition; transform.rotation = saveData.worldRotation; Logging.Debug($"[PlayerTouchController] Restored position: {saveData.worldPosition}"); } } catch (System.Exception ex) { Logging.Warning($"[PlayerTouchController] Failed to restore state: {ex.Message}"); } } #endregion } }