This document is about: FUSION 2
SWITCH TO

Metaverse Music


Available in the Industries Circle
Circle

概要

Musicシーンでは、音や曲を鳴らすパッドやライトショーを制御して、プレイヤーのDJスキルを試すことができます。これによって、オーディオトラックや光をネットワークを通して同期する方法を示します。シーンにはいくつかのDJパッドが置いてあり、ミュージックを制御するものと、ライトを制御するものがあります。

デスクトップモードでは、各パッド下部のズームアイコンから、マウスでUIを操作しやすくするためにパッドをフルスクリーンで表示できます。

Fusion Metaverse Music

ミュージックパッド

各ミュージックパッドは1つ以上のボタンで構成されます。各ボタンはAudioSouceの音に対応しています。音はループ再生に設定できます。
ミュージックパッドのスライダーで音量を調整できます。

Fusion Metaverse Music

DJパッドの振る舞いは、3つのクラスで管理されています。

  • DJPadVolumeSlider : パッドの音量の管理
  • DJPadTouch : サウンドボタンの管理
  • DJPadManager : パッドの全体的な機能を管理

DJPadVolumeSlider

プレイヤーがスライダーをタッチして音量を変更すると、DJPadVolumeSliderDJPadManagerChangeVolumeメソッドを呼び出します。

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の状態が変更された時にどのボタンに通知するのかを把握しています。
そのため、ユーザーがボタンにタッチした時に、DJPadManagerDJPadTouchUpdatePadStatus()メソッドを呼び出します。
そして、UpdatePadStatus()DJPadManagerの新しいステータスを通知します。

C#

public void UpdatePadStatus()
{
    if (audioSource.clip)
    { 
        isPlaying = !isPlaying;
        padManager.ChangeAudioSourceState(this, isPlaying);
    }
}

また、ボタンがタッチされた時、DJPadManagerDJPadTouchOnAudioSourceStatusChangedメソッドを呼び出して、新しいステータスに応じてボタンの色を更新します。

DJPadManager

各ボタンのステータスは、PadsStatusと呼ばれるネットワークディクショナリーによって同期されます。

C#

[Networked]
Capacity(50)]
public NetworkDictionary<int, NetworkBool> PadsStatus { get; }

開始時に、DJPadManagerはすべてのボタンをaudioSourceManagersディクショナリーに保存します(すべてのDJPadTouchIAudioSourceManagerインターフェースを実装しています)。

C#

foreach (var manager in GetComponentsInChildren<IAudioSourceManager>(true))
{
    audioSourceManagers[manager.AudioSourceIndex] = manager;
}

DJPadManagerChangeAudioSourceState()から新しいボタンのステータスを受け取ると、状態権限を持っていなければ状態権限をリクエストして、それからすべてのリモートユーザーが更新を受け取れるようにネットワークディクショナリーを更新します。

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と非常に似ています。
プレイヤーがスライダーをタッチしてライトの強さを変更すると、RequestIntensityChangeLightPadManagerChangeIntensity()メソッドを呼び出します。

別の方法として、リモートプレイヤーがライトの強さを変更すると、スライダーの位置を更新するために、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の状態が変更された時にどのボタンに通知するのかを把握しています。
そのため、ユーザーがボタンにタッチした時に、DJPadManagerDJPadTouchUpdatePadStatus()メソッドを呼び出します。
そして、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