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