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