Addressable Asset System은 Unity에서 제공하는 에셋(Asset) 관리 시스템으로,

프로젝트 내의 리소스(프리팹, 사운드, 텍스처, UI 등)를 주소(Address) 를 통해 효율적으로 로드하고,

필요에 따라 비동기 로딩, 메모리 관리, 원격 패치 등을 수행할 수 있게 해줍니다.

특징 요약

기능  설명
주소 기반 로드(Address-based Loading) 기존 Resources.Load() 대신, 문자열 주소를 통해 에셋을 로드합니다.
비동기 로드 지원(Asynchronous Loading) AsyncOperationHandle을 통해 비동기 로딩/해제 가능 → 로딩 중 UI나 진행률 표시 구현에 유리합니다.
자동 번들 관리(Automatic AssetBundle Handling) Addressables이 내부적으로 AssetBundle을 자동 생성 및 관리하므로, 별도의 번들 빌드 과정을 직접 제어할 필요가 없습니다.
리모트 서버 패치 및 업데이트(Remote Patch) 빌드된 에셋 번들을 원격 서버(CDN, S3 등)에 업로드하여, 실행 중에도 최신 리소스로 업데이트 가능합니다.
효율적인 메모리 관리(Memory Optimization) 필요할 때만 에셋을 로드하고, 사용 후 Release()를 호출하여 메모리 점유를 최소화할 수 있습니다.

 

설치 및 기본 설정

어드레서블 사용을 위해서는 Package Manager에서 Addressable을 설치해야 합니다.

 

Addressable 지정

Addressable을 체크하면 기본적으로 저장 경로에 저장되며 

체크 후 표시되는 Address 필드에서 경로 또는 이름을 변경하여 쉽게 주소를 정의할 수 있습니다.

 

Addressables Groups에서 Addressable로 지정된 목록 확인이 가능합니다.

 

주요 메서드 정리

메서드  주요 기능 사용 예시 비고
InitializeAsync() Addressables 시스템 초기화 yield return Addressables.InitializeAsync(); 사용 전 1회 호출 필수
LoadAssetAsync(address) 주소 기반 에셋 비동기 로드 var h = Addressables.LoadAssetAsync<Sprite>("UI/Icon"); 완료 후 Addressables.Release(h)
InstantiateAsync(address, pos, rot) 프리팹 비동기 생성 var h = Addressables.InstantiateAsync("Enemy", pos, rot); 제거 시 Addressables.ReleaseInstance()
Release(handle) 로드된 에셋 메모리 해제 Addressables.Release(h); handle 재사용 불가
ReleaseInstance(obj) Instantiate된 프리팹 해제 Addressables.ReleaseInstance(obj); Destroy() 대신 사용
CheckForCatalogUpdates() 원격 카탈로그 업데이트 확인 var h = Addressables.CheckForCatalogUpdates(false); 패치 필요 여부 확인
UpdateCatalogs(list) 새 카탈로그 다운로드 및 갱신 var h = Addressables.UpdateCatalogs(list); 패치 시 필수 단계
GetDownloadSizeAsync(key) 다운로드 예상 용량 확인 long size = await Addressables.GetDownloadSizeAsync("patch_assets").Task; 미리 용량 표시 가능
DownloadDependenciesAsync(key) 특정 라벨/주소의 패치 파일 다운로드 var h = Addressables.DownloadDependenciesAsync("patch_assets"); 캐시에 저장되어 재다운로드 방지
ClearDependencyCacheAsync(key) 캐시된 번들 삭제 Addressables.ClearDependencyCacheAsync("patch_assets"); 패치 전 초기화용

Addressable Asset를 이용한 패치 다운로드

버킷 만들기

모든 퍼블릭 엑세스 차단 해제 후 버킷만들기를 합니다.

 

버킷 정책의 편집 버튼 클릭

버킷 ARN 복사 후 정책 생성기 클릭

Step 1: Select policy type → Type of Policy : S3 Bucket Policy 선택

Step 2

Principal: *

Actions: GetObject 선택

ARN에는 복사한 ARN 붙여놓은 뒤 Add Statement를 클릭합니다.

 

이후 Generate Policty를 복사하여 정책에 붙여넣기 합니다.

 

이후 Load Path에 필요한 객체 URL을 얻기 위해 아무 파일이나 업로드 한뒤

