Metaverse Music
概述
音樂 場景允許玩家來測試他的DJ技能,其附有盤來允許他觸發聲音及音樂,以及控制燈光秀。
這展示了透過網路來同步一個音軌或燈光的方法。
場景包含了多個DJ盤。有些控制音樂而另一個控制燈光。
請注意,在桌面模式中,各個盤底部的縮放圖標可以全螢幕顯示盤,以透過滑鼠來更好地控制UI。
音樂盤
各個音樂盤由一個或多個按鈕組成。各個按鈕對應一個音源或一個聲音。聲音可以被設定為一個迴圈。
音樂盤含有一個滑塊來更改音量。
DJ盤行為由3個類別來管理:
DJPadVolumeSlider
:管理盤的音量DJPadTouch
:管理聲音按鈕DJPadManager
:管理盤的總體功能
DJ盤音量滑塊
當玩家觸碰滑塊來更改音量時,DJPadVolumeSlider
調用DJPadManager ChangeVolume
方法。
C#
void RequestVolumeChange(float volume)
{
padManager.ChangeVolume(volume);
}
public async void ChangeVolume(float volume)
{
// We use an attribute, so if another volume is requested while taking the authority, the last volume request is the one executed
lastVolumeRequest = volume;
if (!Object.HasStateAuthority)
{
await Object.WaitForStateAuthority();
}
MasterVolume = lastVolumeRequest;
}
在DJPadManager
中的已連網變數MasterVolume
隨後透過網路被同步。
ChangeDetector
用於偵測在Render()
迴圈中的MasterVolume
變數調整。
C#
[Networked]
public float MasterVolume { get; set; } = 1;
ChangeDetector changeDetector;
public override void Render()
{
base.Render();
foreach (var changedVar in changeDetector.DetectChanges(this))
{
if (changedVar == nameof(MasterVolume))
{
OnMasterVolumeChanged();
}
}
}
void OnMasterVolumeChanged()
{
if(volumeManager != null) volumeManager.OnVolumeChanged(this, MasterVolume);
}
這樣任何人可以更新本機音量。
C#
public void OnVolumeChanged(DJPadManager bPMClipsPlayer, float volume)
{
ChangeSliderValue(volume);
}
DJ盤觸碰
各個按鈕參照一個索引,這樣DJPadManager
知道哪個按鈕控制各個音源,並且知道在一個音源狀態改變時應該通知哪個按鈕。
因此當使用者觸碰一個按鈕,它調用DJPadTouch
的UpdatePadStatus()
方法。
然後,UpdatePadStatus()
通知新的狀態的DJPadManager
。
C#
public void UpdatePadStatus()
{
if (audioSource.clip)
{
isPlaying = !isPlaying;
padManager.ChangeAudioSourceState(this, isPlaying);
}
}
同時,當已經觸碰一個按鈕,DJPadManager
調用DJPadTouch
的OnAudioSourceStatusChanged
方法,以要求它根據新的狀態來更新按鈕顏色。
DJ盤管理器
利用一個名為PadsStatus
的網路字典來同步各個按鈕的狀態。
C#
[Networked]
Capacity(50)]
public NetworkDictionary<int, NetworkBool> PadsStatus { get; }
在開始時,DJPadManager
在一個audioSourceManagers
字典中儲存所有按鈕(所有DJPadTouch
執行IAudioSourceManager
介面)。
C#
foreach (var manager in GetComponentsInChildren<IAudioSourceManager>(true))
{
audioSourceManagers[manager.AudioSourceIndex] = manager;
}
當DJPadManager
接收到一個新的附有ChangeAudioSourceState()
的按鈕狀態,它請求狀態授權(如果它還沒有的話),然後更新已連網字典,這樣所有遠端玩家將接收更新。
C#
public async void ChangeAudioSourceState(IAudioSourceManager audioSourceManager, bool isPlaying)
{
if (!Object.HasStateAuthority)
{
await Object.WaitForStateAuthority();
}
PadsStatus.Set(audioSourceManager.AudioSourceIndex, isPlaying);
}
RefreshPads()
, SyncAudioSource()
及OnAudioStatusChanged()
方法負責:
- 據音量滑塊值來更新各個按鈕(
DJPadTouch
)的音源 - 如果一個按鈕狀態已經改變(舉例而言,一個音訊片段已經完成),更新盤已連網字典
- 根據已連網字典來同步各個按鈕的音源(播放/停止)
- 告知一個按鈕,其已更改狀態,以更新按鈕顏色
並非在已連網字典更改後立即進行這些更新,而是基於預先定義的BPM參數來定期執行這些更新
(方法AudioLoop()
)。
燈光盤
燈光盤控制4個燈光。
針對各個燈光,盤允許使用者來:
- 切換燈光開/關
- 切換燈光的移動的開/關
- 改變燈光強度
燈光盤架構非常類似於音樂盤。
燈光由以下類別來管理:
LightPadManager
:管理盤的總體功能LightPadTouch
:管理燈光按鈕LightSystem
:管理燈光狀態LightIntensitySlider
:管理燈光強度LightDirectionControler
:管理燈光旋轉器
燈光強度滑塊
LightIntensitySlider
非常類似於DJPadVolumeSlider
。
當玩家透過觸碰滑塊來更改強度時,RequestIntensityChange
調用LightPadManager ChangeIntensity()
方法。
在另一方面,如果遠端玩家更改強度,LightPadManager
調用OnLightStatusChanged
方法,以更新滑塊位置。
燈光盤觸碰
LightPadTouch
管理燈光按鈕。
如同在LightPadManager
中定義的,有3種燈光按鈕:
C#
public enum LightManagerType
{
OnOff, // switch on/off the light
Movement, // switch on/off the movement
Intensity // slider to change the intensity
}
各個按鈕參照一個燈光索引,這樣LightPadManager
知道哪個按鈕控制各個燈光,並且知道在燈光狀態更改時,應該通知哪個按鈕。
當玩家觸碰一個按鈕時,調用UpdatePadStatus()
。它告知PadManager
,必須更改狀態。
當狀態更改時,由PadManager
調用OnLightStatusChanged()
,因此必須更新按鈕UI。
燈光系統
LightSystem
類別繼承自抽象類別EffectSystem
。
LightSystem
負責根據在ChangeState
方法中接收的參數來更改燈光狀態:
燈光方向控制器
LightDirectionControler
類別控制旋轉器指令碼,其旋轉燈光遊戲物件。
LightDirectionControler
由燈光系統啟用/停用。
燈光盤管理器
LightPadManager
參照所有燈光物件(LightInfo
)。
各個LightInfo
有一個索引及一個效果系統,其可以調整燈光參數。
C#
public struct LightInfo
{
public int lightIndex;
public EffectSystem effectSystem;
}
在開始時,LightPadManager
註冊所有燈光按鈕到一個字典之中。
同時,有3個已連網字典,來儲存燈光的各種參數(燈光開/關、移動開/關、燈光強度)
C#
[Networked]
[Capacity(MAX_LIGHTS)]
public NetworkDictionary<int, NetworkBool> LightStatus { get; }
[Networked]
[Capacity(MAX_LIGHTS)]
public NetworkDictionary<int, NetworkBool> LightMovementStatus { get; }
[Networked]
[Capacity(MAX_LIGHTS)]
public NetworkDictionary<int, float> LightIntensities { get; }
ChangeDetector changeDetector;
當本機玩家按下一個按鈕,由關聯的方法(ChangeLightState()/ChangeMovementState()/ChangeIntensity()
)告知LightPadManager
。
然後更新關聯的已連網字典。
舉例而言,當一個燈光被切換開/關,LightPadManager
請求StateAuthority
(如果它還沒有它),並且隨後更新已連網字典,這樣所有遠端玩家將接收更新。
C#
public async void ChangeLightState(LightPadTouch lightPadTouch, bool isLightOn)
{
if (!Object.HasStateAuthority)
{
await Object.WaitForStateAuthority();
}
// update network status
LightStatus.Set(lightPadTouch.LightIndex, isLightOn);
// movement must be stopped if the light is off
if (!isLightOn)
LightMovementStatus.Set(lightPadTouch.LightIndex, false);
}
當更新字典後,利用ChangeDetector
在本機及遠端玩家上調用Refresh()
方法。
C#
public override void Render()
{
base.Render();
foreach (var changedVar in changeDetector.DetectChanges(this))
{
if (changedVar == nameof(LightStatus))
{
Refresh();
}
if (changedVar == nameof(LightMovementStatus))
{
Refresh();
}
if (changedVar == nameof(LightIntensities))
{
Refresh();
}
}
}
Refresh()
負責使用UpdateLightsAndButtons()
方法來更新所有燈光狀態及關聯的按鈕
當玩家加入時,它的燈光透過已連網字典被更新。
Back to top