// - CancellationTokenSource(CTS) 클래스
// - UnityObject와 달리 자동 해제가 되지 않음
// - 작업 취소(Cancel)와 메모리 해제(Dispose)를 반드시 수동으로 호출해야 함
private CancellationTokenSource _source = new();
private void Start()
{
SomeAsyncMethod().Forget();
}
private void OnDestroy()
{
// 1) CTS에 취소 신호 전송
// _source.Token을 사용하는 모든 UniTask가 즉시 중단됨
_source.Cancel();
// 2) CTS 메모리 해제
// 직접 만든 CTS는 Unity가 자동으로 정리해주지 않기 때문에 Dispose 필수
_source.Dispose();
}
private async UniTaskVoid SomeAsyncMethod()
{
// (A) 직접 생성한 Token을 사용하는 Delay
// - 개발자가 원하는 타이밍에 강제로 취소 가능
// - OnDestroy에서도 Cancel 호출 필요
await UniTask.Delay(3000, cancellationToken: _source.Token);
// (B) UnityObject의 생명주기와 연결된 자동 취소 Token
// - 대상 MonoBehaviour가 Destroy되면 자동으로 취소
// - Dispose 필요 없음 (Unity가 관리)
// - "파괴되면 중단"이라는 Unity 특성과 정확히 맞는 패턴
await UniTask.Delay(3000, cancellationToken: this.GetCancellationTokenOnDestroy());
}
6. UniTask Tracker (작업 추적하기)
UniTask는 실행 중인 비동기 작업을 볼 수 있는 UniTask Tracker 창을 제공합니다.
어떤 async 작업이 현재 실행 중인지
어디에서 대기 중인지
메모리 누수가 있는지
를 실시간으로 확인할 수 있어 디버깅에 매우 유용합니다. Coroutine에는 없는 기능입니다.
Photon Fusion은 Unity용 고성능 네트워크 라이브러리로, 상태 동기화(State Sync), 스냅샷(Snapshot) 보간, 클라이언트 예측(Client-Side Prediction) 등을 지원합니다. Dedicated Server, Host, Shared Mode 등 다양한 네트워크 구조를 선택할 수 있으며, FPS나 TPS처럼 실시간성이 중요한 게임에 적합한 구조를 갖추고 있습니다.
1-1. Tick-Based Simulation
기존 동기화 방식은 시간 단위(초, 프레임 등)를 기준으로 하지만, 기기마다 시간 처리 속도가 달라 오차가 생기기 쉬웠습니다. Fusion은 이를 보완하기 위해 Tick(틱) 개념을 도입했습니다.
Tick은 초 단위를 일정한 간격으로 나눈 네트워크의 기준 단위입니다. 예를 들어 1초에 60틱이면 서버는 16.67ms마다 상태를 계산하고, 그 결과를 모든 클라이언트에 전송해 정밀한 동기화를 유지합니다.
1-2. 상태 권한 & 입력 권한
State Authority : 게임 상태를 변경할 수 있는 권한 (보통 서버나 호스트가 가짐)
Input Authority : 입력을 제공하는 권한 (클라이언트가 가짐)
클라이언트는 입력을 전송하고, 서버(또는 호스트)는 해당 입력을 연산해 새로운 상태를 생성한 후 모든 클라이언트에 전달합니다. 이 구조 덕분에 일관성과 보안성이 향상됩니다.
1-3 Client Side Prediction & Server Reconciliation
서버, 클라이언트 모드에서 클라이언트는 서버에게 입력만 제공할 수 있을 뿐
다른 물체를 컨트롤 할 수 없습니다. 서버가 입력을 받아 새로운 상태를 반환할 때까지 기다려야 합니다.
클라이언트가 입력을 보낸 뒤 서버의 응답을 기다리면, 입력 반응이 늦어져 조작감이 떨어질 수 있습니다. 이를 해결하기 위해 Fusion은 클라이언트 예측(Client Prediction) 을 사용합니다.
클라이언트는 자신의 입력을 즉시 반영해 다음 틱을 미리 시뮬레이션하고, 이후 서버에서 정확한 상태가 도착하면 그 위에 결과를 덮어 수정하는 과정을 서버 재조정(Server Reconciliation) 이라 합니다. 이 방식으로 실시간 반응성과 정확도를 모두 확보할 수 있습니다.
2. 주요 토폴로지 및 모드
Server Mode: 전용 서버를 두는 구조로 안정성과 보안성이 높지만, 서버 비용이 발생합니다.
Host Mode: 한 플레이어가 호스트 역할(서버 역할)을 맡고 나머지가 클라이언트로 접속하는 구조입니다. 서버 유지비용을 줄일 수 있음. 비용이 적고 간단하지만 해킹에 취약합니다.
Shared Mode: 여러 클라이언트가 상태 권한을 공유하는 구조로, Fusion 2.x에서 안정성이 개선되었습니다.
3. 핵심 API 및 클래스 구조
클래스 / 속성
역할 / 설명
NetworkRunner
네트워크 세션(Host, Server, Client)을 생성·관리하는 Fusion의 핵심. StartGame(), Shutdown(), JoinSessionLobby() 등 API로 서버/클라이언트 세션 관리.
NetworkObject
네트워크 상에 존재하는 모든 오브젝트(플레이어, 총알, 문 등)의 기본 단위. Runner.Spawn(), Runner.Despawn() 으로 생성·제거.
NetworkBehaviour
Fusion용 스크립트 베이스 클래스 (FixedUpdateNetwork(), [Networked] 속성 지원)
[Networked]
변수 앞에 붙여서 자동 동기화 (state replication). → public [Networked] int health { get; set; } 처럼 선언.
Spawn / Despawn
Runner.Spawn(prefab, position, rotation, owner) 으로 네트워크 오브젝트 생성. Runner.Despawn(obj) 로 제거.
FixedUpdateNetwork()
Fusion Tick 마다 호출되는 네트워크 업데이트 메서드. → 물리, 입력 처리, 예측 로직 작성.
Render()
클라이언트 프레임 렌더링 시 호출. → 보간(interpolation), 비주얼 업데이트용.
NetworkRunner.StartGame()
게임 세션 시작. 매개변수로 GameMode (Host, Server, Client, Shared) 선택.
GameMode (enum)
Fusion의 실행 모드. Host, Server, Client, Shared 중 하나를 선택.
NetworkInput / GetInput() / SetInput()
플레이어 입력을 서버로 전달하는 구조. Runner.SetInput() → 서버 → GetInput(out T input) 순으로 전달됨.
NetworkInputData (커스텀 struct)
INetworkInput 구현 구조체로 입력 데이터를 담는 컨테이너. 예: Vector2 moveDir; bool jumpPressed;
NetworkTransform
Transform 의 Position/Rotation/Scale 을 네트워크 상에서 자동 동기화.
NetworkCharacterControllerPrototype
FPS / TPS용 네트워크 캐릭터 컨트롤러. 중력, 점프, 이동 로직 내장, 서버 예측 지원.
NetworkRigidbody
Rigidbody 물리 기반 객체의 위치·회전 동기화. 물리 시뮬레이션을 서버 기준으로 관리.
원격 절차 호출 기능. [Rpc(RpcSources.InputAuthority, RpcTargets.All)] 형태로 사용.
NetworkSceneInfo
씬 동기화 시 로드 정보 전달용 구조체.
NetworkStatistics / NetworkObjectStatistics
런타임 패킷, Ping, 패킷 드랍률 등 모니터링 데이터 제공.
4. Fusion의 기본 동기화 방식
Fusion 네트워크 동기화의 기초 설정 A. NetworkRunner
NetworkRunner는 Fusion의 핵심이자, 네트워크 연결의 중심입니다. 게임 내에서 반드시 하나만 존재해야 하며, 여러 개가 동시에 존재하면 충돌이 발생할 수 있습니다.
NetworkRunner는 기본적으로 DontDestroyOnLoad 상태로 유지되므로, 씬 전환 시 중복 생성에 주의해야 합니다.
B. NetworkRunnerCallbacks Fusion은 네트워크 이벤트(플레이어 입장, 퇴장, 연결 종료 등)가 발생할 때 자동으로 콜백을 호출해 처리할 수 있도록 합니다. 이 콜백들은 INetworkRunnerCallbacks 인터페이스를 통해 구현하며, NetworkRunner와 같은 오브젝트에 스크립트를 추가하면 자동으로 연결됩니다.
using UnityEngine;
using UnityEngine.SceneManagement;
using Fusion;
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { }
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { }
public void OnInput(NetworkRunner runner, NetworkInput input) { }
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { }
public void OnConnectedToServer(NetworkRunner runner) { }
public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason) { }
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { }
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { }
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { }
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) { }
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { }
public void OnSceneLoadDone(NetworkRunner runner) { }
public void OnSceneLoadStart(NetworkRunner runner) { }
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player){ }
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player){ }
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data){ }
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress){ }
}
이 인터페이스는 네트워크 세션의 거의 모든 이벤트를 감지할 수 있으며,
필요한 이벤트만 골라서 구현해도 무방합니다.
C. Session을 이용한 플레이어 매칭(Match Making) Fusion에서 플레이어는 하나의 Session(세션) 에 배정되어 게임을 진행합니다. NetworkRunner의 StartGame() 메서드를 사용해 세션을 만들거나 참가할 수 있습니다.
Single : 오프라인 싱글 플레이 Session Name: 로비 이름 Scene: 이전의 Scene, build Index를 통해 대입 SceneManager: 네트워크 게임 씬을 담당하는 Scene Manager, 보통은 Fusion의 디폴트 값인 NetworkSceneManagerDefault를 사용하면 되며, 특수한 요구가 필요할 경우 사용자 정의형으로 편집 후 대입 가능
NetworkRunner.ProvideInput = true 를 설정해야 클라이언트의 입력이 서버로 전송됩니다. 또한 StartGame()은 비동기(async) 메서드이므로, 이후의 네트워크 관련 동작은 반드시 await 이후 실행되어야 합니다.
D. NetworkObject
네트워크에서 동기화할 오브젝트는 반드시 NetworkObject 컴포넌트를 포함해야 합니다. 이 컴포넌트가 있어야 Fusion이 해당 오브젝트의 상태를 추적하고 동기화합니다.
E. Spawn과 Despawn
네트워크에서 오브젝트를 생성하거나 제거할 때는 Runner.Spawn()과 Runner.Despawn()을 사용해야 합니다. Unity의 Instantiate()나 Destroy()를 사용하면 동기화되지 않습니다.
Spawn()의 마지막 인수 playerRef는 해당 오브젝트의 소유자(PlayerRef)를 지정합니다.
Fusion 중 네트워크 상태 동기화의 3가지 방식
A. Input을 사용한 전송방식
Fusion의 기본 구조는 클라이언트(Input Authority)가 입력을 보내고, 서버(State Authority)가 이를 계산해 상태를 갱신한 뒤 모든 클라이언트에 다시 전달하는 형태입니다.
입력 데이터는 INetworkInput 인터페이스를 구현한 구조체로 정의해야 합니다.
public struct NetworkInputData : INetworkInput
{
public Vector3 movementInput;
}
입력 수집은 OnInput() 콜백에서 처리합니다.
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetKey(KeyCode.W))
data.movementInput += Vector3.forward;
if (Input.GetKey(KeyCode.S))
data.movementInput += Vector3.back;
if (Input.GetKey(KeyCode.A))
data.movementInput += Vector3.left;
if (Input.GetKey(KeyCode.D))
data.movementInput += Vector3.right;
input.Set(data);
}
이후 NetworkBehaviour를 상속받은 클래스의 FixedUpdateNetwork() 안에서 입력 데이터를 받아 사용합니다. 이 메서드는 Unity의 FixedUpdate()와 비슷하지만, Fusion의 Tick 단위로 실행된다는 점이 다릅니다.
B. [Networked] 변수 동기화
만약 우리가 하나의 변수 데이터를 네트워크상에 동기화 하고 싶다면, [Networked] 속성을 사용하면 됩니다.
[Networked]
public int Hp { get; set; }
Fusion 규칙상 [Networked] 프로퍼티는 반드시 빈 getter/setter 형태로 선언해야 하며, 값이 변경될 때 실행되는 콜백을 지정할 수 있습니다
[Networked(OnChanged = nameof(OnHpChanged))]
public int Hp { get; set; }
private static void OnHpChanged(Changed<PlayerController> changed)
{
changed.Behaviour.hpBar.fillAmount =
(float)changed.Behaviour.Hp / changed.Behaviour.maxHp;
}
OnChanged 메서드는 반드시 정적(static)메서드를 사용해야 하며, Changed<T> 매개변수를 사용해야 합니다.
Fusion 2는 OnChanged 메서드가 제거되고 OnChangedRender 또는 GetChangeDetector 방식을 사용합니다.
OnChangedRender 방식
[Networked, OnChangedRender(nameof(OnHpChanged_Render))]
public int Hp { get; set; }
private void OnHpChanged_Render()
{
// 값이 변경될 때 자동 호출 (렌더 단계)
hpBar.fillAmount = (float)Hp / maxHp;
}
OnChangedRender 속성은 변경 감지 기반 처리를 위한 가장 쉬운 방법 입니다.
Networked 프로퍼티가 변경될 때 Fusion이 자동으로 콜백을 호출합니다. 렌더 단계(Render stage)에서 실행되므로 UI 업데이트, HP바, 애니메이션 등 시각적 변경 처리에 적합합니다. 단, 스폰 직후 설정되는 초기값에는 콜백이 호출되지 않을 수 있으므로, 초기화는 Spawned() 안에서 해주는 것이 안전합니다.
GetChangeDetector 방식
private ChangeDetector _changeDetector;
public override void Spawned()
{
// Networked 프로퍼티 변경 감지를 위한 ChangeDetector 초기화
_changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);
}
public override void Render()
{
foreach (var change in _changeDetector.DetectChanges(this))
{
switch (change)
{
case nameof(Hp):
hpBar.fillAmount = (float)Hp / maxHp;
break;
case nameof(Score):
scoreText.text = Score.ToString();
break;
}
}
}
이 방식은 렌더나 시뮬레이션 단계에서 직접 변경 여부를 검사하는 수동 감지 방식입니다. DetectChanges()를 호출하면 변경된 프로퍼티 목록을 얻을 수 있으며
여러 Networked 값(HP, Score 등)을 한꺼번에 감시할 수 있습니다..
이전 값은 단순히 마지막으로 감지기가 호출되었을 때의 상태를 기준으로 비교되기 때문에,
변화는 FixedUpdateNetwork()의 틱 경계뿐만 아니라 Render() 또는 Update()와 같은
중간 렌더링 프레임에서도 감지할 수 있습니다. 이로 인해 게임 로직 처리나 여러 상태의 동시 관리에 특히 유용합니다.
이 방식으로 체력, 점수 등 상태 값을 실시간으로 자동 동기화할 수 있습니다.
C. RPC (Remote Procedure Call)
RPC는 원격 프로시저 호출로 말 그대로 원격으로 함수를 실행할 때 사용합니다. 메서드 위에 [Rpc] 속성을 붙이고, 송신자(RpcSources)와 수신자(RpcTargets)를 지정합니다.
for (int i = 0; i < anim.textInfo.characterCount; i++)
{
if (!anim.textInfo.characterInfo[i].isVisible) continue;
anim.DOOffsetChar(i, new Vector3(0, 30, 0), 1f);
} // 글자들이 위에서 떨어지는 애니메이션