해당 파일을 클릭하여 객체 URL을 복사합니다.

어드레서블 그룹 창으로 이동합니다.

객체 url을 Remote.LoadPath에 붙여넣어 수정합니다.

 

 

Build & Load Paths Remote로 변경

Path Review에서 수정한 Path가 맞는지 확인합니다.

 

맞다면 기존에 객체 URL을 얻고자 올린 테스트 파일을 삭제합니다.

 

Play Mode Script는 Addressables을 실행할 때 어떤 데이터 소스를 사용할지 결정하는 옵션입니다.

 

Use Asset Database는 그냥 로컬에 있는 파일을 바로 불러옵니다

즉, Addressables 빌드 결과물(AssetBundle) 을 사용하지 않아요.

가장 빠르지만, S3나 원격 서버에서 다운로드하는 구조를 테스트할 수 없습니다.

 

Use Existing Build는 마지막으로 Build한 Addressables 결과물(AssetBundles)을 사용합니다.

즉, ServerData/Windows/ (또는 Android/iOS)에 빌드된 카탈로그, 해시, 번들 파일을 실제로 불러옵니다.

따라서 S3 RemoteLoadPath 설정이 되어 있다면, 실제 S3 서버에서 다운로드하는 패치 테스트가 가능합니다.

 

 

Build → New Build → Default Build Script를 선택합니다.

 

 

빌드가 완료되면 ServerData → StandaloneWindows64 폴더에 빌드 파일이 저장됩니다.

 

StandaloneWindows64 폴더째로 버킷에 업로드 합니다.

Unity에서 비동기 작업을 구현하는 방식은 크게 Coroutine, Task, UniTask 세 가지입니다.
겉보기에는 모두 “기다린다”, “비동기 실행한다”처럼 비슷해 보이지만,
작동 방식·성능·API 구조·사용 목적은 서로 완전히 다릅니다.

 

1. 세 기술의 기본 개념

Coroutine (코루틴)

Unity 엔진이 제공하는 프레임 기반 비동기 흐름 제어 방식입니다.
yield return 문법을 사용하며, 메인 스레드를 벗어나지 않습니다.

  • 애니메이션·쿨타임·타이머 같은 게임 흐름에 적합
  • 메모리 할당이 잦고(StartCoroutine을 사용) 예외 처리에 약함
  • 코루틴 함수 종료 후 값 반환 불가

Task

C#(.NET)의 스레드 기반 비동기 처리 시스템입니다.
멀티스레드를 통해 CPU 연산을 병렬로 처리할 수 있습니다.

  • 네트워크, 파일 처리, 무거운 CPU 작업에 적합
  • Unity 메인 스레드와 호환성이 낮음
  • Task.Delay는 게임 프레임과 무관하게 동작

UniTask

Unity 환경에 맞게 최적화된 경량 async/await 라이브러리입니다.
코루틴과 유사한 기능을 제공 및 단점을 해결하고, Task의 GC 문제와 Unity 호환성 문제를 개선했습니다.

  • 구조체 기반이라 메모리 할당이 거의 없음(GC-Free)
  • Unity 프레임·EndOfFrame·FixedUpdate 등 정확한 타이밍 await 가능
  • Addressables, DOTween, TextMeshPro 등 서드파티까지 await 지원
  • 값 반환 가능(UniTask<T>), 예외 처리 용이

2. 작동 방식(실행 모델) 비교

항목  Coroutine  Task  UniTask
실행 위치 Unity 메인 스레드 .NET 스레드 풀 Unity 메인 스레드
시간 관리 프레임 기반 OS 타이머 기반 프레임·실시간 모두 지원
엔진 이벤트 대응 강함 없음 매우 강함
멀티스레드 불가 가능 가능하나 주목적 아님

중요한 차이점

  • Coroutine/UniTask → Unity 게임 루프 기반
  • Task → CPU 중심 스레드 기반

따라서 게임 흐름에는 UniTask/Coroutine이 유리하고,
논리 연산·대규모 계산에는 Task가 유리합니다.

 

Coroutine vs Task vs UniTask 문법 비교

