1441 lines
53 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |