유니티 Fusion2 with Shared Mode #시작하기
Fusion2란?
Fusion2는 Photon의 차세대 네트워크 엔진으로, 서버 모드, 호스트 모드, 공유 모드 등 다양한 네트워크 토폴로지를 지원합니다.
모드 | 특징 | 사용 사례 |
서버 모드 | 전용 서버, 공용 IP, 높은 안정성, 보안성 | 대규모 멀티플레이어 온라인 게임 |
호스트 모드 | 호스트 플레이어, 간편한 설정 | 소규모 협동 게임 |
공유 모드 | 클라우드 룸, StateAuthority | 다양한 디바이스와 네트워크 환경 지원 |
Fusion2의 주요 기능으로는 네트워크 객체 동기화, 입력 동기화, 네트워크 이벤트 처리, 유연한 Room 관리 등이 있습니다.
Fusion2 시작하기
유니티 프로젝트 세팅 및 Fusion2 App ID 설정은 위 Photon 홈페이지를 참고해서 따라하시면 됩니다.
이 포스팅은 Host Mode가 아닌 Shared Mode 기반으로 제작됩니다.
또한, Fusion2 공식문서에서 중요하다고 생각되는 부분만 작성할 예정이므로 자세한 내용은 '반드시' 공식 문서를 참고해주세요.
IPlayerJoined , SimulationBehaviour
IPlayerJoined 인터페이스를 사용하면 플레이어들이 세션에 참여할 때마다 호출되는 PlayerJoined함수를 이용할 수 있습니다.
PlayerJoined함수의 경우 본인만 호출되는 것이 아닌 다른 플레이어에게도 발생합니다.
SimulationBehaviour는 로컬 플레이어의 ID를 포함하여 현재 세션에 대한 정보를 포함하는 NetworkRunner에 엑세스하는데 사용됩니다.
using Fusion;
using UnityEngine;
public class PlayerSpawner : SimulationBehaviour, IPlayerJoined
{
public GameObject PlayerPrefab;
public void PlayerJoined(PlayerRef player)
{
if (player == Runner.LocalPlayer)
{
Runner.Spawn(PlayerPrefab, new Vector3(0, 1, 0), Quaternion.identity);
}
}
}
FixedUpdateNetwork
Fusion2에서는 움직임과 같은 모든 Tick을 업데이트하는 코드가 Unity MonoBehaviour의 Update / FixedUpdate에서 실행되면 안됩니다. 대신 FixedUpdateNetwork를 사용해서 업데이트해야 합니다. FixedUpdateNetwork를 통해 모든 클라이언트에서 움직임이 보간되어 부드러운 모습을 보여줄 수 있습니다.
using Fusion;
using UnityEngine;
public class PlayerMovement : NetworkBehaviour
{
private CharacterController _controller;
public float PlayerSpeed = 2f;
private void Awake()
{
_controller = GetComponent<CharacterController>();
}
public override void FixedUpdateNetwork()
{
// Only move own player and not every other player. Each player controls its own player object.
if (HasStateAuthority == false)
{
return;
}
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
_controller.Move(move);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
}
}
MonoBehaviour대신에 NetworkBehaviour를 상속하게 됩니다.
HasStateAuthority를 사용하여 클라이언트 본인의 것만 제어할 수 있게 만들 수 있습니다.
FixedUpdateNetwork에서 코드 실행 시 반드시 Runner.DeltaTime을 사용해야 합니다!
카메라 제어
플레이어를 따라다니는 카메라를 추가 할 때 로컬 플레이어 객체를 따라다니는 카메라가 필요합니다. 이를 구현하는 두 가지 일반적인 방법은 다음과 같습니다.
- 카메라 인스턴스화
- 로컬 플레이어 객체가 생성될 때, 새로운 카메라를 인스턴스화하여 플레이어를 따라다니도록 설정합니다.
- 카메라 부착
- 카메라를 씬의 일부로 미리 배치해 둡니다. 로컬 플레이어 객체가 생성되면, 해당 카메라를 플레이어 객체에 부착하여 따라다니도록 설정합니다.
Spawned
NetworkObjects를 초기화할 때는 반드시 MonoBehaviour의 Awake/Start대신 NetworkBehaviour의 Spanwed를 사용해야 합니다.
Spawned함수는 모든 클라이언트에서 호출됩니다.
[Networked] 속성
네트워크를 통해 동기화하려는 변수에는 [Networked] 속성을 사용해야 합니다.
[Networked] 속성은 StateAuthority(상태 권한을 가진 클라이언트)에서 다른 모든 클라이언트로 상태를 동기화합니다.
StateAuthority는 특정 네트워크 객체의 상태를 관리하고 이를 다른 클라이언트에 동기화하는 권한을 가진 클라이언트입니다.
StateAuthority가 아닌 클라이언트가 네트워크화된 속성을 변경하는 경우, 이 변경은 네트워크를 통해 동기화되지 않으며 로컬 예측으로만 적용됩니다.
예시를 들어 설명하자면 두 명의 플레이어 A와 B가 있다고 하고, 플레이어 A가 StateAuthority를 가지고 있으며 'Health'라는 [Networked]된 속성이 있다고 가정하겠습니다.
플레이어 A는 Health에 대한 권한이 있으므로 수정 시 다른 네트워크 클라이언트에게 동기화할 수 있습니다. 하지만 플레이어 B가 플레이어 A의 'Health'를 수정하게 되면 플레이어 B에서만 로컬에서 임시로 변경되고 다른 클라이언트들에게는 동기화 되지 않습니다.
ChangedDetecion
각 클라이언트가 [Networked]된 속성의 변화를 감지할 때 Fusion에서는 ChangeDetection을 사용합니다.
예를 들어 [Networked]된 Color가 변경될 때 OnChangedRender 속성을 사용할 수 있습니다. OnChangedRender 속성을 사용하기 위해서는 [Networked]된 Color가 변경될 때마다 실행될 함수를 만들어야 합니다.
using Fusion;
using UnityEngine;
public class PlayerColor : NetworkBehaviour
{
public MeshRenderer MeshRenderer;
[Networked, OnChangedRender(nameof(ColorChanged))]
public Color NetworkedColor { get; set; }
void Update()
{
if (HasStateAuthority && Input.GetKeyDown(KeyCode.E))
{
// Changing the material color here directly does not work since this code is only executed on the client pressing the button and not on every client.
NetworkedColor = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), 1f);
}
}
void ColorChanged()
{
MeshRenderer.material.color = NetworkedColor;
}
}
RPC
RPC(원격 프러시저 호출, Remote Procedure Call)는 클라이언트가 다른 클라이언트 또는 서버에서 특정 코드를 실행할 수 있게 해주는 기능입니다.
RPC를 사용하는 이유는 네트워크화된 게임에서 안전하고 일관되게 상태를 변경하기 위함입니다. 클라이언트가 다른 클라이언트의 [Networked] 변수를 직접 수정하지 않고 RPC를 사용하는 이유(위에서도 설명한거와 같이)는 권한 없는 클라이언트가 직접 [Networked] 변수를 수정하면 네트워크 동기화가 제대로 이루어지지 않고 일관성 문제가 발생할 수 있기 때문입니다.
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void DealDamageRpc(float damage)
{
NetworkedHealth -= damage;
}
옵션 |
설명 |
RpcSources | |
RpcSources.All | 모든 클라이언트가 이 RPC를 호출할 수 있습니다. |
RpcSources.InputAuthority | InputAuthority를 가진 클라이언트만 이 RPC를 호출할 수 있습니다. |
RpcSources.StateAuthority | StateAuthority를 가진 클라이언트만 이 RPC를 호출할 수 있습니다. |
RpcTargets | |
RpcTargets.All | 모든 클라이언트에서 이 RPC가 실행됩니다. |
RpcTargets.InputAuthority | InputAuthority를 가진 클라이언트에서 이 RPC가 실행됩니다. |
RpcTargets.StateAuthority | StateAuthority를 가진 클라이언트에서 이 RPC가 실행됩니다. |
RpcTargets.Proxies | StateAuthority와 InputAuthority를 제외한 모든 클라이언트에서 이 RPC가 실행됩니다. |