기능  Coroutine  Task  UniTask
초기 설정 기본 제공 using System.Threading.Tasks using Cysharp.Threading.Tasks
기다리기(1초) yield return new WaitForSeconds(1f); await Task.Delay(1000); await UniTask.Delay(1000);
다음 프레임까지 대기 yield return null; 불가능 await UniTask.Yield();
특정 프레임 수 대기 직접 구현 필요 불가능 await UniTask.DelayFrame(60);
FixedUpdate 타이밍 yield return new WaitForFixedUpdate(); 불가능 await UniTask.WaitForFixedUpdate();
EndOfFrame yield return new WaitForEndOfFrame(); 불가능 await UniTask.WaitForEndOfFrame();
조건 만족까지 대기 yield return new WaitUntil(()=>cond); 직접 루프 필요 await UniTask.WaitUntil(()=>cond);
값 반환 불가 가능(Task) 가능(UniTask)
취소(Cancellation) StopCoroutine 필요 Token 필요 UnityObject 자동 취소 지원
서드파티 Await 제한적 불가능 Addressables / DOTween / TMP 지원
예외 처리 내부 예외 포착 어려움 try-catch try-catch

유니데스크 실제 사용

1. 설치 후 준비

UniTask를 사용하려면 Window - PackageManager에 들어가, + 버튼을 클릭, Add packacge from git URL을 선택합니다.

이후, 아래 주소를 입력하여 설치하여야 UniTask가 사용이 가능해집니다.

https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask

 

UniTask를 사용하려면 스크립트 상단에 다음 네임스페이스를 추가하여야 합니다.

using Cysharp.Threading.Tasks;

 

2. 일정 시간 대기하기

Coroutine 방식

yield return new WaitForSeconds(1f);

 

UniTask 방식

await UniTask.Delay(1000);

또는 아래처럼 TimeSpan을 활용할 수도 있습니다

await UniTask.Delay(TimeSpan.FromSeconds(1));

UniTask.Delay는 실제 시간 기반이며, 프레임 기반이 필요하다면 아래처럼 사용할 수 있습니다.

await UniTask.DelayFrame(60);  // 정확히 60프레임 기다림

 

3. UniTaskVoid와 Forget()

UniTaskVoid란?

  • 반환값이 없는 fire-and-forget 형태
  • await가 불가능합니다
  • 예외가 상위로 전달되지 않습니다

Forget()

UniTaskVoid처럼 예외를 무시하고 실행만 하는 목적일 때 사용합니다.

private void Start()
{
    SomeAsyncMethod().Forget();
}

private async UniTaskVoid SomeAsyncMethod()
{
    await UniTask.Delay(1000);
}

 

또한, UniTaskVoid 반환 형식의 함수는

await를 붙여서 함수를 호출할 수 없습니다.

UniTaskVoid가 awaitable 속성이 아니기 때문입니다.

 

4. 조건 만족 후 실행하기 (WaitUntil)

Coroutine 방식

yield return new WaitUntil(() => curTime >= 3f);

UniTask 방식

await UniTask.WaitUntil(() => curTime >= 3f);

프레임마다 조건을 체크하며, 조건이 true가 되면 이후 코드를 실행합니다.

 

5. 비동기 작업 취소하기

오브젝트가 Destroy되었는데 비동기 코드가 계속 실행되면

  • NullReferenceException 발생
  • 메모리 누수
  • 예측 불가능한 동작이 발생할 수 있습니다.

UnityObject와 결합된 안전한 취소 Token 제공:

// - 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에는 없는 기능입니다.

Window → UniTask Tracker에서 확인 가능합니다.

https://www.photonengine.com/ko-kr/fusion

 

멀티플레이어 게임의 벤치마크!

Fusion기능의 스냅샷 Unity 전문가를 위한 최상급 상태 동기화 넷코드 (Netcode) SDK의 핵심 정보를 확인해 보세요. 확인해 보세요

www.photonengine.com

 

