유니티 Fusion2 with Shared Mode #시작하기

Fusion2란?

Fusion2는 Photon의 차세대 네트워크 엔진으로, 서버 모드, 호스트 모드, 공유 모드 등 다양한 네트워크 토폴로지를 지원합니다. 


모드 특징 사용 사례
서버 모드 전용 서버, 공용 IP, 높은 안정성, 보안성 대규모 멀티플레이어 온라인 게임
호스트 모드 호스트 플레이어, 간편한 설정 소규모 협동 게임
공유 모드 클라우드 룸, StateAuthority 다양한 디바이스와 네트워크 환경 지원

 

Fusion2의 주요 기능으로는 네트워크 객체 동기화, 입력 동기화, 네트워크 이벤트 처리, 유연한 Room 관리 등이 있습니다.

 


Fusion2 시작하기

 

Fusion 2 1 - 시작하기 | Photon Engine

Fusion 공유 모드 기초는 Fusion 프로젝트를 시작하는 데 필요한 초기 단계를 설명합니다. 유니티와 C#에 대한 전반적인 이해가 필요합니다. Fusion은 여러 네트워크 토폴로지를 지원합니다. 서버 모

doc.photonengine.com

 

유니티 프로젝트 세팅 및 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을 사용해야 합니다!


카메라 제어

플레이어를 따라다니는 카메라를 추가 할 때 로컬 플레이어 객체를 따라다니는 카메라가 필요합니다. 이를 구현하는 두 가지 일반적인 방법은 다음과 같습니다.

  1. 카메라 인스턴스화
    • 로컬 플레이어 객체가 생성될 때, 새로운 카메라를 인스턴스화하여 플레이어를 따라다니도록 설정합니다.
  2. 카메라 부착
    • 카메라를 씬의 일부로 미리 배치해 둡니다. 로컬 플레이어 객체가 생성되면, 해당 카메라를 플레이어 객체에 부착하여 따라다니도록 설정합니다.

 


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가 실행됩니다.