메타버스 뮤직
개요
Music 씬을 통해 플레이어는 소리와 음악을 트리거 할 수 있는 패드로 DJ 기술을 테스트할 수 있을 뿐만 아니라 조명 쇼를 제어할 수 있습니다.
이것은 네트워크를 통해 오디오 트랙이나 조명을 동기화하는 방법을 보여줍니다.
그 씬은 여러 개의 DJ 패드를 포함합니다. 어떤 것은 음악을 조절하고 다른 것은 조명을 조절합니다.
데스크톱 모드에서는 각 패드 하단의 줌 아이콘을 사용하여 마우스로 UI를 더 잘 제어할 수 있도록 전체 화면으로 표시할 수 있습니다.
뮤직 패드
각각의 뮤직 패드는 하나 또는 그 이상의 버튼들로 구성되어 있습니다. 각각의 버튼에는 오디오 소스와 소리가 대응합니다. 소리는 루프로 설정될 수 있습니다.
뮤직 패드에는 볼륨을 변경할 수 있는 슬라이더가 포함되어 있습니다.
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
이 네트워크를 통해 동기화됩니다.
Render()
루프에서 MasterVolume
변수 수정을 탐지하기 위해 ChangeDetector
를 사용합니다.
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
는 각 오디오 소스를 제어하는 버튼을 알고, 오디오 소스 상태가 바뀌었을 때 어떤 버튼을 통지해야 하는지 알 수 있습니다.
그래서 사용자가 버튼을 터치하면 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
)의 오디오 소스를 업데이트합니다 - 버튼 상태가 변경된 경우 패드 네트워크 사전을 업데이트합니다(예: 오디오 클립이 완료되었습니다)
- 네트워크 사전에 따라 각 버튼의 오디오 소스(재생/정지)를 동기화합니다
- 버튼 색상을 업데이트하기 위해 버튼의 상태가 변경되었음을 알려줍니다
네트워크 사전이 변경되는 즉시 이러한 업데이트를 수행하는 대신 사전에 정의된 BPM 매개 변수를 기반으로 정기적으로 수행됩니다(AudioLoop()
메소드).
라이트 패드
라이트 패드는 4개의 조명을 제어합니다.
각 조명에 대해 패드를 사용하면 다음을 수행할 수 있습니다:
- 조명을 켜다/끄다
- 조명의 움직임을 켜다/끄다
- 빛의 세기를 바꾸다
라이트 패드 구조는 뮤직 패드와 매우 유사합니다.
조명은 다음 클래스에 의해 관리됩니다:
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
에게 알려줍니다.
OnLightStatusChanged()
는 상태가 변경되면 PadManager
에 의해 호출되므로 버튼 UI를 업데이트해야 합니다.
LightSystem
LightSystem
클래스는 EffectSystem
이라는 추상적 클래스에서 상속받습니다.
LightSystem
은 ChangeState
메소드로 수신되는 파라미터에 따라 조명 상태를 변경하는 역할을 합니다:
LightDirectionControler
LightDirectionControler
클래스는 라이트 게임 객체를 회전시키는 회전 스크립트를 제어합니다.
LightDirectionControler
는 LightSystem에 의해 활성화/비활성화됩니다.
LightPadManager
LightPadManager
는 모든 조명 객채(LightInfo
)를 의미합니다.
각 LightInfo
에는 조명의 매개변수를 수정할 수 있는 인덱스와 효과 시스템이 있습니다.
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;
로컬 플레이어가 버튼을 누르면 LightPadManager
가 관련된 메소드(ChangeLightState()/ChangeMovementState()/ChangeIntensity()
)로 알려줍니다.
그런 다음 연결된 네트워크 사전이 업데이트됩니다.
예를 들어 조명이 켜지거나 꺼지면 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