1. 개요 & 특징

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 물리 기반 객체의 위치·회전 동기화. 물리 시뮬레이션을 서버 기준으로 관리.
Runner.IsServer / IsClient / IsSharedMode / IsForwarding 현재 인스턴스가 어떤 모드에서 동작 중인지 확인.
Runner.LocalPlayer 현재 로컬 플레이어의 PlayerRef (플레이어 참조 ID).
PlayerRef Fusion이 각 플레이어에게 부여하는 고유 식별자. → 입력·소유권 관리 시 자주 사용.
Object.HasStateAuthority / HasInputAuthority 현재 오브젝트의 권한(서버/클라이언트)을 판단. StateAuthority = 상태 관리 권한, InputAuthority = 입력 전송 권한.
NetworkRunner.Spawned / Despawned Callbacks 오브젝트가 네트워크에 스폰되거나 사라질 때 자동 호출.
NetworkPrefabRef 네트워크 스폰 가능 프리팹 참조 타입. ScriptableObject처럼 Prefab 대신 안전하게 직렬화.
SimulationBehaviour NetworkBehaviour와 달리 씬 전역에 존재하면서 시뮬레이션 단계별 콜백 수신.
Runner.LagCompensation 히트스캔(총알) 등의 지연 보정 시스템 제공. Runner.LagCompensation.Raycast() 로 과거 위치 기반 충돌 검사.
NetworkSceneManager Fusion이 씬 전환을 관리하도록 도와주는 헬퍼. 멀티 씬 환경 지원.
NetworkEvents (OnPlayerJoined, OnPlayerLeft) 플레이어 입장/퇴장 콜백. INetworkRunnerCallbacks 인터페이스로 구현.
INetworkRunnerCallbacks 네트워크 이벤트를 수신하기 위한 인터페이스. → OnConnectedToServer, OnDisconnectedFromServer, OnInput(), OnPlayerJoined() 등 포함.
Runner.ProvideInput = true 이 Runner가 입력을 서버로 전달하도록 활성화.
NetworkPrefabTable 프로젝트의 네트워크 프리팹 레지스트리. Fusion이 Spawn 시 참조.
Runner.SessionInfo / SessionListUpdated() 세션(방) 정보 관리, 매치메이킹 및 로비 UI 연동용.
TickTimer 네트워크 틱 기반 타이머 도우미. (로컬 시간 대신 서버 틱으로 정확히 동작)
Runner.DeltaTime / Runner.Tick / Runner.SimulationTime Fusion의 시간 관리 값. Frame 시간이 아닌 네트워크 틱 단위로 활용.
Rpc / [Rpc] 원격 절차 호출 기능. [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() 메서드를 사용해 세션을 만들거나 참가할 수 있습니다.

private NetworkRunner runner;
    
    private async void Start()
    {
        await StartMultiPlayer();
    }

    private async Task StartMultiPlayer()
    {
        runner = gameObject.AddComponent<NetworkRunner>();
        runner.ProvideInput = true;
        runner.AddCallbacks(this);

        var sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();

        await runner.StartGame(new StartGameArgs()
        {
            GameMode = GameMode.AutoHostOrClient, // 첫 번째 플레이어: Host / 이후: Client
            SessionName = "MultiPlayerRoom",
            SceneManager = sceneManager            
        });
    }

GameMode 종류

  • Server : 전용 서버로 참여
  • Host : 플레이어이면서 서버 역할 수행
  • Client : 클라이언트 전용으로 참여
  • AutoHostOrClient : 첫 참가자는 Host, 이후는 Client
  • 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()를 사용하면 동기화되지 않습니다.

runner.Spawn(networkObjectPrefab, spawnPosition, spawnRotation, playerRef);
runner.Despawn(networkObject);

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)를 지정합니다.

[Rpc(RpcSources.InputAuthority, RpcTargets.All)]
public void SendMessage_RPC(string msg)
{
    messageTxt.text = msg;
}

RPC는 상태 저장이 필요 없는
메시지·이벤트·채팅 같은 기능에 적합합니다.
단, 지속적인 동기화가 필요한 데이터(위치, 체력 등)는
[Networked]나 Input 기반 방식으로 처리해야 합니다.

 

SendMessage RPC의 속성

옵션 설명
RpcSources  
RpcSources.InputAuthority 입력 권한을 가진 클라이언트만이 메시지를 보내기 위해 RPC를 트리거(호출) 할 수 있습니다.
RpcSources.StateAuthority 상태 권한을 가진 클라이언트만이 메시지를 보내기 위해 RPC를 트리거(호출) 할 수 있습니다.
RpcSources.All 모든 클라이언트가 이 RPC를 호출할 수 있습니다.
RpcTargets  
RpcTargets.InputAuthority 입력 권한을 가진 클라이언트에서 RPC를 실행합니다.
RpcTargets.StateAuthority 상태 권한을 가진 클라이언트에서 RPC를 실행합니다.
RpcTargets.All 모든 클라이언트에서 이 RPC가 실행됩니다.
RpcTargets.Proxies 입력 권한과 상태 권한을 제외한 모든 클라이언트에서 RPC를 실행합니다.

