Files
DeviantMobile-Rohan/Assets/Scripts/Managers/WaitingSceneManager.cs

1441 lines
53 KiB
C#

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Cinemachine;
using TMPro;
using UnityEngine.Rendering.PostProcessing;
public class WaitingSceneManager : MonoBehaviour
{
// References to scene objects
[Header("Scene References")]
public Transform spawnPoint;
public CinemachineDollyCart dollyCart;
public CinemachineVirtualCamera dollyCamera;
public Button skipButton;
public TextMeshProUGUI countdownText;
public GameObject loadingOverlay;
public PostProcessVolume postProcessVolume;
// Configuration
[Header("Configuration")]
public float entranceDuration = 10f;
public float walkSpeed = 1.5f;
public float dollySpeed = 2f;
[Range(0, 1)]
public float cameraShakeIntensity = 0.1f;
[Header("Enhanced Cinematics")]
public Light[] cinematicLights;
public GameObject[] atmosphericEffects;
public AudioClip dramaticMusic;
public AudioClip crowdCheering;
[Range(0, 1)]
public float musicVolume = 0.7f;
[Range(0, 1)]
public float crowdVolume = 0.5f;
[Range(0, 5)]
public float dramaticPauseTime = 1.5f;
// Private variables
private GameObject playerInstance;
private Animator playerAnimator;
private AudioSource footstepAudio;
private AudioSource musicAudioSource;
private AudioSource crowdAudioSource;
private bool isTransitioning = false;
private float remainingTime;
private Vector3 walkDirection = Vector3.forward;
private Vector3 walkTarget;
private bool isPlayerMoving = false;
private CinemachineBasicMultiChannelPerlin noise;
private DepthOfField depthOfField;
private Vignette vignette;
private ChromaticAberration chromaticAberration;
private ColorGrading colorGrading;
private Grain filmGrain;
// Camera shot state
private enum CameraShot { Establishing, CloseUp, MidShot, DramaticTilt, Final }
private CameraShot currentShot = CameraShot.Establishing;
private float shotTimer = 0f;
private float shotDuration = 2.5f;
private void Start()
{
// Initialize
remainingTime = entranceDuration;
if (countdownText != null)
{
countdownText.text = Mathf.Ceil(remainingTime).ToString();
}
if (skipButton != null)
{
skipButton.onClick.AddListener(SkipWaitingScene);
}
// Set up audio sources
SetupAudioSources();
// Set up post-processing if not already set
SetupPostProcessing();
// Set up and start the entrance sequence
SetupDollyCamera();
StartCoroutine(SpawnPlayerAndStartWalking());
StartCoroutine(CountdownTimer());
// Start camera shot sequence
StartCoroutine(CinematicShotSequence());
// Start dynamic lighting sequence
StartCoroutine(DynamicLightingSequence());
// Start audio synchronization with camera
StartCoroutine(SyncAudioWithCameraShots());
}
private void SetupAudioSources()
{
// Setup music audio source
if (dramaticMusic != null)
{
var musicObj = new GameObject("MusicSource");
musicObj.transform.SetParent(transform);
musicAudioSource = musicObj.AddComponent<AudioSource>();
musicAudioSource.clip = dramaticMusic;
musicAudioSource.volume = musicVolume;
musicAudioSource.loop = true;
musicAudioSource.playOnAwake = false;
musicAudioSource.spatialBlend = 0f; // 2D sound
musicAudioSource.Play();
}
// Setup crowd cheering audio source
if (crowdCheering != null)
{
var crowdObj = new GameObject("CrowdSource");
crowdObj.transform.SetParent(transform);
crowdAudioSource = crowdObj.AddComponent<AudioSource>();
crowdAudioSource.clip = crowdCheering;
crowdAudioSource.volume = 0f; // Start silent and fade in
crowdAudioSource.loop = true;
crowdAudioSource.playOnAwake = false;
crowdAudioSource.spatialBlend = 0f; // 2D sound
crowdAudioSource.Play();
// Start with very low volume and gradually increase
StartCoroutine(FadeCrowdAudio());
}
}
private void SetupPostProcessing()
{
// If post-process volume doesn't exist, create one
if (postProcessVolume == null)
{
var ppVolumeObject = GameObject.Find("Post Process Volume");
if (ppVolumeObject == null)
{
ppVolumeObject = new GameObject("Post Process Volume");
postProcessVolume = ppVolumeObject.AddComponent<PostProcessVolume>();
ppVolumeObject.layer = LayerMask.NameToLayer("PostProcessing");
// If post-processing layer doesn't exist, use Default
if (ppVolumeObject.layer == -1)
{
ppVolumeObject.layer = 0;
}
postProcessVolume.isGlobal = true;
postProcessVolume.priority = 1;
postProcessVolume.profile = ScriptableObject.CreateInstance<PostProcessProfile>();
}
else
{
postProcessVolume = ppVolumeObject.GetComponent<PostProcessVolume>();
if (postProcessVolume == null)
{
postProcessVolume = ppVolumeObject.AddComponent<PostProcessVolume>();
postProcessVolume.isGlobal = true;
postProcessVolume.priority = 1;
postProcessVolume.profile = ScriptableObject.CreateInstance<PostProcessProfile>();
}
}
}
// Setup effects if they don't exist
if (postProcessVolume.profile != null)
{
// Depth of Field
if (!postProcessVolume.profile.TryGetSettings(out depthOfField))
{
depthOfField = postProcessVolume.profile.AddSettings<DepthOfField>();
depthOfField.enabled.Override(true);
depthOfField.focusDistance.Override(4.5f);
depthOfField.aperture.Override(5.6f);
depthOfField.focalLength.Override(50f);
}
// Vignette
if (!postProcessVolume.profile.TryGetSettings(out vignette))
{
vignette = postProcessVolume.profile.AddSettings<Vignette>();
vignette.enabled.Override(true);
vignette.intensity.Override(0.25f);
vignette.smoothness.Override(0.4f);
}
// Chromatic Aberration
if (!postProcessVolume.profile.TryGetSettings(out chromaticAberration))
{
chromaticAberration = postProcessVolume.profile.AddSettings<ChromaticAberration>();
chromaticAberration.enabled.Override(true);
chromaticAberration.intensity.Override(0.1f);
}
// Color Grading
if (!postProcessVolume.profile.TryGetSettings(out colorGrading))
{
colorGrading = postProcessVolume.profile.AddSettings<ColorGrading>();
colorGrading.enabled.Override(true);
colorGrading.contrast.Override(10f);
colorGrading.saturation.Override(10f);
colorGrading.temperature.Override(5f);
}
// Film Grain
if (!postProcessVolume.profile.TryGetSettings(out filmGrain))
{
filmGrain = postProcessVolume.profile.AddSettings<Grain>();
filmGrain.enabled.Override(true);
filmGrain.intensity.Override(0.2f);
filmGrain.size.Override(1.5f);
}
}
// Make sure camera can see post-processing effects
var mainCamera = Camera.main;
if (mainCamera != null)
{
var layer = LayerMask.NameToLayer("PostProcessing");
if (layer != -1)
{
mainCamera.cullingMask |= (1 << layer);
}
// Add post-process layer component if missing
if (mainCamera.GetComponent<PostProcessLayer>() == null)
{
var ppLayer = mainCamera.gameObject.AddComponent<PostProcessLayer>();
ppLayer.volumeLayer = LayerMask.GetMask("Default");
ppLayer.antialiasingMode = PostProcessLayer.Antialiasing.FastApproximateAntialiasing;
}
}
Debug.Log("Post-processing setup complete");
}
private IEnumerator DynamicLightingSequence()
{
if (cinematicLights == null || cinematicLights.Length == 0)
{
yield break;
}
// Fade in lights at start
foreach (var light in cinematicLights)
{
if (light == null) continue;
float originalIntensity = light.intensity;
light.intensity = 0;
// Gradually increase intensity
float elapsed = 0;
float duration = 2.0f;
while (elapsed < duration)
{
light.intensity = Mathf.Lerp(0, originalIntensity, elapsed / duration);
elapsed += Time.deltaTime;
yield return null;
}
light.intensity = originalIntensity;
}
while (!isTransitioning)
{
// Create dramatic light pulses timed with camera changes
if (shotTimer > shotDuration - 0.5f && shotTimer < shotDuration - 0.3f)
{
// Brief flash of higher intensity before camera change
foreach (var light in cinematicLights)
{
if (light == null) continue;
float originalIntensity = light.intensity;
light.intensity = originalIntensity * 1.5f;
// Return to normal intensity
yield return new WaitForSeconds(0.2f);
light.intensity = originalIntensity;
}
}
yield return null;
}
// Fade out lights at end
if (isTransitioning)
{
foreach (var light in cinematicLights)
{
if (light == null) continue;
float originalIntensity = light.intensity;
// Gradually decrease intensity
float elapsed = 0;
float duration = 1.5f;
while (elapsed < duration)
{
light.intensity = Mathf.Lerp(originalIntensity, 0, elapsed / duration);
elapsed += Time.deltaTime;
yield return null;
}
light.intensity = 0;
}
}
}
private IEnumerator CinematicShotSequence()
{
// Wait for player to be initialized
while (playerInstance == null)
yield return null;
// Initial shot timing
shotTimer = 0f;
currentShot = CameraShot.Establishing;
// Apply initial shot settings
ApplyCameraShotSettings();
// Dramatic pause at start
if (dramaticPauseTime > 0)
{
Time.timeScale = 0.3f; // Slow motion effect
yield return new WaitForSecondsRealtime(dramaticPauseTime);
Time.timeScale = 1.0f; // Back to normal
// Initial camera shake
if (noise != null)
{
float originalAmplitude = noise.m_AmplitudeGain;
noise.m_AmplitudeGain = cameraShakeIntensity * 2;
yield return new WaitForSeconds(0.3f);
noise.m_AmplitudeGain = originalAmplitude;
}
}
while (!isTransitioning)
{
shotTimer += Time.deltaTime;
// Change shots based on timer
if (shotTimer > shotDuration)
{
shotTimer = 0f;
// Optional dramatic pause between shots
Time.timeScale = 0.5f; // Slight slow motion during transition
yield return new WaitForSecondsRealtime(0.2f);
Time.timeScale = 1.0f;
// Cycle through shot types with better pacing
switch (currentShot)
{
case CameraShot.Establishing:
currentShot = CameraShot.MidShot;
shotDuration = 3.0f; // Longer for mid shot
break;
case CameraShot.MidShot:
currentShot = CameraShot.CloseUp;
shotDuration = 2.0f; // Shorter for close-up
break;
case CameraShot.CloseUp:
currentShot = CameraShot.DramaticTilt;
shotDuration = 1.5f; // Even shorter for dramatic tilt
break;
case CameraShot.DramaticTilt:
currentShot = CameraShot.Final;
shotDuration = 3.5f; // Longer for final shot
break;
case CameraShot.Final:
// Stay in final shot
break;
}
// Apply new shot settings with transition effect
StartCoroutine(TransitionToNewShot());
}
yield return null;
}
}
private IEnumerator TransitionToNewShot()
{
// Quick camera shake during transition
if (noise != null)
{
float originalAmplitude = noise.m_AmplitudeGain;
noise.m_AmplitudeGain = cameraShakeIntensity * 2;
// Increase chromatic aberration briefly
if (chromaticAberration != null)
{
float originalIntensity = chromaticAberration.intensity.value;
chromaticAberration.intensity.Override(originalIntensity * 2f);
yield return new WaitForSeconds(0.2f);
chromaticAberration.intensity.Override(originalIntensity);
}
else
{
yield return new WaitForSeconds(0.2f);
}
noise.m_AmplitudeGain = originalAmplitude;
}
// Apply the actual camera settings
ApplyCameraShotSettings();
}
private void ApplyCameraShotSettings()
{
if (dollyCamera == null || playerInstance == null) return;
CinemachineComposer composer = dollyCamera.GetCinemachineComponent<CinemachineComposer>();
if (composer == null)
{
composer = dollyCamera.AddCinemachineComponent<CinemachineComposer>();
}
switch (currentShot)
{
case CameraShot.Establishing:
// Wide establishing shot from a slightly elevated angle
dollyCamera.m_Lens.FieldOfView = 42f;
composer.m_TrackedObjectOffset = new Vector3(0, 0.7f, 0);
composer.m_DeadZoneWidth = 0.1f;
composer.m_DeadZoneHeight = 0.1f;
dollyCamera.m_Lens.Dutch = 0f;
// Set composer framing for widescreen cinematic effect
composer.m_ScreenX = 0.5f;
composer.m_ScreenY = 0.45f; // Slightly lower to show more headroom
composer.m_SoftZoneWidth = 0.8f;
composer.m_SoftZoneHeight = 0.7f;
// Subtle depth of field
if (depthOfField != null)
{
depthOfField.focusDistance.Override(10f);
depthOfField.aperture.Override(5.6f);
depthOfField.focalLength.Override(50f);
}
// Minimal noise for establishing shot
if (noise != null)
{
noise.m_AmplitudeGain = 0.1f;
noise.m_FrequencyGain = 0.1f;
}
// Subtle post-processing
if (vignette != null)
{
vignette.intensity.Override(0.2f);
}
if (chromaticAberration != null)
{
chromaticAberration.intensity.Override(0.1f);
}
if (colorGrading != null)
{
colorGrading.contrast.Override(5f);
colorGrading.saturation.Override(5f);
}
break;
case CameraShot.MidShot:
// Mid shot - shows upper body
dollyCamera.m_Lens.FieldOfView = 32f;
composer.m_TrackedObjectOffset = new Vector3(0, 1.0f, 0);
composer.m_DeadZoneWidth = 0.15f;
composer.m_DeadZoneHeight = 0.15f;
dollyCamera.m_Lens.Dutch = 2f; // Slight tilt
// Rule of thirds framing
composer.m_ScreenX = 0.33f; // Position player at 1/3 of screen
composer.m_ScreenY = 0.5f;
composer.m_SoftZoneWidth = 0.6f;
composer.m_SoftZoneHeight = 0.6f;
// Mid-shot depth of field
if (depthOfField != null)
{
depthOfField.focusDistance.Override(6f);
depthOfField.aperture.Override(4.0f);
depthOfField.focalLength.Override(75f);
}
if (noise != null)
{
noise.m_AmplitudeGain = 0.2f;
noise.m_FrequencyGain = 0.2f;
}
// Medium vignette
if (vignette != null)
{
vignette.intensity.Override(0.3f);
}
if (chromaticAberration != null)
{
chromaticAberration.intensity.Override(0.15f);
}
if (colorGrading != null)
{
colorGrading.contrast.Override(8f);
colorGrading.saturation.Override(8f);
colorGrading.temperature.Override(3f); // Slightly warmer
}
break;
case CameraShot.CloseUp:
// Close-up on character's face/upper body
dollyCamera.m_Lens.FieldOfView = 28f;
composer.m_TrackedObjectOffset = new Vector3(0, 1.4f, 0); // Higher to frame face
composer.m_DeadZoneWidth = 0.05f;
composer.m_DeadZoneHeight = 0.05f;
dollyCamera.m_Lens.Dutch = -3f; // Opposite tilt for visual interest
// Dramatic close-up framing
composer.m_ScreenX = 0.55f; // Slightly off-center
composer.m_ScreenY = 0.45f;
composer.m_SoftZoneWidth = 0.4f; // Tighter framing
composer.m_SoftZoneHeight = 0.4f;
// Shallow depth of field for close-up
if (depthOfField != null)
{
depthOfField.focusDistance.Override(3f);
depthOfField.aperture.Override(2.8f); // Very shallow DOF
depthOfField.focalLength.Override(100f);
}
if (noise != null)
{
noise.m_AmplitudeGain = 0.3f; // More shake for tension
noise.m_FrequencyGain = 0.3f;
}
// Stronger vignette for close-up
if (vignette != null)
{
vignette.intensity.Override(0.35f);
}
if (chromaticAberration != null)
{
chromaticAberration.intensity.Override(0.2f);
}
if (colorGrading != null)
{
colorGrading.contrast.Override(12f); // Higher contrast
colorGrading.saturation.Override(10f);
colorGrading.temperature.Override(5f); // Even warmer
}
break;
case CameraShot.DramaticTilt:
// Low angle dramatic shot
dollyCamera.m_Lens.FieldOfView = 36f;
composer.m_TrackedObjectOffset = new Vector3(0, 0.3f, 0); // Lower offset to look up
composer.m_DeadZoneWidth = 0.2f;
composer.m_DeadZoneHeight = 0.2f;
dollyCamera.m_Lens.Dutch = 15f; // Extreme Dutch angle
// Low angle hero shot framing
composer.m_ScreenX = 0.5f; // Center framing
composer.m_ScreenY = 0.4f; // Lower in frame to emphasize height
composer.m_SoftZoneWidth = 0.5f;
composer.m_SoftZoneHeight = 0.5f;
// Dramatic depth of field
if (depthOfField != null)
{
depthOfField.focusDistance.Override(4f);
depthOfField.aperture.Override(3.5f);
depthOfField.focalLength.Override(85f);
}
if (noise != null)
{
noise.m_AmplitudeGain = 0.4f; // Most shake for dramatic shot
noise.m_FrequencyGain = 0.4f;
}
// Heavy vignette for dramatic shot
if (vignette != null)
{
vignette.intensity.Override(0.45f);
vignette.smoothness.Override(0.3f);
}
if (chromaticAberration != null)
{
chromaticAberration.intensity.Override(0.3f); // Strong aberration
}
if (colorGrading != null)
{
colorGrading.contrast.Override(15f); // Highest contrast
colorGrading.saturation.Override(15f); // Highest saturation
colorGrading.temperature.Override(-5f); // Switch to cool tone for drama
}
if (filmGrain != null)
{
filmGrain.intensity.Override(0.4f); // Add more grain for grit
}
break;
case CameraShot.Final:
// Final hero shot - wide, powerful framing
dollyCamera.m_Lens.FieldOfView = 38f;
composer.m_TrackedObjectOffset = new Vector3(0, 0.8f, 0);
composer.m_DeadZoneWidth = 0.1f;
composer.m_DeadZoneHeight = 0.1f;
dollyCamera.m_Lens.Dutch = 0f; // Level out for final shot
// Epic final framing
composer.m_ScreenX = 0.5f; // Center
composer.m_ScreenY = 0.5f; // Center
composer.m_SoftZoneWidth = 0.6f;
composer.m_SoftZoneHeight = 0.6f;
// Clear depth of field for final shot
if (depthOfField != null)
{
depthOfField.focusDistance.Override(7f);
depthOfField.aperture.Override(5.6f); // Deeper DOF
depthOfField.focalLength.Override(50f);
}
if (noise != null)
{
noise.m_AmplitudeGain = 0.15f; // Settle down for final pose
noise.m_FrequencyGain = 0.15f;
}
// Balanced post-processing for final shot
if (vignette != null)
{
vignette.intensity.Override(0.25f);
}
if (chromaticAberration != null)
{
chromaticAberration.intensity.Override(0.12f);
}
if (colorGrading != null)
{
colorGrading.contrast.Override(10f);
colorGrading.saturation.Override(12f);
colorGrading.temperature.Override(2f); // Slightly warm
}
if (filmGrain != null)
{
filmGrain.intensity.Override(0.2f);
}
break;
}
Debug.Log("Applied camera shot: " + currentShot);
}
private void Update()
{
// Move the player if walking animation is active
if (isPlayerMoving && playerInstance != null && !isTransitioning)
{
// Calculate the distance to move this frame
float step = walkSpeed * Time.deltaTime;
// Move the player
playerInstance.transform.position = Vector3.MoveTowards(
playerInstance.transform.position,
walkTarget,
step
);
// Check if we've reached the target
if (Vector3.Distance(playerInstance.transform.position, walkTarget) < 0.001f)
{
// Stop moving if we've reached the walk target
isPlayerMoving = false;
// Slow down to idle animation
if (playerAnimator != null)
{
StartCoroutine(SlowToIdle());
}
}
}
}
private IEnumerator SlowToIdle()
{
// Gradually slow down the animation from walking to idle
float currentSpeed = 0.2f;
float targetSpeed = 0f;
float transitionTime = 1.0f;
float elapsedTime = 0f;
while (elapsedTime < transitionTime)
{
elapsedTime += Time.deltaTime;
float t = elapsedTime / transitionTime;
float newSpeed = Mathf.Lerp(currentSpeed, targetSpeed, t);
if (playerAnimator != null)
{
playerAnimator.SetFloat("Speed", newSpeed);
}
yield return null;
}
// Ensure we end at exactly idle
if (playerAnimator != null)
{
playerAnimator.SetFloat("Speed", 0f);
}
// Maybe also play an idle pose animation here
if (playerAnimator != null && playerAnimator.parameters.Length > 0)
{
// Look for an "IdlePose" trigger parameter
for (int i = 0; i < playerAnimator.parameters.Length; i++)
{
if (playerAnimator.parameters[i].name == "IdlePose" &&
playerAnimator.parameters[i].type == AnimatorControllerParameterType.Trigger)
{
playerAnimator.SetTrigger("IdlePose");
break;
}
}
}
}
private void SetupDollyCamera()
{
if (dollyCart != null)
{
// Set the dolly cart speed to match the player walk speed
dollyCart.m_Speed = 0f; // Initially stopped - we'll control it manually
// We'll manually move the dolly cart to follow the player
StartCoroutine(ControlDollyMovement());
}
// Set camera to face player with cinematic settings
if (dollyCamera != null)
{
// Set camera to look at player's face level with cinematic settings
dollyCamera.m_Lens.FieldOfView = 35f; // Narrower FOV for more cinematic look
// Add noise for subtle camera shake (handheld effect)
noise = dollyCamera.GetCinemachineComponent<CinemachineBasicMultiChannelPerlin>();
if (noise == null)
{
noise = dollyCamera.AddCinemachineComponent<CinemachineBasicMultiChannelPerlin>();
}
noise.m_AmplitudeGain = cameraShakeIntensity;
noise.m_FrequencyGain = 0.3f;
// Add Composer component for proper framing
var composer = dollyCamera.GetCinemachineComponent<CinemachineComposer>();
if (composer == null)
{
composer = dollyCamera.AddCinemachineComponent<CinemachineComposer>();
}
composer.m_TrackedObjectOffset = new Vector3(0, 0.5f, 0); // Offset for proper framing
composer.m_LookaheadTime = 0.2f; // Slight lookahead
composer.m_DeadZoneWidth = 0.1f; // Small dead zone
composer.m_DeadZoneHeight = 0.1f;
composer.m_SoftZoneWidth = 0.6f; // Soft zone for smoother transitions
composer.m_SoftZoneHeight = 0.6f;
// Add a slight Dutch angle for more dramatic effect
dollyCamera.m_Lens.Dutch = 2f; // Slight tilt
}
}
private IEnumerator ControlDollyMovement()
{
if (dollyCart == null || playerInstance == null)
yield break;
// Wait until player is initialized
while (playerInstance == null)
yield return null;
// Get the dolly path
var dollyPath = dollyCart.m_Path as CinemachineSmoothPath;
if (dollyPath == null)
yield break;
// Reset the dolly cart to the beginning of the path
dollyCart.m_Position = 0;
// Position the camera in front of the player
PositionCameraInFrontOfPlayer(dollyPath);
// Wait a frame to ensure everything is initialized
yield return null;
float initialPosition = dollyCart.m_Position;
Vector3 initialPlayerPos = playerInstance.transform.position;
// While the player is moving
while (isPlayerMoving && !isTransitioning)
{
// Calculate how far the player has moved from start
float playerProgress = Vector3.Distance(initialPlayerPos, playerInstance.transform.position);
// Convert player progress to path position (full path length)
float pathLength = dollyPath.PathLength;
float targetPosition = initialPosition + (playerProgress * (pathLength / 15f)); // 15 is the walk distance
// Update the dolly cart position to match player progress with cinematic easing
dollyCart.m_Position = Mathf.SmoothStep(
dollyCart.m_Position,
targetPosition,
Time.deltaTime * 2.5f
);
yield return null;
}
}
private void PositionCameraInFrontOfPlayer(CinemachineSmoothPath path)
{
if (path == null || playerInstance == null) return;
// Create a new path that starts in front of the player and moves backward
path.m_Waypoints = new CinemachineSmoothPath.Waypoint[5]; // Added one more point for smoother path
// Get the player's position and forward direction (which is now facing toward camera)
Vector3 playerPos = playerInstance.transform.position;
Vector3 playerForward = playerInstance.transform.forward; // This is now pointing toward camera
// Since player is facing toward camera now, we position camera in front of them along that direction
Vector3 frontPosition = playerPos + (playerForward * 5f); // 5 units in front in direction they face
frontPosition.y = playerPos.y + 1.7f; // Slightly above eye level
// Create a path that camera follows as player walks toward it
path.m_Waypoints[0] = new CinemachineSmoothPath.Waypoint {
position = frontPosition,
roll = 0
};
// Second point - slightly farther ahead of player
path.m_Waypoints[1] = new CinemachineSmoothPath.Waypoint {
position = frontPosition + (playerForward * 3f) + new Vector3(0.3f, 0.2f, 0),
roll = 2 // Slight roll
};
// Third point - even farther ahead of player
path.m_Waypoints[2] = new CinemachineSmoothPath.Waypoint {
position = frontPosition + (playerForward * 6f) + new Vector3(-0.3f, 0.5f, 0),
roll = -2 // Opposite roll
};
// Fourth point - farther ahead, wider angle
path.m_Waypoints[3] = new CinemachineSmoothPath.Waypoint {
position = frontPosition + (playerForward * 9f) + new Vector3(0.5f, 0.8f, 0),
roll = 3 // Roll again
};
// Fifth point - final camera position
path.m_Waypoints[4] = new CinemachineSmoothPath.Waypoint {
position = frontPosition + (playerForward * 12f) + new Vector3(0, 1f, 0),
roll = 0 // Level out at the end
};
Debug.Log("Camera path positioned in front of player (player facing camera): " + playerPos + " -> " + frontPosition);
}
private IEnumerator SpawnPlayerAndStartWalking()
{
// Wait one frame to ensure SelectionOptions is initialized
yield return null;
// Get the selected player info from SelectionOptions
SpawnSelectedPlayer();
if (playerInstance != null)
{
// Make the player face toward the camera (opposite of default forward)
walkDirection = Vector3.forward * -1; // Reversed - walk toward the beginning (and camera)
playerInstance.transform.rotation = Quaternion.LookRotation(walkDirection);
// Set the walk target - a point ahead of where the player spawned, toward the camera
walkTarget = spawnPoint.position + (walkDirection * 15f);
// Configure the player for walking animation
ConfigurePlayerForEntrance();
// Make the dolly camera follow the player if needed
if (dollyCamera != null && playerInstance != null)
{
SetDollyCameraTarget();
}
// Create ground check setup to avoid GroundCheck errors
SetupGroundCheck();
// Start walking
isPlayerMoving = true;
}
else
{
Debug.LogError("Failed to spawn player in WaitingScene");
SkipWaitingScene(); // Skip if something went wrong
}
}
private void SetupGroundCheck()
{
if (playerInstance == null) return;
// Check if player has a GroundCheck component and set it up
GroundCheck groundCheck = playerInstance.GetComponent<GroundCheck>();
if (groundCheck != null)
{
// We need to properly create the floor_limit as a child of the player
// First check if floor_limit already exists as a child
Transform existingFloorLimit = playerInstance.transform.Find("floor_limit");
if (existingFloorLimit == null)
{
// Create a floor limit game object as a child of the player
GameObject floorLimit = new GameObject("floor_limit");
floorLimit.transform.SetParent(playerInstance.transform);
floorLimit.transform.localPosition = new Vector3(0, 0.1f, 0);
existingFloorLimit = floorLimit.transform;
Debug.Log("Created new floor_limit for player: " + playerInstance.name);
}
// Assign the floor limit to the ground check
groundCheck.groundCheckTransform = existingFloorLimit;
// Set ground layer mask
groundCheck.groundLayer = LayerMask.GetMask("Ground");
if (groundCheck.groundLayer == 0)
{
// Fallback to default layer if Ground layer doesn't exist
groundCheck.groundLayer = 1; // Default layer
Debug.LogWarning("Ground layer not found. Add a 'Ground' layer in your project settings for best results.");
}
groundCheck.groundDistance = 0.2f;
groundCheck.isGrounded = true;
// Force the Start method to run to initialize properly
groundCheck.enabled = false;
groundCheck.enabled = true;
Debug.Log("GroundCheck setup complete. groundCheckTransform: " +
(groundCheck.groundCheckTransform != null ? groundCheck.groundCheckTransform.name : "null"));
}
else
{
Debug.LogWarning("No GroundCheck component found on player: " + playerInstance.name);
}
// Similar check for FallCheck
FallCheck fallCheck = playerInstance.GetComponent<FallCheck>();
if (fallCheck != null)
{
// Safe initialization for FallCheck if needed
fallCheck.enabled = false; // Disable during the waiting scene
}
}
private void SpawnSelectedPlayer()
{
if (SelectionOptions.Instance == null)
{
Debug.LogError("SelectionOptions.Instance is null");
return;
}
// Check if we're in AI mode or Player mode
if (SelectionOptions.Instance.isAISelected)
{
SpawnAIPlayer();
}
else
{
SpawnLocalPlayer();
}
// Rotate player to face the walk direction
if (playerInstance != null)
{
// Set player's forward direction to match the walk direction
playerInstance.transform.rotation = Quaternion.LookRotation(walkDirection);
}
}
private void SpawnAIPlayer()
{
if (SelectionOptions.Instance.selectedPlayersAI == null || SelectionOptions.Instance.selectedPlayersAI.Count == 0)
{
Debug.LogError("No AI players selected");
return;
}
// Find the player character (not the AI opponent)
foreach (var kvp in SelectionOptions.Instance.selectedPlayersAI)
{
if (kvp.Value == "PLAYER")
{
string playerName = kvp.Key;
GameObject prefab = CharacterPrefabManager.Instance.GetCharacterPrefab(playerName, false);
if (prefab != null)
{
playerInstance = Instantiate(prefab, spawnPoint.position, spawnPoint.rotation);
playerInstance.tag = "Player";
break;
}
}
}
}
private void SpawnLocalPlayer()
{
if (SelectionOptions.Instance.selectedPlayerDevice == null || SelectionOptions.Instance.selectedPlayerDevice.Count == 0)
{
Debug.LogError("No players selected");
return;
}
// For local multiplayer, just spawn the first player for the entrance
foreach (var kvp in SelectionOptions.Instance.selectedPlayerDevice)
{
string playerName = kvp.Key;
GameObject prefab = CharacterPrefabManager.Instance.GetCharacterPrefab(playerName, false);
if (prefab != null)
{
playerInstance = Instantiate(prefab, spawnPoint.position, spawnPoint.rotation);
playerInstance.tag = "Player";
break; // Just spawn the first player for the entrance
}
}
}
private void ConfigurePlayerForEntrance()
{
if (playerInstance == null) return;
// Get or add required components
playerAnimator = playerInstance.GetComponent<Animator>();
if (playerAnimator == null)
{
playerAnimator = playerInstance.AddComponent<Animator>();
}
// Play walking animation
if (playerAnimator != null)
{
playerAnimator.SetFloat("Speed", 0.2f); // Set to walking speed in the blend tree
}
// Set up walking sound
footstepAudio = playerInstance.GetComponent<AudioSource>();
if (footstepAudio == null)
{
footstepAudio = playerInstance.AddComponent<AudioSource>();
}
// Find walking sound clip from CharacterMovement component if available
CharacterMovement characterMovement = playerInstance.GetComponent<CharacterMovement>();
if (characterMovement != null && characterMovement.walkSoundEffect != null)
{
footstepAudio.clip = characterMovement.walkSoundEffect;
footstepAudio.loop = true;
footstepAudio.volume = 0.7f;
footstepAudio.Play();
}
else
{
// Try to find walking sound from resources as fallback
AudioClip walkSound = Resources.Load<AudioClip>("Sounds/FootstepWalk");
if (walkSound != null)
{
footstepAudio.clip = walkSound;
footstepAudio.loop = true;
footstepAudio.volume = 0.7f;
footstepAudio.Play();
}
}
// Disable player input/controls during entrance
DisablePlayerControls();
}
private void DisablePlayerControls()
{
// Disable any scripts that would allow player control
if (playerInstance == null) return;
// Disable PlayerScript if it exists
PlayerScript playerScript = playerInstance.GetComponent<PlayerScript>();
if (playerScript != null)
{
playerScript.enabled = false;
}
// Disable PlayerInput if it exists
UnityEngine.InputSystem.PlayerInput playerInput = playerInstance.GetComponent<UnityEngine.InputSystem.PlayerInput>();
if (playerInput != null)
{
playerInput.enabled = false;
}
}
private void SetDollyCameraTarget()
{
if (dollyCamera == null || playerInstance == null) return;
// Find a good target on the player (head bone or similar)
Transform targetBone = FindCharacterHead(playerInstance);
if (targetBone != null)
{
dollyCamera.Follow = targetBone;
dollyCamera.LookAt = targetBone;
}
else
{
// Fallback to the player's transform if no head bone is found
dollyCamera.Follow = playerInstance.transform;
dollyCamera.LookAt = playerInstance.transform;
}
// Adjust camera to look at player's face
dollyCamera.m_Lens.Dutch = 0; // No tilt
dollyCamera.GetCinemachineComponent<CinemachineTransposer>().m_XDamping = 1f;
dollyCamera.GetCinemachineComponent<CinemachineTransposer>().m_YDamping = 1f;
dollyCamera.GetCinemachineComponent<CinemachineTransposer>().m_ZDamping = 1f;
}
private Transform FindCharacterHead(GameObject character)
{
if (character == null) return null;
string characterName = character.name;
Transform headTransform = null;
try
{
// This logic is copied from SelectionOptions.FindCharacterHead
switch (characterName)
{
case var _ when characterName.Contains("Cheetah"):
headTransform = character.transform.GetChild(9)?.GetChild(6)?.GetChild(1);
break;
case var _ when characterName.Contains("Rabbit"):
case var _ when characterName.Contains("Eagle"):
case var _ when characterName.Contains("Hyena"):
case var _ when characterName.Contains("HM"):
case var _ when characterName.Contains("Cat"):
case var _ when characterName.Contains("CAT N"):
headTransform = character.transform.GetChild(7)?.GetChild(6)?.GetChild(1);
break;
}
}
catch (System.Exception e)
{
Debug.LogWarning($"Error finding head for character {characterName}: {e.Message}");
}
return headTransform ?? character.transform;
}
private IEnumerator CountdownTimer()
{
while (remainingTime > 0 && !isTransitioning)
{
remainingTime -= Time.deltaTime;
if (countdownText != null)
{
countdownText.text = Mathf.Ceil(remainingTime).ToString();
}
yield return null;
}
if (!isTransitioning)
{
LoadGameScene();
}
}
public void SkipWaitingScene()
{
if (!isTransitioning)
{
isTransitioning = true;
LoadGameScene();
}
}
private void LoadGameScene()
{
isTransitioning = true;
StartCoroutine(TransitionToGameScene());
}
private IEnumerator TransitionToGameScene()
{
// Show loading UI
if (loadingOverlay != null)
{
loadingOverlay.SetActive(true);
}
else if (GameManager.Instance != null)
{
GameManager.Instance.ShowLoadingUI();
}
// Give the loading UI time to appear
yield return new WaitForSeconds(0.5f);
// Load Game scene (Editor: built-in, Android: custom AssetBundle)
// SceneLoader now handles everything automatically!
var asyncLoad = SceneLoader.LoadSceneAsyncOnce("Game", LoadSceneMode.Single, allowSceneActivation: true);
if (asyncLoad == null)
{
// Another load already in progress; wait for it
while (SceneLoader.IsLoading)
yield return null;
yield break;
}
// Wait for the scene to finish loading (custom loader handles activation internally)
while (!asyncLoad.isDone)
{
yield return null;
}
}
private IEnumerator FadeCrowdAudio()
{
// Wait until player is initialized
while (playerInstance == null)
yield return null;
if (crowdAudioSource == null)
yield break;
// Start with low volume
crowdAudioSource.volume = 0.05f;
// Gradually increase the crowd volume over time
float elapsed = 0f;
float fadeDuration = entranceDuration * 0.6f; // Fade in over 60% of the entrance time
while (elapsed < fadeDuration && !isTransitioning)
{
elapsed += Time.deltaTime;
float t = elapsed / fadeDuration;
// Ease-in function for smoother volume increase
float easedT = 1f - Mathf.Cos(t * Mathf.PI * 0.5f);
crowdAudioSource.volume = Mathf.Lerp(0.05f, crowdVolume, easedT);
yield return null;
}
// Ensure we reach the target volume
if (!isTransitioning)
{
crowdAudioSource.volume = crowdVolume;
}
}
private IEnumerator SyncAudioWithCameraShots()
{
// Wait for player to be initialized
while (playerInstance == null)
yield return null;
if (musicAudioSource == null)
yield break;
// Initial volume
musicAudioSource.volume = musicVolume * 0.5f;
while (!isTransitioning)
{
// Adjust music based on current camera shot
switch (currentShot)
{
case CameraShot.Establishing:
// Softer, establishing music
musicAudioSource.volume = Mathf.Lerp(musicAudioSource.volume, musicVolume * 0.6f, Time.deltaTime * 2f);
musicAudioSource.pitch = Mathf.Lerp(musicAudioSource.pitch, 0.95f, Time.deltaTime * 2f);
// Quieter crowd for establishing shot
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(crowdAudioSource.volume, crowdVolume * 0.3f, Time.deltaTime * 2f);
}
break;
case CameraShot.MidShot:
// Medium intensity
musicAudioSource.volume = Mathf.Lerp(musicAudioSource.volume, musicVolume * 0.7f, Time.deltaTime * 2f);
musicAudioSource.pitch = Mathf.Lerp(musicAudioSource.pitch, 1.0f, Time.deltaTime * 2f);
// Medium crowd
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(crowdAudioSource.volume, crowdVolume * 0.6f, Time.deltaTime * 2f);
}
break;
case CameraShot.CloseUp:
// More intense for close-up
musicAudioSource.volume = Mathf.Lerp(musicAudioSource.volume, musicVolume * 0.8f, Time.deltaTime * 2f);
musicAudioSource.pitch = Mathf.Lerp(musicAudioSource.pitch, 1.05f, Time.deltaTime * 2f);
// Growing crowd excitement
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(crowdAudioSource.volume, crowdVolume * 0.8f, Time.deltaTime * 2f);
}
break;
case CameraShot.DramaticTilt:
// Peak intensity for dramatic tilt
musicAudioSource.volume = Mathf.Lerp(musicAudioSource.volume, musicVolume * 1.0f, Time.deltaTime * 2f);
musicAudioSource.pitch = Mathf.Lerp(musicAudioSource.pitch, 1.1f, Time.deltaTime * 2f);
// Full crowd
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(crowdAudioSource.volume, crowdVolume * 1.0f, Time.deltaTime * 3f);
}
break;
case CameraShot.Final:
// Resolving, heroic feel for final shot
musicAudioSource.volume = Mathf.Lerp(musicAudioSource.volume, musicVolume * 0.9f, Time.deltaTime * 2f);
musicAudioSource.pitch = Mathf.Lerp(musicAudioSource.pitch, 1.0f, Time.deltaTime * 2f);
// Maintain full crowd volume
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(crowdAudioSource.volume, crowdVolume * 1.0f, Time.deltaTime * 2f);
}
break;
}
// Add audio effects during shot transitions
if (shotTimer > shotDuration - 0.5f && shotTimer < shotDuration - 0.3f)
{
// Brief volume swell before camera change
musicAudioSource.volume = Mathf.Lerp(musicAudioSource.volume, musicVolume * 1.1f, Time.deltaTime * 5f);
// Brief crowd swell at transition
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(crowdAudioSource.volume, crowdVolume * 1.2f, Time.deltaTime * 5f);
}
}
// If footstep audio exists, adjust it based on player movement
if (footstepAudio != null && playerAnimator != null)
{
// Sync footstep volume with animation speed
float animSpeed = playerAnimator.GetFloat("Speed");
if (animSpeed > 0.05f && !footstepAudio.isPlaying)
{
footstepAudio.Play();
}
else if (animSpeed < 0.05f && footstepAudio.isPlaying)
{
footstepAudio.Pause();
}
// Adjust footstep volume based on animation speed
footstepAudio.volume = animSpeed * 0.7f;
}
yield return null;
}
// Fade out music when transitioning
if (isTransitioning)
{
float duration = 1.5f;
float startMusicVolume = musicAudioSource.volume;
float startCrowdVolume = crowdAudioSource != null ? crowdAudioSource.volume : 0f;
float elapsed = 0f;
while (elapsed < duration)
{
musicAudioSource.volume = Mathf.Lerp(startMusicVolume, 0f, elapsed / duration);
if (crowdAudioSource != null)
{
crowdAudioSource.volume = Mathf.Lerp(startCrowdVolume, 0f, elapsed / duration);
}
elapsed += Time.deltaTime;
yield return null;
}
musicAudioSource.volume = 0f;
if (crowdAudioSource != null)
{
crowdAudioSource.volume = 0f;
}
}
}
}