Udon의 SendCustomNetworkEvent는 두 가지 옵션 밖에 없다. NetworkEventTarget.All
또는 NetworkEventTarget.Owner
. 즉 임의의 플레이어에게 이벤트를 보낼 수는 없다.
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Chair : UdonSharpBehaviour
{
[SerializeField] private VRCStation station;
public void Seat(VRCPlayerApi player)
{
station.UseStation(player); // 권한문제!
}
}
Udon은 Remote player를 텔레포트, 앉게 할 수 없다. 따라서 해당 플레이어가 직접 함수를 호출해야한다.
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Chair : UdonSharpBehaviour
{
[UdonSynced] private int _playerId;
[SerializeField] private VRCStation station;
public void Seat(VRCPlayerApi player)
{
RequestSerialization(); // _playerId 동기화 요청
SendCustomNetworkEvent(NetworkEventTarget.ALl, "SeatEvent");
}
public void SeatEvent()
{
// _playerId가 동기화 됐을까???
if (!VRCPlayerApi.GetPlayerById(_playerId).isLocal)
return;
station.UseStation(Networking.LocalPlayer);
}
}
의자에 앉히고 싶은 _playerId
를 동기화하고, SeatEvent
를 모든 플레이어에게 요청했다.
무엇이 문제인지 알겠는가?
RequestSerialization
에 의한 _playerId
의 동기화가 SeatEvent
호출보다 먼저 일어날 것이라는 보장이 없다!
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Chair : UdonSharpBehaviour
{
[SerializeField] private VRCStation station;
public override void OnOwnershipTransferred(VRCPlayerApi player)
{
if (!Networking.IsOwner(gameObject)) return;
station.UseStation(Networking.LocalPlayer);
}
public void Seat(VRCPlayerApi player)
{
if (player.isLocal)
{
station.UseStation(player);
}
else
{
Networking.SetOwner(player, gameObject);
}
}
}
소유권이 넘어가고 호출되는 이벤트인 OnOwnershipTransferred
에서 이 의자가 자신의 소유인지 확인하고, 스스로 앉는다.
Udon에서는 특정 플레이어에게만 이벤트를 보낼 수 있는 방법이 없어서, 소유권 이전을 통한 꼼수가 (지금까지 발견한) 최선의 방법인 것 같다.
세 번째 방법의 문제점:
플레이어가 나가거나 할 때 소유권 이전이 일어난다.
이 때도, 플레이어를 앉게 해버리는 버그가 발생한다.
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Chair : UdonSharpBehaviour
{
[SerializeField] private int _playerId;
[SerializeField] private VRCStation station;
public override void OnDeserialization()
{
if (_playerId == Networking.LocalPlayer.playerId)
station.UseStation(Networking.LocalPlayer);
}
public void Seat(VRCPlayerApi player)
{
_playerId = player.playerId;
RequestSerialization();
if (player.isLocal)
{
station.UseStation(player);
}
}
}
OnDeserialization()을 통해 동기화 타이밍과 버그 모두 해결할 수 있었다.