'Unity > 멀티플레이어' 카테고리의 다른 글

[Unity] 멀티플레이어 설계 이론  (2) 2025.06.25

https://dotween.demigiant.com/documentation.php

이글은 계속 업데이트 예정입니다. 공식 문서 기준

🎵 Audio

AudioSource

  • DOFade(float to, float duration)
audioSource.DOFade(0, 1f); // 1초 동안 볼륨을 0으로 줄임
  • DOPitch(float to, float duration)
audioSource.DOPitch(1.5f, 2f); // 피치를 2초 동안 1.5로 변경

AudioMixer

  • DOSetFloat(string param, float to, float duration)
audioMixer.DOSetFloat("Volume", -20f, 1f); // 오디오 믹서 파라미터를 변경

📷 Camera

  • DOFieldOfView(float to, float duration)
Camera.main.DOFieldOfView(30, 1f); // 1초 동안 카메라 줌인
  • DOShakePosition(float duration, float strength)
Camera.main.DOShakePosition(0.5f, 0.3f); // 카메라 흔들림 효과
  • DOColor(Color to, float duration)
Camera.main.DOColor(Color.black, 1f); // 카메라 배경색 전환

💡 Light

  • DOColor(Color to, float duration)
light.DOColor(Color.red, 1f); // 라이트 색상을 빨강으로 변경
  • DOIntensity(float to, float duration)
light.DOIntensity(5f, 2f); // 2초 동안 광원 밝기를 5로 변경

🎨 Material

  • DOColor(Color to, float duration)
renderer.material.DOColor(Color.green, 1f); // 머티리얼 색상을 초록으로
  • DOFade(float to, float duration)
renderer.material.DOFade(0.5f, 2f); // 머티리얼 투명도 조절
  • DOOffset(Vector2 to, float duration)
renderer.material.DOOffset(new Vector2(1, 1), 2f); // 텍스처 오프셋 변경

🔧 Rigidbody / Rigidbody2D

  • DOMove(Vector3 to, float duration)
rigidbody.DOMove(new Vector3(0, 5, 0), 1f); // 물리 기반 이동
  • DOJump(Vector3 endValue, float jumpPower, int numJumps, float duration)
rigidbody.DOJump(Vector3.up * 5, 2, 1, 1f); // 점프 궤적 이동
  • DORotate(Vector3 to, float duration)
rigidbody.DORotate(new Vector3(0, 90, 0), 1f); // 물리 회전

🔄 Transform

  • DOMove(Vector3 to, float duration)
transform.DOMove(new Vector3(0, 5, 0), 1f); // 위치 이동
  • DORotate(Vector3 to, float duration)
transform.DORotate(new Vector3(0, 180, 0), 2f); // 회전
  • DOScale(Vector3 to, float duration)
transform.DOScale(new Vector3(2, 2, 2), 1f); // 스케일 변경
  • DOShakePosition(float duration, float strength)
transform.DOShakePosition(1f, 0.5f); // 오브젝트 흔들림
  • DOPunchScale(Vector3 punch, float duration, int vibrato, float elasticity)
transform.DOPunchScale(Vector3.one * 0.2f, 0.5f, 5, 0.5f); // 스프링처럼 튀는 스케일 애니메이션

🖼️ Unity UI

CanvasGroup

  • DOFade(float to, float duration)
canvasGroup.DOFade(0, 1f); // UI 그룹 전체 투명화

Graphic/Image/Text

  • DOColor(Color to, float duration)
image.DOColor(Color.red, 1f); // UI 색상 변경
  • DOFade(float to, float duration)
text.DOFade(0, 1f); // 텍스트 투명화
  • DOText(string to, float duration)
text.DOText("Hello DOTween!", 2f); // 글자가 타이핑되듯 출력

🔤 TextMesh Pro

  • DOText(string to, float duration)
