Files
RoomAwareVR/Assets/VRMPAssets/Scripts/Gameplay/RoomMusic.cs
2025-05-26 00:46:28 +02:00

426 lines
14 KiB
C#

using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Unity.Netcode;
namespace XRMultiplayer
{
/// <summary>
/// Controls the music playback in a room.
/// </summary>
public class RoomMusic : NetworkBehaviour
{
/// <summary>
/// Indicates whether the music should start playing automatically.
/// </summary>
[SerializeField] bool m_AutoPlay;
/// <summary>
/// The list of music clips to play.
/// </summary>
[SerializeField, Tooltip("Format clip name to 'Song Title-Artist Name'")] AudioClip[] m_MusicClips;
/// <summary>
/// The audio source to play the music.
/// </summary>
[SerializeField] AudioSource m_AudioSource;
/// <summary>
/// The slider to control the timeline of the current playing track.
/// </summary>
[SerializeField] Slider m_TimelineSlider;
/// <summary>
/// The slider to control the volume of the current playing track.
/// </summary>
[SerializeField] Slider m_VolumeSlider;
/// <summary>
/// The dropdown to select the current track to play.
/// </summary>
[SerializeField] TMP_Dropdown m_Dropdown;
/// <summary>
/// The text to display the current playing track.
/// </summary>
[SerializeField] TMP_Text[] m_CurrentSongText;
/// <summary>
/// The toggle to play/pause the current track.
/// </summary>
[SerializeField] Toggle m_PlayPauseToggle;
/// <summary>
/// The toggle to shuffle the music.
/// </summary>
[SerializeField] Toggle m_ShuffleToggle;
/// <summary>
/// The button to play the next track.
/// </summary>
[SerializeField] Button m_NextButton;
/// <summary>
/// The button to play the previous track.
/// </summary>
[SerializeField] Button m_PreviousButton;
/// <summary>
/// The image to display the play/pause state.
/// </summary>
[SerializeField] Image m_PlayPauseImage;
/// <summary>
/// The sprite to display the pause state.
/// </summary>
[SerializeField] Sprite m_PauseSprite;
/// <summary>
/// The sprite to display the play state.
/// </summary>
[SerializeField] Sprite m_PlaySprite;
/// <summary>
/// The network variable to store the current song ID.
/// </summary>
readonly NetworkVariable<int> m_CurrentSongIdNetworked = new NetworkVariable<int>(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
/// <summary>
/// The network variable to store the state of whether or not we are actively playing music.
/// </summary>
readonly NetworkVariable<bool> m_IsPlayingNetworked = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
/// <summary>
/// Indicates whether the music should be shuffled.
/// </summary>
bool m_Shuffle;
/// <summary>
/// The current song ID.
/// </summary>
int currentSongId = 0;
/// <inheritdoc/>
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();
}
/// <inheritdoc/>
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;
}
}
/// <inheritdoc/>
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();
}
}
/// <inheritdoc/>
public override void OnDestroy()
{
base.OnDestroy();
RemoveUIListeners();
}
/// <summary>
/// Sets up the listeners for UI events.
/// </summary>
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); });
}
/// <summary>
/// Removes the listeners for UI events.
/// </summary>
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); });
}
/// <summary>
/// Updates the volume of the audio source.
/// </summary>
/// <param name="volume">The new volume value.</param>
void UpdateVolume(float volume)
{
m_AudioSource.volume = volume;
}
/// <summary>
/// Toggles the shuffle mode.
/// </summary>
/// <param name="toggle">The new toggle state.</param>
void ToggleShuffle(bool toggle)
{
m_Shuffle = toggle;
}
/// <summary>
/// Toggles the play/pause state of the audio source.
/// </summary>
/// <param name="toggle">The new toggle state.</param>
void TogglePlay(bool toggle)
{
if (IsServer)
{
m_IsPlayingNetworked.Value = toggle;
}
}
/// <summary>
/// Picks a new song based on the shuffle mode and direction.
/// </summary>
/// <param name="dir">The direction to pick the new song (-1 for previous, 1 for next).</param>
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);
}
}
/// <summary>
/// Picks a random song to play.
/// </summary>
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);
}
/// <summary>
/// Picks a song to play based on the selected dropdown option.
/// </summary>
/// <param name="songId">The ID of the song to play.</param>
void PickSong(int songId)
{
m_Dropdown.value = songId;
SetClipTime(0.0f);
if (IsServer)
{
m_CurrentSongIdNetworked.Value = songId;
m_IsPlayingNetworked.Value = true;
}
}
/// <summary>
/// Plays a song based on the given song ID.
/// </summary>
/// <param name="oldSongId">The ID of the previous song.</param>
/// <param name="songId">The ID of the new song.</param>
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);
}
/// <summary>
/// Sets the clip time of the audio source based on the timeline slider value.
/// </summary>
/// <param name="value">The new value of the timeline slider.</param>
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);
}
/// <summary>
/// Updates the song title text.
/// </summary>
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 = $"<b>{songNameSplit[0]}</b>\n{songNameSplit[1]}";
}
foreach (TMP_Text text in m_CurrentSongText)
{
text.text = songName;
}
}
}
}