Metaverse Music
概要
Musicシーンでは、音や曲を鳴らすパッドやライトショーを制御して、プレイヤーのDJスキルを試すことができます。これによって、オーディオトラックや光をネットワークを通して同期する方法を示します。シーンにはいくつかのDJパッドが置いてあり、ミュージックを制御するものと、ライトを制御するものがあります。
デスクトップモードでは、各パッド下部のズームアイコンから、マウスでUIを操作しやすくするためにパッドをフルスクリーンで表示できます。
ミュージックパッド
各ミュージックパッドは1つ以上のボタンで構成されます。各ボタンはAudioSouceの音に対応しています。音はループ再生に設定できます。
ミュージックパッドのスライダーで音量を調整できます。
DJパッドの振る舞いは、3つのクラスで管理されています。
DJPadVolumeSlider: パッドの音量の管理DJPadTouch: サウンドボタンの管理DJPadManager: パッドの全体的な機能を管理
DJPadVolumeSlider
プレイヤーがスライダーをタッチして音量を変更すると、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);
}
DJPadTouch
各ボタンはインデックスを参照しているため、DJPadManagerは、どのボタンがAudioSourceを制御しているのか、AudioSourceの状態が変更された時にどのボタンに通知するのかを把握しています。
そのため、ユーザーがボタンにタッチした時に、DJPadManagerはDJPadTouchのUpdatePadStatus()メソッドを呼び出します。
そして、UpdatePadStatus()はDJPadManagerの新しいステータスを通知します。
C#
public void UpdatePadStatus()
{
if (audioSource.clip)
{
isPlaying = !isPlaying;
padManager.ChangeAudioSourceState(this, isPlaying);
}
}
また、ボタンがタッチされた時、DJPadManagerはDJPadTouchのOnAudioSourceStatusChangedメソッドを呼び出して、新しいステータスに応じてボタンの色を更新します。
DJPadManager
各ボタンのステータスは、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)のAudioSourceを更新 - ボタンのステータスが変更(例えば、
AudioClipが再生完了)されたら、パッドのネットワークディクショナリーを更新 - ネットワークディクショナリーに応じて、各ボタンの
AudioSource(再生/停止)を同期 - ボタンにステータス変更を通知して、ボタンの色を更新
ネットワークディクショナリーが変更されてすぐに更新を行うかわりに、定義済みのBPMパラメーターに基づいて定期的に更新が実行されます(AudioLoop()メソッド)。
ライトパッド
ライトパッドは4つのライトを制御します。
各ライトについて、ユーザーはパッドで以下のことができます。
- ライトのon/off
- ライトの動きのon/off
- ライトの強さの変更
ライトパッドの構造はミュージックパッドと非常に似ています。
ライティングは以下のクラスで管理されます。
LightPadManager: パッドの全体的な機能の管理LightPadTouch: ライトボタンの管理LightSystem: ライトの状態の管理LightIntensitySlider: ライトの強さの管理LightDirectionControler: ライトの回転の管理
LightIntensitySlider
LightIntensitySliderは、DJPadVolumeSliderと非常に似ています。
プレイヤーがスライダーをタッチしてライトの強さを変更すると、RequestIntensityChangeがLightPadManagerのChangeIntensity()メソッドを呼び出します。
別の方法として、リモートプレイヤーがライトの強さを変更すると、スライダーの位置を更新するために、LightPadManagerによってOnLightStatusChangedメソッドが呼び出されます。
LightPadTouch
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には変更すべき状態が通知されます。
ステータスが変更されてボタンのUIを更新が必要な時に、PadManagerからOnLightStatusChanged()が呼び出されます。
AudioSourceの状態が変更された時にどのボタンに通知するのかを把握しています。
そのため、ユーザーがボタンにタッチした時に、DJPadManagerはDJPadTouchのUpdatePadStatus()メソッドを呼び出します。
そして、UpdatePadStatus()はDJPadManagerの新しいステータスを通知します。
LightSystem
LightSystemクラスは、抽象クラスEffectSystemを継承しています。
LightSystemは、ChangeStateメソッドから受け取ったパラメーターに応じたライトの状態の変更を担います。
- ライト自身 (on/off)
- ライトの動き (on/off)
- ライトの強さ (float)
LightDirectionControler
LightDirectionControlerクラスは、ライトのゲームオブジェクトの回転方向のスクリプトを制御します。
LightDirectionControlerは、LightSystemから有効/無効にできます。
LightPadManager
LightPadManagerは、すべてのライトオブジェクト(LightInfo)を参照します。
各LightInfoはインデックスと、ライトのパラメーターを変更できるEffectSystemを持ちます。
C#
public struct LightInfo
{
public int lightIndex;
public EffectSystem effectSystem;
}
開始時に、LightPadManagerはすべてのライトボタンをディクショナリーに登録します。
また、ライトの様々なパラメーター(ライトのon/off、ライトの動きのon/off、ライトの強さ)を保存するために、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に通知されます。
そして、関連するネットワークディクショナリーが更新されます。
例えば、ライトのスイッチをon/offすると、LightPadManagerは、状態権限をまだ持っていなければリクエストして、それからすべてのリモートユーザーが更新を受け取れるようにネットワークディクショナリーを更新します。
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()は、すべてのライトの状態と、UpdateLightAndButtons()メソッドを使用して関連するボタンの更新を担います。
プレイヤーがセッションに参加すると、ネットワークディクショナリーによってライトが更新されます。
Back to top