tmpText.DOText("DOTween TMP!", 2f); // TMP 텍스트 타이핑 효과
  • DOTweenTMPAnimator.DOScaleChar(int index, float value, float duration)
DOTweenTMPAnimator anim = new DOTweenTMPAnimator(tmpText); anim.DOScaleChar(0, 2f, 1f); // 첫 글자 확대
  • DOTweenTMPAnimator.DOOffsetChar(int index, Vector3 offset, float duration)
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);
        } // 글자들이 위에서 떨어지는 애니메이션

1. 현재 시간 가져오기

DateTime now = DateTime.Now;      // 현재 PC 시간
DateTime utc = DateTime.UtcNow;   // 현재 UTC (세계 표준시)
DateTime today = DateTime.Today;  // 오늘 자정 (00:00:00)

Debug.Log(now);    // 예: 2025-09-06 14:30:15
Debug.Log(utc);    // 예: 2025-09-06 05:30:15 (한국은 UTC+9)
Debug.Log(today);  // 예: 2025-09-06 00:00:00

 

2. 날짜/시간 구성 요소

DateTime now = DateTime.Now;

Debug.Log(now.Year);   // 2025
Debug.Log(now.Month);  // 9
Debug.Log(now.Day);    // 6
Debug.Log(now.Hour);   // 14
Debug.Log(now.Minute); // 30
Debug.Log(now.Second); // 15

 

3. 날짜/시간 연산

날짜 더하기/빼기

DateTime today = DateTime.Today;

DateTime tomorrow = today.AddDays(1);
DateTime nextHour = today.AddHours(1);
DateTime after30Min = today.AddMinutes(30);

Debug.Log(tomorrow);    // 2025-09-07 00:00:00
Debug.Log(nextHour);    // 2025-09-06 01:00:00
Debug.Log(after30Min);  // 2025-09-06 00:30:00

용도: 내일 자정 계산, 쿨타임 만료 시각 계산

 

4. 날짜 비교

DateTime resetTime = DateTime.Today.AddDays(1); // 내일 자정
DateTime now = DateTime.Now;

if (now < resetTime)
    Debug.Log("아직 초기화 전입니다.");
else
    Debug.Log("퀘스트 초기화 시간 도착!");

용도: 특정 시간 전에/후에 이벤트 실행 여부 판별

 

5. 두 시간 차이 (TimeSpan)

DateTime start = DateTime.Now;
DateTime end = start.AddHours(5).AddMinutes(30);

TimeSpan diff = end - start;

Debug.Log(diff.TotalHours);   // 5.5
Debug.Log(diff.TotalMinutes); // 330
Debug.Log($"{diff.Hours}시간 {diff.Minutes}분 {diff.Seconds}초");
// 5시간 30분 0초

용도: 다음 리셋까지 남은 시간, 플레이 시간 등 계산

 

6. 문자열 변환 (저장/불러오기)

날짜 → 문자열

DateTime now = DateTime.Now;

string s1 = now.ToString("yyyy-MM-dd HH:mm:ss");
string s2 = now.ToString("MM/dd/yyyy");
string s3 = now.ToString("dddd, MMM dd yyyy");

Debug.Log(s1); // 2025-09-06 14:45:30
Debug.Log(s2); // 09/06/2025
Debug.Log(s3); // Saturday, Sep 06 2025

 

문자열 → 날짜

string saved = "2025-09-06 12:00:00";
DateTime parsed = DateTime.Parse(saved);

Debug.Log(parsed); // 2025-09-06 12:00:00

활용 추가 예시 모음

1. 일일 퀘스트 자정 리셋

private void CheckDailyQuestReset()
{
    if (lastResetDate.Date < DateTime.Today)
    {
        ResetQuests();
        lastResetDate = DateTime.Today;
        Debug.Log("일일 퀘스트 초기화 완료!");
    }
}

활용: 매일 00시에 퀘스트 초기화

 

2. 남은 시간 표시 (HH:mm:ss)

// 내일 자정 (퀘스트 리셋 시각)
        DateTime nextReset = DateTime.Today.AddDays(1);

        // 남은 시간
        TimeSpan remaining = nextReset - DateTime.Now;

        // 음수 방지 (혹시라도 시간이 꼬일 경우)
        if (remaining.TotalSeconds < 0)
        {
            remaining = TimeSpan.Zero;
        }

        // 시:분:초 표시
        string timeLeft = $"{remaining.Hours:D2}:{remaining.Minutes:D2}:{remaining.Seconds:D2}";

        text.text = $"다음 퀘스트 초기화까지: {timeLeft}";

 

