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(); 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(); 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(); 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(); } else { postProcessVolume = ppVolumeObject.GetComponent(); if (postProcessVolume == null) { postProcessVolume = ppVolumeObject.AddComponent(); postProcessVolume.isGlobal = true; postProcessVolume.priority = 1; postProcessVolume.profile = ScriptableObject.CreateInstance(); } } } // 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.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.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.enabled.Override(true); chromaticAberration.intensity.Override(0.1f); } // Color Grading if (!postProcessVolume.profile.TryGetSettings(out colorGrading)) { colorGrading = postProcessVolume.profile.AddSettings(); 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(); 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() == null) { var ppLayer = mainCamera.gameObject.AddComponent(); 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(); if (composer == null) { composer = dollyCamera.AddCinemachineComponent(); } 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(); if (noise == null) { noise = dollyCamera.AddCinemachineComponent(); } noise.m_AmplitudeGain = cameraShakeIntensity; noise.m_FrequencyGain = 0.3f; // Add Composer component for proper framing var composer = dollyCamera.GetCinemachineComponent(); if (composer == null) { composer = dollyCamera.AddCinemachineComponent(); } 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(); 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(); 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(); if (playerAnimator == null) { playerAnimator = playerInstance.AddComponent(); } // 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(); if (footstepAudio == null) { footstepAudio = playerInstance.AddComponent(); } // Find walking sound clip from CharacterMovement component if available CharacterMovement characterMovement = playerInstance.GetComponent(); 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("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(); if (playerScript != null) { playerScript.enabled = false; } // Disable PlayerInput if it exists UnityEngine.InputSystem.PlayerInput playerInput = playerInstance.GetComponent(); 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().m_XDamping = 1f; dollyCamera.GetCinemachineComponent().m_YDamping = 1f; dollyCamera.GetCinemachineComponent().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; } } } }