using TMPro; using UnityEngine; using UnityEngine.UI; using Unity.Netcode; namespace XRMultiplayer { /// /// Controls the music playback in a room. /// public class RoomMusic : NetworkBehaviour { /// /// Indicates whether the music should start playing automatically. /// [SerializeField] bool m_AutoPlay; /// /// The list of music clips to play. /// [SerializeField, Tooltip("Format clip name to 'Song Title-Artist Name'")] AudioClip[] m_MusicClips; /// /// The audio source to play the music. /// [SerializeField] AudioSource m_AudioSource; /// /// The slider to control the timeline of the current playing track. /// [SerializeField] Slider m_TimelineSlider; /// /// The slider to control the volume of the current playing track. /// [SerializeField] Slider m_VolumeSlider; /// /// The dropdown to select the current track to play. /// [SerializeField] TMP_Dropdown m_Dropdown; /// /// The text to display the current playing track. /// [SerializeField] TMP_Text[] m_CurrentSongText; /// /// The toggle to play/pause the current track. /// [SerializeField] Toggle m_PlayPauseToggle; /// /// The toggle to shuffle the music. /// [SerializeField] Toggle m_ShuffleToggle; /// /// The button to play the next track. /// [SerializeField] Button m_NextButton; /// /// The button to play the previous track. /// [SerializeField] Button m_PreviousButton; /// /// The image to display the play/pause state. /// [SerializeField] Image m_PlayPauseImage; /// /// The sprite to display the pause state. /// [SerializeField] Sprite m_PauseSprite; /// /// The sprite to display the play state. /// [SerializeField] Sprite m_PlaySprite; /// /// The network variable to store the current song ID. /// readonly NetworkVariable m_CurrentSongIdNetworked = new NetworkVariable(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); /// /// The network variable to store the state of whether or not we are actively playing music. /// readonly NetworkVariable m_IsPlayingNetworked = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); /// /// Indicates whether the music should be shuffled. /// bool m_Shuffle; /// /// The current song ID. /// int currentSongId = 0; /// void Start() { // Clear the dropdown options m_Dropdown.ClearOptions(); if (m_MusicClips == null || m_MusicClips.Length == 0) { Utils.LogWarning("No music clips found in RoomMusic script. Please add some music clips to the RoomMusic script."); enabled = false; return; } // Add the music clip names as options to the dropdown foreach (var c in m_MusicClips) { TMP_Dropdown.OptionData optionData = new TMP_Dropdown.OptionData(c.name); m_Dropdown.options.Add(optionData); } // If auto play is enabled, pick a random song if (m_AutoPlay) { PickRandomSong(); } // Set the initial volume and clip for the audio source m_AudioSource.volume = m_VolumeSlider.value; m_AudioSource.clip = m_MusicClips[Random.Range(0, m_MusicClips.Length)]; SetupUIListeners(); // Update the song title text UpdateSongTitleText(); } /// public override void OnNetworkSpawn() { base.OnNetworkSpawn(); // Set the volume of the audio source m_AudioSource.volume = m_VolumeSlider.value; // Add listeners for the network variable change events m_CurrentSongIdNetworked.OnValueChanged += CurrentSongUpdated; m_IsPlayingNetworked.OnValueChanged += OnIsPlayingChanged; if (IsServer) { m_IsPlayingNetworked.Value = m_AutoPlay; if (!m_AutoPlay) { m_CurrentSongIdNetworked.Value = 0; SetClipTime(0.0f); m_TimelineSlider.SetValueWithoutNotify(0.0f); } } else { if (m_IsPlayingNetworked.Value) { SetSong(m_CurrentSongIdNetworked.Value); GetCurrentSongPercFromServerRpc(); } OnIsPlayingChanged(false, m_IsPlayingNetworked.Value); } CurrentSongUpdated(0, m_CurrentSongIdNetworked.Value); } public override void OnNetworkDespawn() { base.OnNetworkDespawn(); OnIsPlayingChanged(false, false); // Remove listeners for the network variable change events m_CurrentSongIdNetworked.OnValueChanged -= CurrentSongUpdated; m_IsPlayingNetworked.OnValueChanged -= OnIsPlayingChanged; SetClipTime(0.0f); m_TimelineSlider.SetValueWithoutNotify(0.0f); if (IsServer) { if (m_CurrentSongIdNetworked != null) m_CurrentSongIdNetworked.Value = 0; if (m_IsPlayingNetworked != null) m_IsPlayingNetworked.Value = false; } } /// private void Update() { if (!m_IsPlayingNetworked.Value || !NetworkManager.Singleton.IsConnectedClient) return; // Calculate the current playback percentage float perc = m_AudioSource.time / m_AudioSource.clip.length; m_TimelineSlider.SetValueWithoutNotify(perc); // If the playback is near the end, pick a new song if (perc >= .999f) { PickNewSong(); } } /// public override void OnDestroy() { base.OnDestroy(); RemoveUIListeners(); } /// /// Sets up the listeners for UI events. /// void SetupUIListeners() { m_TimelineSlider.onValueChanged.AddListener(SetClipTime); m_VolumeSlider.onValueChanged.AddListener(UpdateVolume); m_Dropdown.onValueChanged.AddListener(PickSong); m_PlayPauseToggle.onValueChanged.AddListener(TogglePlay); m_ShuffleToggle.onValueChanged.AddListener(ToggleShuffle); m_NextButton.onClick.AddListener(delegate { PickNewSong(1); }); m_PreviousButton.onClick.AddListener(delegate { PickNewSong(-1); }); } /// /// Removes the listeners for UI events. /// void RemoveUIListeners() { m_TimelineSlider.onValueChanged.RemoveListener(SetClipTime); m_VolumeSlider.onValueChanged.RemoveListener(UpdateVolume); m_Dropdown.onValueChanged.RemoveListener(PickSong); m_PlayPauseToggle.onValueChanged.RemoveListener(TogglePlay); m_ShuffleToggle.onValueChanged.RemoveListener(ToggleShuffle); m_NextButton.onClick.RemoveListener(delegate { PickNewSong(1); }); m_PreviousButton.onClick.RemoveListener(delegate { PickNewSong(-1); }); } /// /// Updates the volume of the audio source. /// /// The new volume value. void UpdateVolume(float volume) { m_AudioSource.volume = volume; } /// /// Toggles the shuffle mode. /// /// The new toggle state. void ToggleShuffle(bool toggle) { m_Shuffle = toggle; } /// /// Toggles the play/pause state of the audio source. /// /// The new toggle state. void TogglePlay(bool toggle) { if (IsServer) { m_IsPlayingNetworked.Value = toggle; } } /// /// Picks a new song based on the shuffle mode and direction. /// /// The direction to pick the new song (-1 for previous, 1 for next). void PickNewSong(int dir = 1) { if (!IsServer) return; if (m_Shuffle) { PickRandomSong(); } else { int nextSongId = Utils.RealMod(m_Dropdown.value + dir, m_MusicClips.Length); PickSong(nextSongId); } } /// /// Picks a random song to play. /// void PickRandomSong() { int tries = 10; int randomSongId = m_CurrentSongIdNetworked.Value; while (tries > 0) { tries--; randomSongId = Random.Range(0, m_MusicClips.Length); if (randomSongId != m_CurrentSongIdNetworked.Value) { break; } } PickSong(randomSongId); } /// /// Picks a song to play based on the selected dropdown option. /// /// The ID of the song to play. void PickSong(int songId) { m_Dropdown.value = songId; SetClipTime(0.0f); if (IsServer) { m_CurrentSongIdNetworked.Value = songId; m_IsPlayingNetworked.Value = true; } } /// /// Plays a song based on the given song ID. /// /// The ID of the previous song. /// The ID of the new song. void CurrentSongUpdated(int oldSongId, int songId) { if (songId >= 0 && songId < m_MusicClips.Length) { SetSong(songId); if (m_IsPlayingNetworked.Value) { SetClipTime(0.0f); m_AudioSource.Play(); m_PlayPauseImage.sprite = m_PauseSprite; m_PlayPauseToggle.SetIsOnWithoutNotify(true); } } } void SetSong(int songId) { m_AudioSource.clip = m_MusicClips[songId]; currentSongId = songId; UpdateSongTitleText(); } void OnIsPlayingChanged(bool oldValue, bool newValue) { if (newValue) { m_AudioSource.Play(); m_PlayPauseImage.sprite = m_PauseSprite; } else { m_AudioSource.Pause(); m_PlayPauseImage.sprite = m_PlaySprite; } m_PlayPauseToggle.SetIsOnWithoutNotify(newValue); } /// /// Sets the clip time of the audio source based on the timeline slider value. /// /// The new value of the timeline slider. void SetClipTime(float value) { if (!enabled) return; m_AudioSource.time = Mathf.Clamp(value, .01f, .99f) * m_AudioSource.clip.length; } [Rpc(SendTo.Server)] void GetCurrentSongPercFromServerRpc(RpcParams rpcParams = default) { SendCurrentSongPercToClientRpc(m_AudioSource.time / m_AudioSource.clip.length, RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp)); } [Rpc(SendTo.SpecifiedInParams)] void SendCurrentSongPercToClientRpc(float perc, RpcParams rpcParams = default) { m_AudioSource.time = perc * m_AudioSource.clip.length; m_TimelineSlider.SetValueWithoutNotify(perc / m_AudioSource.clip.length); } /// /// Updates the song title text. /// void UpdateSongTitleText() { string songName = m_MusicClips[currentSongId == -1 ? 0 : currentSongId].name; string[] songNameSplit = m_MusicClips[currentSongId == -1 ? 0 : currentSongId].name.Split('-'); if (songNameSplit.Length > 1) { songName = $"{songNameSplit[0]}\n{songNameSplit[1]}"; } foreach (TMP_Text text in m_CurrentSongText) { text.text = songName; } } } }