출력 예시:

리셋까지 남은 시간: 09:45:22

 

3. 출석 체크 (오늘 로그인 했는지 확인)

DateTime lastLogin = DateTime.Parse(PlayerPrefs.GetString("LastLogin", DateTime.MinValue.ToString()));

if (lastLogin.Date < DateTime.Today)
{
    Debug.Log("출석 보상 지급!");
    PlayerPrefs.SetString("LastLogin", DateTime.Now.ToString());
}
else
{
    Debug.Log("오늘은 이미 출석 체크 완료");
}
활용: 매일 첫 로그인 시 보상 지급
 

4. 쿨타임 체크 (스킬, 아이템 사용)

DateTime lastUsedTime;
TimeSpan cooldown = TimeSpan.FromSeconds(30);

public bool CanUseSkill()
{
    return DateTime.Now - lastUsedTime >= cooldown;
}

public void UseSkill()
{
    if (CanUseSkill())
    {
        Debug.Log("스킬 사용!");
        lastUsedTime = DateTime.Now;
    }
    else
    {
        TimeSpan remain = cooldown - (DateTime.Now - lastUsedTime);
        Debug.Log($"쿨타임 남음: {remain.Seconds}초");
    }
}

활용: RPG 스킬 쿨타임, 아이템 재사용 시간

 

5. 이벤트 기간 확인

DateTime eventStart = new DateTime(2025, 9, 1, 0, 0, 0);
DateTime eventEnd   = new DateTime(2025, 9, 10, 23, 59, 59);

DateTime now = DateTime.Now;

if (now >= eventStart && now <= eventEnd)
    Debug.Log("이벤트 참여 가능!");
else
    Debug.Log("이벤트 기간이 아닙니다.");

📌 활용: 특정 기간 한정 이벤트, 출석 이벤트 등

 

6. 다음 주 월요일 자정 계산

DateTime today = DateTime.Today;
int daysUntilMonday = ((int)DayOfWeek.Monday - (int)today.DayOfWeek + 7) % 7;
DateTime nextMonday = today.AddDays(daysUntilMonday);

Debug.Log($"다음 주 월요일: {nextMonday}");

📌 활용: 주간 퀘스트 리셋 (월요일 자정 초기화)

 

7. 플레이 시간 측정

DateTime startTime = DateTime.Now;

// ... 게임 플레이 중 ...

DateTime endTime = DateTime.Now;
TimeSpan playTime = endTime - startTime;

Debug.Log($"플레이 시간: {playTime.TotalMinutes:F1}분");

📌 활용: 세션별 플레이 타임, 누적 플레이 시간 기록

 

8. 남은 시간 자연어 출력

TimeSpan remaining = TimeSpan.FromMinutes(125); // 2시간 5분

string msg = "";
if (remaining.TotalHours >= 1)
    msg = $"{remaining.Hours}시간 {remaining.Minutes}분 남음";
else if (remaining.TotalMinutes >= 1)
    msg = $"{remaining.Minutes}분 남음";
else
    msg = $"{remaining.Seconds}초 남음";

Debug.Log(msg); // 2시간 5분 남음
 

📌 활용: 남은 시간 안내를 더 자연스럽게 보여주기

 

9. 다음 특정 시각까지 남은 시간

// 오늘 오후 6시 (18:00)
DateTime target = DateTime.Today.AddHours(18);
TimeSpan left = target - DateTime.Now;

if (left.TotalSeconds > 0)
    Debug.Log($"오늘 18시까지 {left.Hours}시간 {left.Minutes}분 남음");
else
    Debug.Log("이미 시간이 지났습니다.");

 

📌 활용: '오늘 오후 6시까지 접속 보상' 같은 조건

 

10. 날짜 간격 (D-Day 계산)

DateTime deadline = new DateTime(2025, 12, 31);
int daysLeft = (deadline - DateTime.Today).Days;

Debug.Log($"올해 남은 날: {daysLeft}일");

📌 활용: 이벤트 종료일, 프로젝트 마감일 카운트다운

 

+ Recent posts