300 lines
12 KiB
C#
300 lines
12 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using System.Collections;
|
|||
|
|
using Unity.Cinemachine;
|
|||
|
|
using Data.CardSystem;
|
|||
|
|
using Pixelplacement;
|
|||
|
|
using Core;
|
|||
|
|
using UI;
|
|||
|
|
|
|||
|
|
namespace Interactions
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Special pickup that plays a fancy sequence when collected:
|
|||
|
|
/// - Follower picks up normally
|
|||
|
|
/// - Pauses follower movement
|
|||
|
|
/// - Zooms camera in
|
|||
|
|
/// - Grows/pulses the booster pack
|
|||
|
|
/// - Animates to backpack icon
|
|||
|
|
/// - Gives player a booster pack
|
|||
|
|
/// - Restores camera and resumes movement
|
|||
|
|
/// </summary>
|
|||
|
|
public class BoosterPackPickup : Pickup
|
|||
|
|
{
|
|||
|
|
[Header("Booster Pack Sequence")]
|
|||
|
|
[SerializeField] private GameObject glowVisualPrefab;
|
|||
|
|
[SerializeField] private CinemachineCamera zoomCamera;
|
|||
|
|
[SerializeField] private int boosterPackCount = 1;
|
|||
|
|
|
|||
|
|
[Header("Sequence Timing")]
|
|||
|
|
[SerializeField] private float cameraBlendWait = 0.3f;
|
|||
|
|
[SerializeField] private float growDuration = 0.25f;
|
|||
|
|
[SerializeField] private float pulseDuration = 0.3f;
|
|||
|
|
[SerializeField] private int pulseCount = 3;
|
|||
|
|
[SerializeField] private float disappearDuration = 0.5f;
|
|||
|
|
|
|||
|
|
[Header("Animation Settings")]
|
|||
|
|
[SerializeField] private float growScale = 1.5f;
|
|||
|
|
[SerializeField] private float pulseScale = 1.2f;
|
|||
|
|
|
|||
|
|
private GameObject glowInstance;
|
|||
|
|
private bool sequencePlaying;
|
|||
|
|
|
|||
|
|
protected override bool DoInteraction()
|
|||
|
|
{
|
|||
|
|
// Let the normal pickup flow happen first
|
|||
|
|
bool success = base.DoInteraction();
|
|||
|
|
|
|||
|
|
// If pickup was successful and we're not already playing the sequence, start it
|
|||
|
|
// IMPORTANT: We start the coroutine on the FollowerController, not on this object,
|
|||
|
|
// because this object gets disabled immediately after pickup
|
|||
|
|
if (success && !sequencePlaying)
|
|||
|
|
{
|
|||
|
|
var follower = FollowerController.FindInstance();
|
|||
|
|
if (follower != null)
|
|||
|
|
{
|
|||
|
|
follower.StartCoroutine(BoosterSequence(follower));
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Logging.Warning("[BoosterPackPickup] No follower found, cannot start sequence");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return success;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerator BoosterSequence(FollowerController follower)
|
|||
|
|
{
|
|||
|
|
sequencePlaying = true;
|
|||
|
|
|
|||
|
|
// IMPORTANT: Disable all children on the original pickup GameObject
|
|||
|
|
// This prevents glow effects and other child objects from appearing in Pulver's hand
|
|||
|
|
foreach (Transform child in transform)
|
|||
|
|
{
|
|||
|
|
child.gameObject.SetActive(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Wait one frame to ensure pickup is fully processed
|
|||
|
|
yield return null;
|
|||
|
|
|
|||
|
|
// Verify follower still exists (safety check)
|
|||
|
|
if (follower == null)
|
|||
|
|
{
|
|||
|
|
Logging.Warning("[BoosterPackPickup] Follower destroyed during sequence, aborting");
|
|||
|
|
sequencePlaying = false;
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Transform heldItemTransform = follower.GetHeldItemTransform();
|
|||
|
|
if (heldItemTransform == null)
|
|||
|
|
{
|
|||
|
|
Logging.Warning("[BoosterPackPickup] No held item transform found, skipping sequence");
|
|||
|
|
sequencePlaying = false;
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 1. Pause follower movement
|
|||
|
|
follower.PauseMovement();
|
|||
|
|
|
|||
|
|
// 2. Activate zoom camera if assigned
|
|||
|
|
if (zoomCamera != null)
|
|||
|
|
{
|
|||
|
|
// Unparent the camera from the booster pack (if it's parented)
|
|||
|
|
zoomCamera.transform.SetParent(null);
|
|||
|
|
|
|||
|
|
// Position camera at follower's location
|
|||
|
|
zoomCamera.transform.position = new Vector3(
|
|||
|
|
follower.transform.position.x,
|
|||
|
|
follower.transform.position.y,
|
|||
|
|
zoomCamera.transform.position.z
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Make the blend instant by setting a custom blend hint
|
|||
|
|
var brain = Camera.main?.GetComponent<CinemachineBrain>();
|
|||
|
|
CinemachineBlendDefinition originalBlend = default;
|
|||
|
|
if (brain != null)
|
|||
|
|
{
|
|||
|
|
originalBlend = brain.DefaultBlend;
|
|||
|
|
brain.DefaultBlend = new CinemachineBlendDefinition(CinemachineBlendDefinition.Styles.EaseInOut, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
zoomCamera.Priority = 20;
|
|||
|
|
zoomCamera.gameObject.SetActive(true);
|
|||
|
|
|
|||
|
|
// Wait a frame for the cut to take effect
|
|||
|
|
yield return null;
|
|||
|
|
|
|||
|
|
// Restore original blend settings
|
|||
|
|
if (brain != null)
|
|||
|
|
{
|
|||
|
|
brain.DefaultBlend = originalBlend;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. Grow animation (no glow during this)
|
|||
|
|
Vector3 originalScale = heldItemTransform.localScale;
|
|||
|
|
Tween.LocalScale(heldItemTransform, originalScale * growScale, growDuration, 0f, Tween.EaseOutBack);
|
|||
|
|
yield return new WaitForSeconds(growDuration);
|
|||
|
|
|
|||
|
|
// 4. Instantiate and pulse glow
|
|||
|
|
if (glowVisualPrefab != null)
|
|||
|
|
{
|
|||
|
|
glowInstance = Instantiate(glowVisualPrefab, heldItemTransform);
|
|||
|
|
glowInstance.transform.localPosition = Vector3.zero;
|
|||
|
|
glowInstance.transform.localScale = Vector3.one;
|
|||
|
|
|
|||
|
|
for (int i = 0; i < pulseCount; i++)
|
|||
|
|
{
|
|||
|
|
Tween.LocalScale(glowInstance.transform, Vector3.one * pulseScale, pulseDuration, 0f, Tween.EaseIn);
|
|||
|
|
yield return new WaitForSeconds(pulseDuration);
|
|||
|
|
Tween.LocalScale(glowInstance.transform, Vector3.one, pulseDuration, 0f, Tween.EaseInOut);
|
|||
|
|
yield return new WaitForSeconds(pulseDuration);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Delete glow before flying to button
|
|||
|
|
Destroy(glowInstance);
|
|||
|
|
glowInstance = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. Disappear to scrapbook button (backpack icon) - straight line movement
|
|||
|
|
GameObject scrabookButton = PlayerHudManager.Instance?.GetScrabookButton();
|
|||
|
|
if (scrabookButton != null)
|
|||
|
|
{
|
|||
|
|
// Get start position in world space
|
|||
|
|
Vector3 startPos = heldItemTransform.position;
|
|||
|
|
|
|||
|
|
// Convert UI button position to world space
|
|||
|
|
RectTransform buttonRect = scrabookButton.GetComponent<RectTransform>();
|
|||
|
|
Vector3 targetWorldPos;
|
|||
|
|
|
|||
|
|
if (buttonRect != null)
|
|||
|
|
{
|
|||
|
|
// UI element - get screen position correctly
|
|||
|
|
Vector3[] corners = new Vector3[4];
|
|||
|
|
buttonRect.GetWorldCorners(corners);
|
|||
|
|
|
|||
|
|
// Get center of the button in screen space
|
|||
|
|
Vector3 buttonCenter = (corners[0] + corners[2]) / 2f;
|
|||
|
|
|
|||
|
|
// For Screen Space Overlay, the corners are already in screen space
|
|||
|
|
Canvas canvas = buttonRect.GetComponentInParent<Canvas>();
|
|||
|
|
Vector3 buttonScreenPos;
|
|||
|
|
|
|||
|
|
if (canvas != null && canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
|||
|
|
{
|
|||
|
|
// Already in screen space
|
|||
|
|
buttonScreenPos = buttonCenter;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Convert from world to screen space
|
|||
|
|
buttonScreenPos = RectTransformUtility.WorldToScreenPoint(canvas?.worldCamera ?? Camera.main, buttonCenter);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Convert screen position to world space at the same Z as the booster pack
|
|||
|
|
targetWorldPos = Camera.main.ScreenToWorldPoint(new Vector3(buttonScreenPos.x, buttonScreenPos.y, Mathf.Abs(Camera.main.transform.position.z - startPos.z)));
|
|||
|
|
|
|||
|
|
Logging.Debug($"[BoosterPackPickup] Button screen pos: {buttonScreenPos}, converted world pos: {targetWorldPos}");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Not a UI element, use direct world position
|
|||
|
|
targetWorldPos = scrabookButton.transform.position;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Logging.Debug($"[BoosterPackPickup] Flying from {startPos} to {targetWorldPos} (button: {scrabookButton.name})");
|
|||
|
|
|
|||
|
|
// Use custom tween with linear interpolation for straight line
|
|||
|
|
float elapsed = 0f;
|
|||
|
|
Vector3 startScale = heldItemTransform.localScale;
|
|||
|
|
|
|||
|
|
while (elapsed < disappearDuration)
|
|||
|
|
{
|
|||
|
|
elapsed += Time.deltaTime;
|
|||
|
|
float t = Mathf.Clamp01(elapsed / disappearDuration);
|
|||
|
|
|
|||
|
|
// Linear interpolation for straight line movement
|
|||
|
|
heldItemTransform.position = Vector3.Lerp(startPos, targetWorldPos, t);
|
|||
|
|
|
|||
|
|
// Ease in for scale (gets smaller as it approaches)
|
|||
|
|
float scaleT = t * t; // Simple ease-in curve
|
|||
|
|
heldItemTransform.localScale = Vector3.Lerp(startScale, Vector3.zero, scaleT);
|
|||
|
|
|
|||
|
|
yield return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Ensure final position/scale
|
|||
|
|
heldItemTransform.position = targetWorldPos;
|
|||
|
|
heldItemTransform.localScale = Vector3.zero;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Logging.Warning("[BoosterPackPickup] No scrapbook button found from PlayerHudManager, skipping fly-to animation");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 7. Give booster pack to player
|
|||
|
|
if (CardSystemManager.Instance != null)
|
|||
|
|
{
|
|||
|
|
CardSystemManager.Instance.AddBoosterPack(boosterPackCount);
|
|||
|
|
Logging.Debug($"[BoosterPackPickup] Gave {boosterPackCount} booster pack(s) to player");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Logging.Warning("[BoosterPackPickup] CardSystemManager not found, cannot give booster pack");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 8. Cleanup glow
|
|||
|
|
if (glowInstance != null)
|
|||
|
|
{
|
|||
|
|
Destroy(glowInstance);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 9. Blend back to main camera and cleanup
|
|||
|
|
if (zoomCamera != null)
|
|||
|
|
{
|
|||
|
|
// Lower priority to blend back to main camera
|
|||
|
|
zoomCamera.Priority = 0;
|
|||
|
|
|
|||
|
|
// Wait for blend back to complete (using default blend settings)
|
|||
|
|
var brain = Camera.main?.GetComponent<CinemachineBrain>();
|
|||
|
|
if (brain != null && brain.IsBlending)
|
|||
|
|
{
|
|||
|
|
// Wait for the blend to finish
|
|||
|
|
while (brain.IsBlending)
|
|||
|
|
{
|
|||
|
|
yield return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// If not blending or no brain, just wait a short moment
|
|||
|
|
yield return new WaitForSeconds(0.3f);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Destroy the zoom camera
|
|||
|
|
Destroy(zoomCamera.gameObject);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 10. Clear the follower's held item since we "consumed" it
|
|||
|
|
follower.ClearHeldItem();
|
|||
|
|
|
|||
|
|
// 11. Resume follower movement
|
|||
|
|
follower.ResumeMovement();
|
|||
|
|
|
|||
|
|
sequencePlaying = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal override void OnManagedDestroy()
|
|||
|
|
{
|
|||
|
|
base.OnManagedDestroy();
|
|||
|
|
|
|||
|
|
// Cleanup glow if still exists
|
|||
|
|
if (glowInstance != null)
|
|||
|
|
{
|
|||
|
|
Destroy(glowInstance);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|