Valentine Notes Dlivery game flow changes and improvements.
This commit is contained in:
@@ -24,3 +24,25 @@ MonoBehaviour:
|
||||
m_PreInfinity: 0
|
||||
m_PostInfinity: 0
|
||||
m_RotationOrder: 0
|
||||
- From: '**ANY CAMERA**'
|
||||
To: TargetCamera
|
||||
Blend:
|
||||
Style: 6
|
||||
Time: 3
|
||||
CustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 0
|
||||
m_PostInfinity: 0
|
||||
m_RotationOrder: 0
|
||||
- From: TargetCamera
|
||||
To: '**ANY CAMERA**'
|
||||
Blend:
|
||||
Style: 1
|
||||
Time: 1
|
||||
CustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 0
|
||||
m_PostInfinity: 0
|
||||
m_RotationOrder: 0
|
||||
|
||||
@@ -788,8 +788,11 @@ MonoBehaviour:
|
||||
camera: {fileID: 761345710}
|
||||
- state: 3
|
||||
camera: {fileID: 263322553}
|
||||
- state: 4
|
||||
camera: {fileID: 1842736650}
|
||||
inactivePriority: 10
|
||||
activePriority: 20
|
||||
cinemachineBrain: {fileID: 1810521058}
|
||||
showDebugLogs: 0
|
||||
--- !u!4 &597044887
|
||||
Transform:
|
||||
@@ -3948,7 +3951,7 @@ Transform:
|
||||
m_GameObject: {fileID: 1810521056}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 12.800001, z: -10}
|
||||
m_LocalPosition: {x: -18.9, y: 9.100001, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
@@ -4136,6 +4139,101 @@ SpriteRenderer:
|
||||
m_SpriteTileMode: 0
|
||||
m_WasSpriteAssigned: 1
|
||||
m_SpriteSortPoint: 0
|
||||
--- !u!1 &1842736649
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1842736651}
|
||||
- component: {fileID: 1842736650}
|
||||
- component: {fileID: 1842736652}
|
||||
m_Layer: 0
|
||||
m_Name: TargetCamera
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1842736650
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1842736649}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f9dfa5b682dcd46bda6128250e975f58, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Priority:
|
||||
Enabled: 0
|
||||
m_Value: 0
|
||||
OutputChannel: 1
|
||||
StandbyUpdate: 2
|
||||
m_StreamingVersion: 20241001
|
||||
m_LegacyPriority: 0
|
||||
Target:
|
||||
TrackingTarget: {fileID: 0}
|
||||
LookAtTarget: {fileID: 0}
|
||||
CustomLookAtTarget: 0
|
||||
Lens:
|
||||
FieldOfView: 60
|
||||
OrthographicSize: 20
|
||||
NearClipPlane: 0.3
|
||||
FarClipPlane: 1000
|
||||
Dutch: 0
|
||||
ModeOverride: 0
|
||||
PhysicalProperties:
|
||||
GateFit: 2
|
||||
SensorSize: {x: 21.946, y: 16.002}
|
||||
LensShift: {x: 0, y: 0}
|
||||
FocusDistance: 10
|
||||
Iso: 200
|
||||
ShutterSpeed: 0.005
|
||||
Aperture: 16
|
||||
BladeCount: 5
|
||||
Curvature: {x: 2, y: 11}
|
||||
BarrelClipping: 0.25
|
||||
Anamorphism: 0
|
||||
BlendHint: 0
|
||||
--- !u!4 &1842736651
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1842736649}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: -9.6, y: 12.900002, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &1842736652
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1842736649}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b617507da6d07e749b7efdb34e1173e1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Cinemachine::Unity.Cinemachine.CinemachineFollow
|
||||
TrackerSettings:
|
||||
BindingMode: 4
|
||||
PositionDamping: {x: 1, y: 1, z: 1}
|
||||
AngularDampingMode: 0
|
||||
RotationDamping: {x: 1, y: 1, z: 1}
|
||||
QuaternionDamping: 1
|
||||
FollowOffset: {x: 0, y: 0, z: -10}
|
||||
--- !u!1 &1897459173
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4895,6 +4993,37 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &2041920295
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2041920296}
|
||||
m_Layer: 0
|
||||
m_Name: GameObject
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &2041920296
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2041920295}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 16.8, y: 12.6, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &2049960991
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -5024,81 +5153,6 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2090212067}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &2103114174
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2103114178}
|
||||
- component: {fileID: 2103114177}
|
||||
m_Layer: 0
|
||||
m_Name: CinemachineCamera
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &2103114177
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2103114174}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f9dfa5b682dcd46bda6128250e975f58, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Priority:
|
||||
Enabled: 0
|
||||
m_Value: 0
|
||||
OutputChannel: 1
|
||||
StandbyUpdate: 2
|
||||
m_StreamingVersion: 20241001
|
||||
m_LegacyPriority: 0
|
||||
Target:
|
||||
TrackingTarget: {fileID: 0}
|
||||
LookAtTarget: {fileID: 0}
|
||||
CustomLookAtTarget: 0
|
||||
Lens:
|
||||
FieldOfView: 60
|
||||
OrthographicSize: 15
|
||||
NearClipPlane: 0.3
|
||||
FarClipPlane: 1000
|
||||
Dutch: 0
|
||||
ModeOverride: 0
|
||||
PhysicalProperties:
|
||||
GateFit: 2
|
||||
SensorSize: {x: 21.946, y: 16.002}
|
||||
LensShift: {x: 0, y: 0}
|
||||
FocusDistance: 10
|
||||
Iso: 200
|
||||
ShutterSpeed: 0.005
|
||||
Aperture: 16
|
||||
BladeCount: 5
|
||||
Curvature: {x: 2, y: 11}
|
||||
BarrelClipping: 0.25
|
||||
Anamorphism: 0
|
||||
BlendHint: 0
|
||||
--- !u!4 &2103114178
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2103114174}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 12.800001, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &2111947703
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -5578,7 +5632,6 @@ SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 1810521061}
|
||||
- {fileID: 2103114178}
|
||||
- {fileID: 742382011}
|
||||
- {fileID: 2125208580}
|
||||
- {fileID: 1239857187}
|
||||
@@ -5596,8 +5649,10 @@ SceneRoots:
|
||||
- {fileID: 595230075}
|
||||
- {fileID: 2041347574}
|
||||
- {fileID: 761345711}
|
||||
- {fileID: 1842736651}
|
||||
- {fileID: 263322554}
|
||||
- {fileID: 1017291799}
|
||||
- {fileID: 1064542409}
|
||||
- {fileID: 1701327424}
|
||||
- {fileID: 2041920296}
|
||||
- {fileID: 1710395163}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using UnityEngine;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using UnityEngine.Playables;
|
||||
using Input;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
public class LevelIntroDirector : ManagedBehaviour
|
||||
namespace Cinematics
|
||||
{
|
||||
public class LevelIntroDirector : ManagedBehaviour
|
||||
{
|
||||
public bool playOnSceneReady;
|
||||
|
||||
[HideInInspector]
|
||||
@@ -38,4 +38,5 @@ public class LevelIntroDirector : ManagedBehaviour
|
||||
InputManager.Instance.SetInputMode(InputMode.InputDisabled);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ namespace Common.Camera
|
||||
[Tooltip("Priority for the active camera")]
|
||||
[SerializeField] protected int activePriority = 20;
|
||||
|
||||
[Header("Cinemachine Brain")]
|
||||
[Tooltip("CinemachineBrain for blend detection (auto-finds if null)")]
|
||||
[SerializeField] protected CinemachineBrain cinemachineBrain;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] protected bool showDebugLogs = false;
|
||||
|
||||
@@ -60,6 +64,11 @@ namespace Common.Camera
|
||||
private TState _currentState;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
// Event-driven blend tracking
|
||||
private CinemachineCamera _pendingBlendTarget;
|
||||
private bool _isBlendComplete;
|
||||
private Action _pendingBlendCallback;
|
||||
|
||||
public TState CurrentState => _currentState;
|
||||
|
||||
#endregion
|
||||
@@ -71,6 +80,11 @@ namespace Common.Camera
|
||||
/// </summary>
|
||||
public event Action<TState, TState> OnStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when camera blend completes after state switch
|
||||
/// </summary>
|
||||
public event Action OnBlendComplete;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
@@ -84,6 +98,17 @@ namespace Common.Camera
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
// Auto-find CinemachineBrain if not assigned
|
||||
if (cinemachineBrain == null)
|
||||
{
|
||||
cinemachineBrain = UnityEngine.Camera.main?.GetComponent<CinemachineBrain>();
|
||||
|
||||
if (cinemachineBrain == null && showDebugLogs)
|
||||
{
|
||||
Logging.Warning($"[{GetType().Name}] CinemachineBrain not found. Blend tracking will be unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize cameras from Inspector mappings
|
||||
InitializeCameraMap();
|
||||
|
||||
@@ -91,6 +116,35 @@ namespace Common.Camera
|
||||
ValidateCameras();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to Cinemachine events on enable
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
// Subscribe to Cinemachine global events
|
||||
CinemachineCore.BlendFinishedEvent.AddListener(OnBlendFinished);
|
||||
CinemachineCore.CameraActivatedEvent.AddListener(OnCameraActivated);
|
||||
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[{GetType().Name}] Subscribed to Cinemachine events");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from Cinemachine events on disable
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
// Unsubscribe from Cinemachine events to prevent memory leaks
|
||||
CinemachineCore.BlendFinishedEvent.RemoveListener(OnBlendFinished);
|
||||
CinemachineCore.CameraActivatedEvent.RemoveListener(OnCameraActivated);
|
||||
|
||||
// Clear any pending callbacks
|
||||
_pendingBlendCallback = null;
|
||||
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[{GetType().Name}] Unsubscribed from Cinemachine events");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
@@ -229,6 +283,115 @@ namespace Common.Camera
|
||||
return _cameraMap.ContainsKey(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend to a state and wait asynchronously (coroutine).
|
||||
/// Yields until Cinemachine blend event fires. Event-driven, no polling.
|
||||
/// Use this when you need to wait for the blend to complete before continuing.
|
||||
/// </summary>
|
||||
public System.Collections.IEnumerator BlendToStateAsync(TState newState)
|
||||
{
|
||||
// Reset completion flag
|
||||
_isBlendComplete = false;
|
||||
|
||||
// Set pending target camera
|
||||
if (!_cameraMap.TryGetValue(newState, out _pendingBlendTarget))
|
||||
{
|
||||
Logging.Error($"[{GetType().Name}] No camera for state {newState}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Switch camera state (triggers blend)
|
||||
SwitchToState(newState);
|
||||
|
||||
// Fallback: if no brain, complete immediately
|
||||
if (cinemachineBrain == null)
|
||||
{
|
||||
if (showDebugLogs)
|
||||
Logging.Warning($"[{GetType().Name}] No brain, completing blend immediately");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Wait for event to fire (event handlers set _isBlendComplete = true)
|
||||
yield return new WaitUntil(() => _isBlendComplete);
|
||||
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[{GetType().Name}] Blend to {newState} completed (event-driven)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend to a state with callback invoked on completion.
|
||||
/// Callback fires when Cinemachine blend event fires. Event-driven, no polling.
|
||||
/// Use this when you want to perform an action after the blend completes.
|
||||
/// </summary>
|
||||
public void BlendToState(TState newState, Action onComplete)
|
||||
{
|
||||
// Set pending target camera
|
||||
if (!_cameraMap.TryGetValue(newState, out _pendingBlendTarget))
|
||||
{
|
||||
Logging.Error($"[{GetType().Name}] No camera for state {newState}");
|
||||
onComplete?.Invoke(); // Still invoke to prevent hanging
|
||||
return;
|
||||
}
|
||||
|
||||
// Store callback
|
||||
_pendingBlendCallback = onComplete;
|
||||
|
||||
// Switch camera state (triggers blend)
|
||||
SwitchToState(newState);
|
||||
|
||||
// Fallback: if no brain, invoke callback immediately
|
||||
if (cinemachineBrain == null)
|
||||
{
|
||||
if (showDebugLogs)
|
||||
Logging.Warning($"[{GetType().Name}] No brain, invoking callback immediately");
|
||||
CompleteBlend();
|
||||
}
|
||||
|
||||
// Event handlers will invoke callback when blend finishes
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Called when Cinemachine finishes a blend (non-zero length blends)
|
||||
/// </summary>
|
||||
private void OnBlendFinished(ICinemachineMixer mixer, ICinemachineCamera cam)
|
||||
{
|
||||
// Filter: only respond to blends from our brain to our expected camera
|
||||
if (mixer == cinemachineBrain && cam == _pendingBlendTarget)
|
||||
{
|
||||
CompleteBlend();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when Cinemachine activates a camera (handles instant cuts)
|
||||
/// </summary>
|
||||
private void OnCameraActivated(ICinemachineCamera.ActivationEventParams evt)
|
||||
{
|
||||
// Filter: only respond to cuts from our brain to our expected camera
|
||||
if (evt.Origin == cinemachineBrain && evt.IncomingCamera == _pendingBlendTarget && evt.IsCut)
|
||||
{
|
||||
CompleteBlend();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark blend as complete and fire callbacks
|
||||
/// </summary>
|
||||
private void CompleteBlend()
|
||||
{
|
||||
_isBlendComplete = true;
|
||||
_pendingBlendCallback?.Invoke();
|
||||
_pendingBlendCallback = null;
|
||||
OnBlendComplete?.Invoke();
|
||||
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[{GetType().Name}] Blend completed, callbacks invoked");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validation
|
||||
|
||||
@@ -332,8 +332,11 @@ namespace Core.Settings
|
||||
float IntroDuration { get; }
|
||||
float PersonIntroDuration { get; }
|
||||
float EvaluationDuration { get; }
|
||||
float TargetFlybyLingerDuration { get; }
|
||||
float TargetFlybyCameraBlendTime { get; }
|
||||
|
||||
// Spawn System
|
||||
float PreSpawnBeyondTargetDistance { get; }
|
||||
float TargetMinDistance { get; }
|
||||
float TargetMaxDistance { get; }
|
||||
float ObjectSpawnMinDistance { get; } // Min distance between spawned objects
|
||||
|
||||
@@ -114,6 +114,30 @@ namespace Minigames.Airplane.Core
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneCameraManager] Stopped following airplane");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the target flyby camera to track the target's position
|
||||
/// </summary>
|
||||
public void SetTargetFlybyTracking(Transform targetTransform)
|
||||
{
|
||||
var flybyCamera = GetCamera(AirplaneCameraState.TargetFlyby);
|
||||
if (flybyCamera == null)
|
||||
{
|
||||
Logging.Warning("[AirplaneCameraManager] Cannot set flyby tracking - TargetFlyby camera not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetTransform == null)
|
||||
{
|
||||
Logging.Warning("[AirplaneCameraManager] Cannot set flyby tracking - target transform is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the tracking target on the flyby camera
|
||||
flybyCamera.Target.TrackingTarget = targetTransform;
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[AirplaneCameraManager] TargetFlyby camera now tracking: {targetTransform.gameObject.name}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Minigames.Airplane.Core
|
||||
|
||||
#region State
|
||||
|
||||
private AirplaneGameState _currentState = AirplaneGameState.AirplaneSelection;
|
||||
private AirplaneGameState _currentState = AirplaneGameState.Intro;
|
||||
private Person _currentPerson;
|
||||
private Person _previousPerson;
|
||||
private AirplaneController _currentAirplane;
|
||||
@@ -74,7 +74,7 @@ namespace Minigames.Airplane.Core
|
||||
private int _successCount;
|
||||
private int _failCount;
|
||||
private int _totalTurns;
|
||||
private AirplaneAbilityType _selectedAirplaneType;
|
||||
private IAirplaneSettings _cachedSettings;
|
||||
|
||||
public AirplaneGameState CurrentState => _currentState;
|
||||
public Person CurrentPerson => _currentPerson;
|
||||
@@ -98,6 +98,13 @@ namespace Minigames.Airplane.Core
|
||||
}
|
||||
_instance = this;
|
||||
|
||||
// Cache settings for performance
|
||||
_cachedSettings = GameManager.GetSettingsObject<IAirplaneSettings>();
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
Logging.Error("[AirplaneGameManager] Failed to load IAirplaneSettings!");
|
||||
}
|
||||
|
||||
// Validate references
|
||||
ValidateReferences();
|
||||
}
|
||||
@@ -216,74 +223,43 @@ namespace Minigames.Airplane.Core
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] ===== GAME STARTING =====");
|
||||
|
||||
// Start with intro camera blend, THEN show selection UI
|
||||
StartCoroutine(IntroSequence());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Turn Type Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Airplane selection sequence: show selection UI, wait for player choice
|
||||
/// Called AFTER intro camera blend
|
||||
/// Check if this is the first turn of the game
|
||||
/// </summary>
|
||||
private IEnumerator AirplaneSelectionSequence()
|
||||
private bool IsFirstTurn()
|
||||
{
|
||||
ChangeState(AirplaneGameState.AirplaneSelection);
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] === AIRPLANE SELECTION STARTING ===");
|
||||
|
||||
// Show selection UI
|
||||
if (selectionUI != null)
|
||||
{
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] SelectionUI found! GameObject: {selectionUI.gameObject.name}, Active: {selectionUI.gameObject.activeSelf}");
|
||||
}
|
||||
|
||||
selectionUI.Show();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] Called selectionUI.Show(). GameObject now active: {selectionUI.gameObject.activeSelf}");
|
||||
}
|
||||
|
||||
// Wait for player to select and confirm
|
||||
yield return new WaitUntil(() => selectionUI.HasSelectedType);
|
||||
|
||||
_selectedAirplaneType = selectionUI.GetSelectedType();
|
||||
selectionUI.Hide();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] Selected airplane: {_selectedAirplaneType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning("[AirplaneGameManager] ⚠️ selectionUI is NULL! Cannot show selection UI. Check Inspector.");
|
||||
Logging.Warning("[AirplaneGameManager] Using default airplane type from settings as fallback.");
|
||||
|
||||
// Fallback: use default type from settings
|
||||
var settings = GameManager.GetSettingsObject<IAirplaneSettings>();
|
||||
if (settings != null)
|
||||
{
|
||||
_selectedAirplaneType = settings.DefaultAirplaneType;
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] No selection UI, using default: {_selectedAirplaneType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedAirplaneType = AirplaneAbilityType.Jet; // Ultimate fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with hellos after selection
|
||||
yield return StartCoroutine(IntroHellosSequence());
|
||||
return _totalTurns == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Intro sequence: blend to intro camera, THEN show airplane selection
|
||||
/// Check if this is a new person (different from previous)
|
||||
/// </summary>
|
||||
private bool IsNewPerson()
|
||||
{
|
||||
return _previousPerson != _currentPerson;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this is a retry turn (same person as previous)
|
||||
/// </summary>
|
||||
private bool IsRetryTurn()
|
||||
{
|
||||
return !IsNewPerson() && _previousPerson != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flow: Intro Sequence
|
||||
|
||||
/// <summary>
|
||||
/// Initial intro sequence: blend to intro camera, introduce all people (stub), then start new person flow
|
||||
/// </summary>
|
||||
private IEnumerator IntroSequence()
|
||||
{
|
||||
@@ -291,116 +267,50 @@ namespace Minigames.Airplane.Core
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Playing intro sequence...");
|
||||
|
||||
// 1. Blend to intro camera
|
||||
// 1. Blend to intro camera and wait for blend to complete
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.SwitchToState(AirplaneCameraState.Intro);
|
||||
yield return new WaitForSeconds(0.5f); // Camera blend time
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.Intro));
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Intro camera ready. Now showing airplane selection...");
|
||||
// 2. Introduce all people (stub for now)
|
||||
yield return StartCoroutine(IntroduceAllPeople());
|
||||
|
||||
// 2. Show airplane selection UI and wait for player choice
|
||||
yield return StartCoroutine(AirplaneSelectionSequence());
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Intro complete, moving to first person...");
|
||||
|
||||
// 3. Start first person flow
|
||||
yield return StartCoroutine(ExecuteNewPersonFlow());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hellos sequence: all people greet, then blend to aiming camera
|
||||
/// Called AFTER airplane selection is complete
|
||||
/// Stub: All people say hello animation. Empty for now.
|
||||
/// </summary>
|
||||
private IEnumerator IntroHellosSequence()
|
||||
private IEnumerator IntroduceAllPeople()
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Starting hellos sequence...");
|
||||
|
||||
// 1. Iterate over each person and allow them to say their hellos
|
||||
if (personQueue != null && personQueue.HasMorePeople())
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Introducing all people...");
|
||||
|
||||
// Get all people from queue without removing them
|
||||
var allPeople = personQueue.GetAllPeople();
|
||||
foreach (var person in allPeople)
|
||||
{
|
||||
if (person != null)
|
||||
{
|
||||
// Wait for each person's greeting to complete
|
||||
yield return StartCoroutine(person.OnHello());
|
||||
}
|
||||
// TODO: All people say hello animation
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] (Stub) All people introduction");
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] All introductions complete");
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 2. Blend to aiming camera (first person's turn will start)
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.SwitchToState(AirplaneCameraState.Aiming);
|
||||
yield return new WaitForSeconds(0.5f); // Camera blend time
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Intro complete");
|
||||
|
||||
// Move to first person's turn
|
||||
StartCoroutine(SetupNextPerson());
|
||||
}
|
||||
#region Flow: New Person Flow
|
||||
|
||||
/// <summary>
|
||||
/// Setup the next person's turn
|
||||
/// New Person Flow: Executed for first person or after successful hit.
|
||||
/// Steps: Pop person → Introduce → Prepare level → Flyby → Select airplane → Aim → Shoot
|
||||
/// </summary>
|
||||
private IEnumerator SetupNextPerson()
|
||||
private IEnumerator ExecuteNewPersonFlow()
|
||||
{
|
||||
// Check if there are more people
|
||||
if (personQueue == null || !personQueue.HasMorePeople())
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] No more people, ending game");
|
||||
StartCoroutine(GameOver());
|
||||
yield return StartCoroutine(GameOver());
|
||||
yield break;
|
||||
}
|
||||
|
||||
ChangeState(AirplaneGameState.NextPerson);
|
||||
|
||||
// If this is NOT the first turn, handle post-shot reaction
|
||||
if (_currentPerson != null)
|
||||
{
|
||||
// Switch to next person camera for reaction/transition
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.SwitchToState(AirplaneCameraState.NextPerson);
|
||||
|
||||
// Wait for camera blend to complete before cleanup and reaction
|
||||
yield return new WaitForSeconds(0.5f); // Camera blend time
|
||||
}
|
||||
|
||||
// NOW cleanup spawned objects after camera has blended (camera shows scene before cleanup)
|
||||
if (spawnManager != null)
|
||||
{
|
||||
if (_lastShotHit)
|
||||
{
|
||||
// Success: Full cleanup - destroy all spawned objects and target
|
||||
spawnManager.CleanupSpawnedObjects();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Cleaned up spawned objects after successful shot");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failure: Keep spawned objects for retry, just reset tracking state
|
||||
spawnManager.ResetForRetry();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Kept spawned objects for retry after miss");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the previous person's reaction (celebrate/disappointment), removal (if hit), and shuffle
|
||||
yield return StartCoroutine(personQueue.HandlePostShotReaction(_lastShotHit));
|
||||
}
|
||||
|
||||
// Get the next person (now at front of queue after potential removal)
|
||||
// Pop next person from queue
|
||||
_previousPerson = _currentPerson;
|
||||
_currentPerson = personQueue.PopNextPerson();
|
||||
_totalTurns++;
|
||||
@@ -411,76 +321,308 @@ namespace Minigames.Airplane.Core
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Check if this is a NEW person (different from previous) or a retry (same person)
|
||||
bool isNewPerson = _previousPerson != _currentPerson;
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
string turnType = isNewPerson ? "NEW PERSON" : "RETRY";
|
||||
Logging.Debug($"[AirplaneGameManager] === Turn {_totalTurns}: {_currentPerson.PersonName} ({turnType}) ===" +
|
||||
Logging.Debug($"[AirplaneGameManager] === NEW PERSON FLOW: Turn {_totalTurns}: {_currentPerson.PersonName} ===" +
|
||||
$"\n Target: {_currentPerson.TargetName}");
|
||||
}
|
||||
|
||||
OnPersonStartTurn?.Invoke(_currentPerson);
|
||||
|
||||
// Only introduce if this is a NEW person
|
||||
if (isNewPerson && _previousPerson != null)
|
||||
{
|
||||
// Switching to a new person (after success) - they say hello
|
||||
yield return StartCoroutine(personQueue.IntroduceNextPerson());
|
||||
// 1. Introduce this person
|
||||
yield return StartCoroutine(IntroducePerson());
|
||||
|
||||
// 2. Prepare level (spawn objects, target)
|
||||
yield return StartCoroutine(PrepareLevel());
|
||||
|
||||
// 3. Execute target flyby
|
||||
yield return StartCoroutine(ExecuteTargetFlyby());
|
||||
|
||||
// 4. Select airplane
|
||||
AirplaneAbilityType selectedType = AirplaneAbilityType.None;
|
||||
yield return StartCoroutine(SelectAirplane((type) => selectedType = type));
|
||||
|
||||
// 5. Enter aiming state with selected airplane
|
||||
EnterAimingState(selectedType);
|
||||
|
||||
// Flow continues to shot → evaluation → routing
|
||||
}
|
||||
else if (_previousPerson == null)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flow: Repeat Shot Flow
|
||||
|
||||
/// <summary>
|
||||
/// Repeat Shot Flow: Executed when same person retries after missing.
|
||||
/// Steps: Brief camera → React (disappointed) → Select airplane → Aim → Shoot
|
||||
/// </summary>
|
||||
private IEnumerator ExecuteRepeatShotFlow()
|
||||
{
|
||||
// First turn - they already said hello during intro, just brief camera pause
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] === REPEAT SHOT FLOW: Turn {_totalTurns}: {_currentPerson.PersonName} (RETRY) ===" +
|
||||
$"\n Target: {_currentPerson.TargetName}");
|
||||
}
|
||||
|
||||
_totalTurns++;
|
||||
OnPersonStartTurn?.Invoke(_currentPerson);
|
||||
|
||||
// 1. Switch to next person camera briefly and wait for blend
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.SwitchToState(AirplaneCameraState.NextPerson);
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.NextPerson));
|
||||
}
|
||||
}
|
||||
// else: Same person retry (after failure) - skip introduction, go straight to aiming
|
||||
|
||||
// Initialize spawn manager for this person's target
|
||||
// 2. Play person reaction (disappointment)
|
||||
yield return StartCoroutine(PlayPersonReaction(false));
|
||||
|
||||
// 3. Reset spawn manager for retry (keeps spawned objects)
|
||||
if (spawnManager != null)
|
||||
{
|
||||
// Pass retry flag: true if same person, false if new person
|
||||
bool isRetry = !isNewPerson;
|
||||
spawnManager.InitializeForGame(_currentPerson.TargetName, isRetry);
|
||||
spawnManager.ResetForRetry();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Kept spawned objects for retry");
|
||||
}
|
||||
}
|
||||
|
||||
// Queue done - continue game flow
|
||||
// 4. Select airplane (new choice for this shot)
|
||||
AirplaneAbilityType selectedType = AirplaneAbilityType.None;
|
||||
yield return StartCoroutine(SelectAirplane((type) => selectedType = type));
|
||||
|
||||
// 5. Enter aiming state with selected airplane
|
||||
EnterAimingState(selectedType);
|
||||
|
||||
// Flow continues to shot → evaluation → routing
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flow: Person Shuffle Flow
|
||||
|
||||
/// <summary>
|
||||
/// Person Shuffle Flow: Executed after successful hit.
|
||||
/// Steps: Camera → React (celebrate) → Advance queue → Cleanup → Next person or game over
|
||||
/// </summary>
|
||||
private IEnumerator ExecutePersonShuffleFlow()
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] === PERSON SHUFFLE FLOW ===");
|
||||
|
||||
// 1. Switch to next person camera and wait for blend
|
||||
if (cameraManager != null)
|
||||
{
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.NextPerson));
|
||||
}
|
||||
|
||||
// 2. Play person reaction (celebration)
|
||||
yield return StartCoroutine(PlayPersonReaction(true));
|
||||
|
||||
// 3. Advance queue (remove person, shuffle)
|
||||
if (personQueue != null)
|
||||
{
|
||||
yield return StartCoroutine(personQueue.AdvanceQueue(true));
|
||||
}
|
||||
|
||||
// 4. Cleanup level (destroy all spawned objects)
|
||||
if (spawnManager != null)
|
||||
{
|
||||
spawnManager.CleanupLevel(true);
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Cleaned up spawned objects after success");
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Check if more people, then continue to new person flow or game over
|
||||
if (personQueue != null && personQueue.HasMorePeople())
|
||||
{
|
||||
yield return StartCoroutine(ExecuteNewPersonFlow());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return StartCoroutine(GameOver());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Phase Methods
|
||||
|
||||
/// <summary>
|
||||
/// Phase: Introduce current person
|
||||
/// </summary>
|
||||
private IEnumerator IntroducePerson()
|
||||
{
|
||||
ChangeState(AirplaneGameState.NextPerson);
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[AirplaneGameManager] Introducing {_currentPerson.PersonName}...");
|
||||
|
||||
// Blend to next person camera if not already there
|
||||
// (Person shuffle flow already blends to NextPerson, so might already be there)
|
||||
if (cameraManager != null && cameraManager.CurrentState != AirplaneCameraState.NextPerson)
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Blending to NextPerson camera...");
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.NextPerson));
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Blend to NextPerson complete");
|
||||
}
|
||||
else if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Already on NextPerson camera, skipping blend");
|
||||
}
|
||||
|
||||
// Person says hello
|
||||
if (showDebugLogs) Logging.Debug($"[AirplaneGameManager] Calling OnHello for {_currentPerson.PersonName}...");
|
||||
yield return StartCoroutine(_currentPerson.OnHello());
|
||||
if (showDebugLogs) Logging.Debug($"[AirplaneGameManager] OnHello complete for {_currentPerson.PersonName}");
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Introduction complete");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase: Prepare level (spawn ground, objects, target)
|
||||
/// </summary>
|
||||
private IEnumerator PrepareLevel()
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Preparing level...");
|
||||
|
||||
if (spawnManager == null)
|
||||
{
|
||||
Logging.Error("[AirplaneGameManager] Cannot prepare level - spawn manager not assigned!");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Initialize spawn manager with new target
|
||||
spawnManager.InitializeForGame(_currentPerson.TargetName, isRetry: false);
|
||||
|
||||
// Pre-spawn entire level
|
||||
spawnManager.PreSpawnLevelToTarget();
|
||||
|
||||
// Set flyby camera tracking on spawned target
|
||||
if (cameraManager != null)
|
||||
{
|
||||
Transform targetTransform = spawnManager.GetSpawnedTargetTransform();
|
||||
if (targetTransform != null)
|
||||
{
|
||||
cameraManager.SetTargetFlybyTracking(targetTransform);
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Target camera now tracking spawned target");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set expected target for validator
|
||||
if (targetValidator != null)
|
||||
{
|
||||
targetValidator.SetExpectedTarget(_currentPerson.TargetName);
|
||||
}
|
||||
|
||||
// Enter aiming state
|
||||
EnterAimingState();
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Level preparation complete");
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enter aiming state - player can aim and launch
|
||||
/// Phase: Select airplane type (shows UI, waits for player choice)
|
||||
/// Blends to Aiming camera if not already there (needed for repeat shot flow)
|
||||
/// </summary>
|
||||
private void EnterAimingState()
|
||||
private IEnumerator SelectAirplane(Action<AirplaneAbilityType> onSelected)
|
||||
{
|
||||
ChangeState(AirplaneGameState.AirplaneSelection);
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] === AIRPLANE SELECTION ===");
|
||||
|
||||
// Blend to aiming camera if not already there
|
||||
// (New person flow: already on Aiming from flyby; Repeat shot flow: on NextPerson, needs blend)
|
||||
if (cameraManager != null && cameraManager.CurrentState != AirplaneCameraState.Aiming)
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Blending to Aiming camera...");
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.Aiming));
|
||||
}
|
||||
else if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[AirplaneGameManager] Already on Aiming camera, skipping blend");
|
||||
}
|
||||
|
||||
AirplaneAbilityType selectedType = AirplaneAbilityType.None;
|
||||
|
||||
// Show selection UI
|
||||
if (selectionUI != null)
|
||||
{
|
||||
selectionUI.Show();
|
||||
|
||||
// Wait for player to select and confirm
|
||||
yield return new WaitUntil(() => selectionUI.HasSelectedType);
|
||||
|
||||
selectedType = selectionUI.GetSelectedType();
|
||||
selectionUI.Hide();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] Selected airplane: {selectedType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning("[AirplaneGameManager] SelectionUI not assigned! Using default airplane type.");
|
||||
|
||||
// Fallback: use default type from settings
|
||||
if (_cachedSettings != null)
|
||||
{
|
||||
selectedType = _cachedSettings.DefaultAirplaneType;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedType = AirplaneAbilityType.Jet; // Ultimate fallback
|
||||
}
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] No selection UI, using default: {selectedType}");
|
||||
}
|
||||
}
|
||||
|
||||
onSelected?.Invoke(selectedType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase: Play person reaction animation
|
||||
/// </summary>
|
||||
private IEnumerator PlayPersonReaction(bool success)
|
||||
{
|
||||
if (personQueue == null || _currentPerson == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] Playing {_currentPerson.PersonName} reaction (success={success})");
|
||||
}
|
||||
|
||||
yield return StartCoroutine(personQueue.PlayPersonReaction(success));
|
||||
}
|
||||
/// <summary>
|
||||
/// Enter aiming state - player can aim and launch with selected airplane type
|
||||
/// Camera should already be on Aiming state from SelectAirplane phase
|
||||
/// </summary>
|
||||
private void EnterAimingState(AirplaneAbilityType selectedType)
|
||||
{
|
||||
ChangeState(AirplaneGameState.Aiming);
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Ready to aim and launch!");
|
||||
|
||||
// Switch to aiming camera
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.SwitchToState(AirplaneCameraState.Aiming);
|
||||
}
|
||||
|
||||
// Spawn airplane at slingshot with selected type
|
||||
if (launchController != null && _selectedAirplaneType != AirplaneAbilityType.None)
|
||||
if (launchController != null && selectedType != AirplaneAbilityType.None)
|
||||
{
|
||||
launchController.SetAirplaneType(_selectedAirplaneType);
|
||||
launchController.SetAirplaneType(selectedType);
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[AirplaneGameManager] Spawned airplane at slingshot: {_selectedAirplaneType}");
|
||||
Logging.Debug($"[AirplaneGameManager] Spawned airplane at slingshot: {selectedType}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +641,45 @@ namespace Minigames.Airplane.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flyby Sequence
|
||||
/// <summary>
|
||||
/// Execute cinematic flyby of the target location.
|
||||
/// Switches to TargetFlyby camera, waits for blend, lingers, then returns to Aiming camera.
|
||||
/// </summary>
|
||||
private IEnumerator ExecuteTargetFlyby()
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Starting target flyby sequence...");
|
||||
|
||||
if (cameraManager == null)
|
||||
{
|
||||
Logging.Warning("[AirplaneGameManager] Cannot execute flyby - camera manager not assigned!");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
Logging.Warning("[AirplaneGameManager] Cannot execute flyby - settings not available!");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 1. Blend to TargetFlyby camera and wait for blend to complete
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.TargetFlyby));
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Blend to target camera complete, lingering...");
|
||||
|
||||
// 2. Linger on target
|
||||
yield return new WaitForSeconds(_cachedSettings.TargetFlybyLingerDuration);
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Linger complete, blending back to aiming...");
|
||||
|
||||
// 3. Blend back to Aiming camera and wait for blend to complete
|
||||
yield return StartCoroutine(cameraManager.BlendToStateAsync(AirplaneCameraState.Aiming));
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AirplaneGameManager] Target flyby sequence complete!");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
@@ -700,11 +881,17 @@ namespace Minigames.Airplane.Core
|
||||
launchController.ClearActiveAirplane();
|
||||
}
|
||||
|
||||
// NOTE: Spawned objects cleanup moved to SetupNextPerson() to happen AFTER camera blend
|
||||
// This ensures camera shows the scene before cleanup and person reaction
|
||||
|
||||
// Move to next person
|
||||
StartCoroutine(SetupNextPerson());
|
||||
// Route to appropriate flow based on result
|
||||
if (success)
|
||||
{
|
||||
// Success: Go to person shuffle flow
|
||||
StartCoroutine(ExecutePersonShuffleFlow());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failure: Go to repeat shot flow
|
||||
StartCoroutine(ExecuteRepeatShotFlow());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -101,9 +101,9 @@ namespace Minigames.Airplane.Core
|
||||
// Plane tracking
|
||||
private Transform _planeTransform;
|
||||
private bool _isSpawningActive;
|
||||
private bool _hasPassedThreshold;
|
||||
|
||||
// Spawning positions (distance-based)
|
||||
private float _lastSpawnedX; // Tracks the furthest forward X position that has been pre-spawned
|
||||
private float _nextObjectSpawnX;
|
||||
private float _nextGroundSpawnX;
|
||||
|
||||
@@ -139,7 +139,6 @@ namespace Minigames.Airplane.Core
|
||||
|
||||
private void Update()
|
||||
{
|
||||
|
||||
if (!_isSpawningActive || _planeTransform == null) return;
|
||||
|
||||
float planeX = _planeTransform.position.x;
|
||||
@@ -150,52 +149,21 @@ namespace Minigames.Airplane.Core
|
||||
_furthestReachedX = planeX;
|
||||
}
|
||||
|
||||
// Check if target should be spawned (when plane gets within spawn distance)
|
||||
if (!_hasSpawnedTarget && _targetPrefabToSpawn != null)
|
||||
{
|
||||
float distanceToTarget = _targetSpawnPosition.x - planeX;
|
||||
if (distanceToTarget <= _settings.SpawnDistanceAhead)
|
||||
{
|
||||
SpawnTarget();
|
||||
_hasSpawnedTarget = true;
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Target spawned at distance {distanceToTarget:F2} from plane");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if plane has crossed threshold
|
||||
float threshold = dynamicSpawnThresholdMarker.position.x;
|
||||
|
||||
if (!_hasPassedThreshold && planeX >= threshold)
|
||||
{
|
||||
_hasPassedThreshold = true;
|
||||
InitializeDynamicSpawning();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Plane crossed threshold at X={planeX:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
// If past threshold, handle spawning (only if we're going further than before)
|
||||
if (_hasPassedThreshold)
|
||||
{
|
||||
// Check if plane has reached the point where we need to continue spawning beyond pre-spawned content
|
||||
// Only spawn new content if plane is beyond previous furthest point (for retries)
|
||||
bool shouldSpawnNewContent = !_isRetryAttempt || planeX > (_furthestReachedX - _settings.SpawnDistanceAhead);
|
||||
|
||||
if (shouldSpawnNewContent)
|
||||
{
|
||||
// Spawn objects when plane reaches spawn position
|
||||
if (planeX >= _nextObjectSpawnX)
|
||||
// Continue spawning objects when plane approaches the last spawned position
|
||||
float spawnTriggerX = _lastSpawnedX + _settings.SpawnDistanceAhead;
|
||||
if (planeX >= spawnTriggerX && planeX >= _nextObjectSpawnX)
|
||||
{
|
||||
SpawnRandomObject();
|
||||
ScheduleNextObjectSpawn(planeX);
|
||||
}
|
||||
|
||||
// Spawn ground tiles ahead of plane
|
||||
// Continue spawning ground tiles ahead of plane
|
||||
float groundSpawnTargetX = planeX + GetGroundSpawnAheadDistance();
|
||||
while (_nextGroundSpawnX < groundSpawnTargetX)
|
||||
{
|
||||
@@ -204,7 +172,6 @@ namespace Minigames.Airplane.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -221,7 +188,6 @@ namespace Minigames.Airplane.Core
|
||||
{
|
||||
_currentTargetKey = targetKey;
|
||||
_isSpawningActive = false;
|
||||
_hasPassedThreshold = false;
|
||||
_isRetryAttempt = isRetry;
|
||||
|
||||
// Only reset target and spawn state if NOT a retry
|
||||
@@ -266,6 +232,94 @@ namespace Minigames.Airplane.Core
|
||||
SetupTargetUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-spawn the entire level from threshold marker to target + buffer.
|
||||
/// Spawns range: (threshold_x, target_x + preSpawnBeyondTargetDistance)
|
||||
/// Call this after InitializeForGame() for new turns (not retries).
|
||||
/// </summary>
|
||||
public void PreSpawnLevelToTarget()
|
||||
{
|
||||
if (_targetPrefabToSpawn == null)
|
||||
{
|
||||
Logging.Error("[SpawnManager] Cannot pre-spawn - target prefab not initialized! Call InitializeForGame first.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hasSpawnedTarget)
|
||||
{
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[SpawnManager] Target already spawned, skipping pre-spawn (retry scenario)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (dynamicSpawnThresholdMarker == null)
|
||||
{
|
||||
Logging.Error("[SpawnManager] Cannot pre-spawn - dynamicSpawnThresholdMarker not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get pre-spawn range: from threshold to target + buffer
|
||||
float preSpawnStartX = dynamicSpawnThresholdMarker.position.x;
|
||||
float preSpawnEndX = _targetDistance + _settings.PreSpawnBeyondTargetDistance;
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Pre-spawning level from X={preSpawnStartX:F2} (threshold) to X={preSpawnEndX:F2} (target={_targetDistance:F2} + buffer={_settings.PreSpawnBeyondTargetDistance:F2})");
|
||||
}
|
||||
|
||||
// 1. Spawn ground tiles FIRST across entire range (so target can raycast)
|
||||
float currentGroundX = preSpawnStartX;
|
||||
while (currentGroundX <= preSpawnEndX)
|
||||
{
|
||||
SpawnGroundTileAt(currentGroundX);
|
||||
currentGroundX += _settings.GroundSpawnInterval;
|
||||
}
|
||||
|
||||
// Set next ground spawn position beyond pre-spawn range
|
||||
_nextGroundSpawnX = preSpawnEndX + _settings.GroundSpawnInterval;
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Ground tiles spawned, now spawning objects");
|
||||
}
|
||||
|
||||
// 2. Spawn objects across entire range (skipping near target)
|
||||
float currentObjectX = preSpawnStartX + Random.Range(_settings.ObjectSpawnMinDistance, _settings.ObjectSpawnMaxDistance);
|
||||
|
||||
while (currentObjectX <= preSpawnEndX)
|
||||
{
|
||||
// Spawn object at this position
|
||||
SpawnRandomObjectAt(currentObjectX);
|
||||
|
||||
// Move to next spawn position (forward)
|
||||
float spawnDistance = Random.Range(_settings.ObjectSpawnMinDistance, _settings.ObjectSpawnMaxDistance);
|
||||
currentObjectX += spawnDistance;
|
||||
}
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Objects spawned, now spawning target with raycast");
|
||||
}
|
||||
|
||||
// 3. Spawn the target LAST (after ground exists for proper raycasting)
|
||||
SpawnTarget();
|
||||
_hasSpawnedTarget = true;
|
||||
|
||||
// 4. Store the furthest forward pre-spawn position as _lastSpawnedX
|
||||
_lastSpawnedX = preSpawnEndX;
|
||||
|
||||
// 5. Schedule next object spawn beyond the pre-spawn range
|
||||
_nextObjectSpawnX = preSpawnEndX + Random.Range(_settings.ObjectSpawnMinDistance, _settings.ObjectSpawnMaxDistance);
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Pre-spawn complete! Last spawned X={_lastSpawnedX:F2}, next object at X={_nextObjectSpawnX:F2}");
|
||||
Logging.Debug($"[SpawnManager] Spawned {_positiveSpawnCount} positive and {_negativeSpawnCount} negative objects");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start tracking the airplane and enable spawning.
|
||||
/// </summary>
|
||||
@@ -380,7 +434,6 @@ namespace Minigames.Airplane.Core
|
||||
|
||||
// Reset all spawn state
|
||||
_hasSpawnedTarget = false;
|
||||
_hasPassedThreshold = false;
|
||||
_furthestReachedX = 0f;
|
||||
_positiveSpawnCount = 0;
|
||||
_negativeSpawnCount = 0;
|
||||
@@ -391,6 +444,33 @@ namespace Minigames.Airplane.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up level based on shot result.
|
||||
/// Success: Full cleanup (destroys all spawned objects).
|
||||
/// Failure: Reset for retry (keeps spawned objects, resets tracking).
|
||||
/// </summary>
|
||||
public void CleanupLevel(bool success)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
CleanupSpawnedObjects();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[SpawnManager] Level cleanup: SUCCESS - destroyed all objects");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetForRetry();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug("[SpawnManager] Level cleanup: FAILURE - kept objects for retry");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset tracking state for retry attempt (keeps spawned objects).
|
||||
/// Call this when player fails and will retry the same shot.
|
||||
@@ -414,6 +494,15 @@ namespace Minigames.Airplane.Core
|
||||
return (_targetSpawnPosition, _targetDistance, _targetIconSprite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the spawned target's transform for camera tracking.
|
||||
/// Returns null if target hasn't been spawned yet.
|
||||
/// </summary>
|
||||
public Transform GetSpawnedTargetTransform()
|
||||
{
|
||||
return _spawnedTarget != null ? _spawnedTarget.transform : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
@@ -494,6 +583,11 @@ namespace Minigames.Airplane.Core
|
||||
{
|
||||
Logging.Warning("[SpawnManager] Launch controller not assigned! Distance calculation will use world origin.");
|
||||
}
|
||||
|
||||
if (dynamicSpawnThresholdMarker == null)
|
||||
{
|
||||
Logging.Warning("[SpawnManager] Dynamic spawn threshold marker not assigned! Pre-spawn will fail.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -501,7 +595,8 @@ namespace Minigames.Airplane.Core
|
||||
#region Target Spawning
|
||||
|
||||
/// <summary>
|
||||
/// Spawn the target at the predetermined position.
|
||||
/// Spawn the target at the predetermined X position.
|
||||
/// Y coordinate is determined via raycast from high position to find ground.
|
||||
/// </summary>
|
||||
private void SpawnTarget()
|
||||
{
|
||||
@@ -511,7 +606,13 @@ namespace Minigames.Airplane.Core
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn target at initial position
|
||||
// Determine Y position via raycast from high position (Y=20)
|
||||
float targetY = DetermineTargetYPosition(_targetDistance);
|
||||
|
||||
// Update target spawn position with correct Y
|
||||
_targetSpawnPosition = new Vector3(_targetDistance, targetY, 0f);
|
||||
|
||||
// Spawn target at determined position
|
||||
_spawnedTarget = Instantiate(_currentTargetEntry.prefab, _targetSpawnPosition, Quaternion.identity);
|
||||
|
||||
if (spawnedObjectsParent != null)
|
||||
@@ -519,14 +620,14 @@ namespace Minigames.Airplane.Core
|
||||
_spawnedTarget.transform.SetParent(spawnedObjectsParent);
|
||||
}
|
||||
|
||||
// Position target using configured spawn mode
|
||||
PositionObject(_spawnedTarget, _targetSpawnPosition.x,
|
||||
// Position target using configured spawn mode (will refine Y if needed)
|
||||
PositionObject(_spawnedTarget, _targetDistance,
|
||||
_currentTargetEntry.spawnPositionMode,
|
||||
_currentTargetEntry.specifiedY,
|
||||
_currentTargetEntry.randomYMin,
|
||||
_currentTargetEntry.randomYMax);
|
||||
|
||||
// Update target spawn position to actual positioned location
|
||||
// Update target spawn position to actual final positioned location
|
||||
_targetSpawnPosition = _spawnedTarget.transform.position;
|
||||
|
||||
// Extract sprite for UI icon
|
||||
@@ -538,6 +639,41 @@ namespace Minigames.Airplane.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine target Y position by raycasting downward from high position (Y=20).
|
||||
/// Ensures target spawns touching the ground surface.
|
||||
/// </summary>
|
||||
private float DetermineTargetYPosition(float xPosition)
|
||||
{
|
||||
// Start raycast from high Y position (20 units up)
|
||||
Vector2 rayOrigin = new Vector2(xPosition, 20f);
|
||||
|
||||
// Raycast downward to find ground
|
||||
int layerMask = 1 << _settings.GroundLayer;
|
||||
RaycastHit2D hit = Physics2D.Raycast(
|
||||
rayOrigin,
|
||||
Vector2.down,
|
||||
_settings.MaxGroundRaycastDistance,
|
||||
layerMask
|
||||
);
|
||||
|
||||
if (hit.collider != null)
|
||||
{
|
||||
// Found ground - return ground Y position
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Target Y determined via raycast: ground at {hit.point.y:F2}");
|
||||
}
|
||||
return hit.point.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ground found - use default
|
||||
Logging.Warning($"[SpawnManager] No ground found for target at X={xPosition:F2} (raycast from Y=20 for {_settings.MaxGroundRaycastDistance} units), using default Y={_settings.DefaultObjectYOffset}");
|
||||
return _settings.DefaultObjectYOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract sprite from target prefab for UI display (without instantiation).
|
||||
/// Finds first SpriteRenderer in prefab or children.
|
||||
@@ -613,24 +749,6 @@ namespace Minigames.Airplane.Core
|
||||
|
||||
#region Dynamic Spawning
|
||||
|
||||
/// <summary>
|
||||
/// Initialize dynamic spawning when threshold is crossed.
|
||||
/// </summary>
|
||||
private void InitializeDynamicSpawning()
|
||||
{
|
||||
// Schedule first spawn trigger from current plane position
|
||||
// Actual spawning will happen at look-ahead distance
|
||||
if (_planeTransform != null)
|
||||
{
|
||||
ScheduleNextObjectSpawn(_planeTransform.position.x);
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Dynamic spawning initialized, first spawn trigger at planeX={_nextObjectSpawnX:F2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the distance ahead to spawn ground (2x object spawn distance).
|
||||
/// </summary>
|
||||
@@ -800,6 +918,105 @@ namespace Minigames.Airplane.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a ground tile at a specific X position (used for pre-spawn).
|
||||
/// </summary>
|
||||
private void SpawnGroundTileAt(float xPosition)
|
||||
{
|
||||
if (groundTilePrefabs == null || groundTilePrefabs.Length == 0) return;
|
||||
|
||||
// Pick random ground tile
|
||||
GameObject tilePrefab = groundTilePrefabs[Random.Range(0, groundTilePrefabs.Length)];
|
||||
|
||||
// Calculate spawn position using configured Y
|
||||
Vector3 spawnPosition = new Vector3(xPosition, groundSpawnY, 0f);
|
||||
|
||||
// Spawn tile
|
||||
GameObject spawnedTile = Instantiate(tilePrefab, spawnPosition, Quaternion.identity);
|
||||
|
||||
if (groundTilesParent != null)
|
||||
{
|
||||
spawnedTile.transform.SetParent(groundTilesParent);
|
||||
}
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Pre-spawned ground tile at ({xPosition:F2}, {groundSpawnY:F2})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a random positive or negative object at a specific X position (used for pre-spawn).
|
||||
/// </summary>
|
||||
private void SpawnRandomObjectAt(float xPosition)
|
||||
{
|
||||
// Check if spawn position is too close to target (avoid obscuring it)
|
||||
float distanceToTarget = Mathf.Abs(xPosition - _targetSpawnPosition.x);
|
||||
float targetClearanceZone = 10f; // Don't spawn within 10 units of target
|
||||
|
||||
if (distanceToTarget < targetClearanceZone)
|
||||
{
|
||||
// Too close to target, skip this spawn
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Skipped pre-spawn at X={xPosition:F2} (too close to target at X={_targetSpawnPosition.x:F2})");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if spawning positive or negative based on weighted ratio
|
||||
bool spawnPositive = ShouldSpawnPositive();
|
||||
|
||||
PrefabSpawnEntry entryToSpawn = null;
|
||||
|
||||
if (spawnPositive)
|
||||
{
|
||||
if (positiveObjectPrefabs != null && positiveObjectPrefabs.Length > 0)
|
||||
{
|
||||
entryToSpawn = positiveObjectPrefabs[Random.Range(0, positiveObjectPrefabs.Length)];
|
||||
_positiveSpawnCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (negativeObjectPrefabs != null && negativeObjectPrefabs.Length > 0)
|
||||
{
|
||||
entryToSpawn = negativeObjectPrefabs[Random.Range(0, negativeObjectPrefabs.Length)];
|
||||
_negativeSpawnCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (entryToSpawn == null || entryToSpawn.prefab == null) return;
|
||||
|
||||
// Spawn object at temporary position
|
||||
Vector3 tempPosition = new Vector3(xPosition, 0f, 0f);
|
||||
GameObject spawnedObject = Instantiate(entryToSpawn.prefab, tempPosition, Quaternion.identity);
|
||||
|
||||
if (spawnedObjectsParent != null)
|
||||
{
|
||||
spawnedObject.transform.SetParent(spawnedObjectsParent);
|
||||
}
|
||||
|
||||
// Position object using entry's spawn configuration
|
||||
PositionObject(spawnedObject, xPosition,
|
||||
entryToSpawn.spawnPositionMode,
|
||||
entryToSpawn.specifiedY,
|
||||
entryToSpawn.randomYMin,
|
||||
entryToSpawn.randomYMax);
|
||||
|
||||
// Initialize components that need post-spawn setup
|
||||
var initializable = spawnedObject.GetComponent<Interactive.ISpawnInitializable>();
|
||||
if (initializable != null)
|
||||
{
|
||||
initializable.Initialize();
|
||||
}
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[SpawnManager] Pre-spawned {(spawnPositive ? "positive" : "negative")} object at {spawnedObject.transform.position}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Positioning
|
||||
|
||||
@@ -212,16 +212,15 @@ namespace Minigames.Airplane.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle post-shot reaction for current person (who just shot).
|
||||
/// If successful: celebrate, remove from queue, shuffle remaining people.
|
||||
/// If failed: show disappointment, stay in queue.
|
||||
/// Awaitable - game flow waits for reactions and animations to complete.
|
||||
/// Play person reaction animation only (celebration or disappointment).
|
||||
/// Does not modify queue - use AdvanceQueue() separately for queue management.
|
||||
/// Awaitable - game flow waits for animation to complete.
|
||||
/// </summary>
|
||||
public IEnumerator HandlePostShotReaction(bool targetHit)
|
||||
public IEnumerator PlayPersonReaction(bool success)
|
||||
{
|
||||
if (peopleInQueue.Count == 0)
|
||||
{
|
||||
Logging.Warning("[PersonQueue] HandlePostShotReaction called but queue is empty!");
|
||||
Logging.Warning("[PersonQueue] PlayPersonReaction called but queue is empty!");
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -229,57 +228,59 @@ namespace Minigames.Airplane.Core
|
||||
Person currentPerson = peopleInQueue[0];
|
||||
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[PersonQueue] Post-shot reaction for {currentPerson.PersonName} (Hit: {targetHit})");
|
||||
Logging.Debug($"[PersonQueue] Playing reaction for {currentPerson.PersonName} (success={success})");
|
||||
|
||||
// Call person's reaction based on result
|
||||
if (targetHit)
|
||||
if (success)
|
||||
{
|
||||
// Success reaction
|
||||
yield return StartCoroutine(currentPerson.OnTargetHit());
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[PersonQueue] Success! Removing person and shuffling queue...");
|
||||
|
||||
// Remember the first person's position BEFORE removing them
|
||||
Vector3 firstPersonPosition = currentPerson.PersonTransform.position;
|
||||
|
||||
// Remove successful person from queue (they're no longer in peopleInQueue)
|
||||
RemoveCurrentPerson();
|
||||
|
||||
// Shuffle remaining people forward to fill the first person's spot
|
||||
yield return StartCoroutine(ShuffleToPosition(firstPersonPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failure reaction
|
||||
yield return StartCoroutine(currentPerson.OnTargetMissed());
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[PersonQueue] Failed - person stays in queue");
|
||||
// On failure, don't remove or shuffle, person gets another turn
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[PersonQueue] Post-shot reaction complete");
|
||||
if (showDebugLogs) Logging.Debug("[PersonQueue] Reaction complete");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Introduce the next person (at front of queue) for their turn.
|
||||
/// Awaitable - game flow waits for introduction to complete.
|
||||
/// Advance the queue after a shot result.
|
||||
/// If success: Remove current person and shuffle remaining people forward.
|
||||
/// If failure: Do nothing (person stays in queue for retry).
|
||||
/// Awaitable - game flow waits for removal and shuffle animations to complete.
|
||||
/// </summary>
|
||||
public IEnumerator IntroduceNextPerson()
|
||||
public IEnumerator AdvanceQueue(bool success)
|
||||
{
|
||||
if (peopleInQueue.Count == 0)
|
||||
{
|
||||
Logging.Warning("[PersonQueue] IntroduceNextPerson called but queue is empty!");
|
||||
Logging.Warning("[PersonQueue] AdvanceQueue called but queue is empty!");
|
||||
yield break;
|
||||
}
|
||||
|
||||
Person nextPerson = peopleInQueue[0];
|
||||
Person currentPerson = peopleInQueue[0];
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[PersonQueue] Introducing next person: {nextPerson.PersonName}");
|
||||
if (success)
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[PersonQueue] Advancing queue - removing {currentPerson.PersonName} and shuffling");
|
||||
|
||||
// Call person's hello sequence
|
||||
yield return StartCoroutine(nextPerson.OnHello());
|
||||
// Remember the first person's position BEFORE removing them
|
||||
Vector3 firstPersonPosition = currentPerson.PersonTransform.position;
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[PersonQueue] Introduction complete");
|
||||
// Remove successful person from queue
|
||||
RemoveCurrentPerson();
|
||||
|
||||
// Shuffle remaining people forward to fill the first person's spot
|
||||
yield return StartCoroutine(ShuffleToPosition(firstPersonPosition));
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[PersonQueue] Queue advance complete");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[PersonQueue] Failed - {currentPerson.PersonName} stays in queue for retry");
|
||||
// On failure, don't remove or shuffle, person gets another turn
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace Minigames.Airplane.Data
|
||||
Intro, // Intro sequence camera
|
||||
NextPerson, // Camera focusing on the next person
|
||||
Aiming, // Camera for aiming the airplane
|
||||
Flight // Camera following the airplane in flight
|
||||
Flight, // Camera following the airplane in flight
|
||||
TargetFlyby // Camera showing the target location (cinematic flyby)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,9 +75,15 @@ namespace Minigames.Airplane.Settings
|
||||
[Tooltip("Duration of result evaluation (seconds)")]
|
||||
[SerializeField] private float evaluationDuration = 1f;
|
||||
|
||||
[Tooltip("Duration to linger on target during flyby (seconds)")]
|
||||
[SerializeField] private float targetFlybyLingerDuration = 1.5f;
|
||||
|
||||
[Tooltip("Fallback blend time if CinemachineBrain blend detection unavailable (seconds)")]
|
||||
[SerializeField] private float targetFlybyCameraBlendTime = 1f;
|
||||
|
||||
[Header("Spawn System")]
|
||||
[Tooltip("Transform marker in scene where dynamic spawning begins (uses X position). If null, uses fallback distance.")]
|
||||
[SerializeField] private Transform dynamicSpawnThresholdMarker;
|
||||
[Tooltip("Distance beyond target to pre-spawn (extends the pre-spawn range)")]
|
||||
[SerializeField] private float preSpawnBeyondTargetDistance = 30f;
|
||||
|
||||
[Tooltip("Minimum random distance for target spawn")]
|
||||
[SerializeField] private float targetMinDistance = 30f;
|
||||
@@ -139,6 +145,9 @@ namespace Minigames.Airplane.Settings
|
||||
public float IntroDuration => introDuration;
|
||||
public float PersonIntroDuration => personIntroDuration;
|
||||
public float EvaluationDuration => evaluationDuration;
|
||||
public float TargetFlybyLingerDuration => targetFlybyLingerDuration;
|
||||
public float TargetFlybyCameraBlendTime => targetFlybyCameraBlendTime;
|
||||
public float PreSpawnBeyondTargetDistance => preSpawnBeyondTargetDistance;
|
||||
public float TargetMinDistance => targetMinDistance;
|
||||
public float TargetMaxDistance => targetMaxDistance;
|
||||
public float ObjectSpawnMinDistance => objectSpawnMinDistance;
|
||||
|
||||
Reference in New Issue
Block a user