NGO가 어떤식으로 작동하는지 예제를 만들어보며 학습해보ㅈr
넷코드는 많은 엔지니어가 데이터 동기화나 지연 보상 등 네트워크 게임플레이의 특정 측면을 더 쉽게 구축할 수 있도록 특별히 고안된 프레임워크를 지칭할 때 사용하는 주요 용어입니다. 출처:유니티 블로그
만들고자하는 게임에 따라서 선택해야 하는 패키지가 다릅니다!
간략하게 설명하자면,
NEntities는 개발 난이도가 높다고 합니다. (나중에 기회가 된다면 포스팅 하겠습니다.)
패키지 매니저에서 아래의 패키지를 설치해주세요.

Multiplayer Play Mode와 Multiplayer for GameObjects만 설치하면 됩니다.
패키지를 전부 다 설치했다면, SampleScene에서 아래와 같이 셋팅해 주세요.

Unity Transport란? Protocol을 모른다면 여기로!
Unity Engine에서 지원하는 모든 플랫폼은 UDP 소켓이나 WebSocket을 통해 제공하는 연결 기반 추상화 계층( 내장 네트워크 드라이버 ) 덕분에 Unity Transport에서 완벽하게 지원됩니다. 둘 다 암호화 여부에 관계없이 설정할 수 있으며, 위의 블록 다이어그램에서 닫힌 자물쇠와 열린 자물쇠로 구현됩니다. 프로젝트에서 메모리 최적화의 이유로 커스텀 프로토콜을 구현해서 사용하는 것이 아니라면, Unity Transport를 사용하면 됩니다!

게임에서 보여질 Player을 만들어 보겠습니다.
NGO는 기본적으로 모든 생성과 삭제가 Server에서 이루어져야 합니다.
ClientHost의 경우, 내가 Client이면서 Server입니다.
그리고 Network를 사용하는 오브젝트는 Prefab으로 생성이 돼있어야 합니다.
아래의 순서를 따라주세요!

NetworkObject는 네트워크의 동기화에 사용되고, NetworkTransform는 Transform의 동기화에 사용됩니다. 
Transform의 Auhoirty는 스크립트의 제어권의 주체를 누구로 둘 지를 정합니다.
Server와 Owner로 2개의 옵션이 있습니다. Player같은 경우는 Owner로 두는 것이 좋겠죠.
이렇게 생성했다면, 해당 오브젝트는 Prefab으로 만들어 주고, 씬에서는 삭제하시면 됩니다.
그 다음, NetworkManager에 Default Player Prefab에 등록해 줍시다.
이렇게 하면, 생선된 세션에 입장을 할 때, 해당 Prefab이 자동으로 Spawn 됩니다.

이제 방을 만들고, 입장을 하는 기능을 구현하겠습니다.
놀랍게도, 아주 쉽게 세션을 생성하고 입장할 수 있습니다.
Netcode에서는, Server, Host, Client로 구분이 되어 있습니다.
Server는 Client와 Server가 아예 분리된 형태입니다.
Host는 Client와 Server가 결합된 상태로, Client이면서 Server의 기능도 하게 됩니다.
Client는 말 그대로 Client입니다.
Canvas를 생성해 주시고, NetworkMangerUI라는 이름으로 UI를 만들어 주세요.

Network Manager UI Script 코드는 아래와 같습니다.
NetworkManager 싱글톤 객체를 가져와 버튼 콜백 함수를 등록하는 간단한 코드입니다!
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;
public class NetworkMangerUI : MonoBehaviour
{
[SerializeField] private Button serverBnt;
[SerializeField] private Button hostBnt;
[SerializeField] private Button clientBnt;
private void Start()
{
serverBnt.onClick.AddListener(() => NetworkManager.Singleton.StartServer());
hostBnt.onClick.AddListener(() => NetworkManager.Singleton.StartHost());
clientBnt.onClick.AddListener(() => NetworkManager.Singleton.StartClient());
}
}
씬 화면은 아래와 같이 구성했습니다.

이제 테스트를 한번 해봅시다!
Window -> Multiplayer -> Multiplayer Play Mode를 눌러주세요.
아래의 이미지처럼 가상의 플레이어를 추가할 수 있습니다.

Player2에 체크를 해주시고, 실행을 한번 해봅시다.
아래 gif는 Host로 세션을 생성 및 참가를 해주고, 두번째 클라이언트로 Enter Client를 하는 모습입니다. Transform도 동기화를 해주고 있어서 Scene에서 좌표를 바꿔보면 동기화가 되는 모습을 볼 수 있습니다!


NetworkBehaviour는 NetworkIdentity 컴포넌트가 있는 오브젝트와 함께 작동하는 특별한 스크립트입니다. 이 스크립트는 Command, ClientRPC, SyncEvent 및 SyncVar 같은 HLAPI 함수를 수행할 수 있습니다.
Unity 네트워크 시스템의 서버 권한이 있는 시스템의 경우, NetworkIdentities가 있는 네트워크로 연결된 오브젝트는 NetworkServer.Spawn()을 사용하여 서버에서 “스폰”되어야 합니다. 그러면 오브젝트가 NetworkInstanceId를 할당 받고 서버에 연결된 클라이언트에서 생성됩니다.
변수 동기화 파트입니다!
Player Prefab에 NetworkPlayer 스크립트를 생성합니다.

using Unity.Netcode;
using UnityEngine;
public class NetworkPlayer : NetworkBehaviour
{
NetworkVariable<int> num = new(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Owner);
public override void OnNetworkSpawn()
{
Debug.Log($"isOwner? {IsOwner}, ClientID: {OwnerClientId}");
num.OnValueChanged += (int preValue, int newValue) =>
{
Debug.Log($"OwnerClientID: {OwnerClientId}, num {num.Value}");
};
}
private void Update()
{
if (!IsOwner)
return;
if(Input.GetKeyUp(KeyCode.A))
{
num.Value = Random.Range(0, 100);
}
}
}

변수 동기화 할 수 있는 타입
커스텀 구조체를 다음과 같이 만들고 동기화할 수 있습니다.
Rpc는 원격 프로시저 호출로 함수를 동기화 합니다.
대상을 지정할 수 있고 일시적이거나 지속적으로 동기화합니다.
Rpc를 할 함수는 반드시 이름이 Rpc로 끝나야 하고 [Rpc(SendTo.대상)] 어트리뷰트가 필요합니다.
SendTo
기본적으로 SendTo의 SpecifiedInParams을 제외하고 SendTo와 대상은 같습니다.
RpcParams의 매개변수로 받으며 이를 호출할 때 대상을 정해줍니다.
RpcTargetUse에는 Temp 임시적으로 한 번만 전송과 Persistent 지속적으로 전송하는 방법이 있습니다.
네트워크로 동기화할 모든 게임오브젝트는 반드시 NetworkObject를 거쳐야만 동기화가 일어납니다.
플레이어는 접속하자마자 바로 스폰이 되었으나 런타임중에 동적으로 생성하고 파괴하는 법을 알아보겠습니다.

Player와 비슷하게 빈 게임오브젝트 Bullet으로 이름을 변경하고, NetworkObject와 NetworkBullet 스크립트를 만들어 넣습니다. 자식으로는 Sphere를 만들고 크기를 0.5로 한 뒤 Bullet을 프리팹으로 만들고 씬에 있는 건 지웁니다. Bullet 스크립트는 아래와 같습니다.
//NetworkBullet.cs
using Unity.Netcode;
using UnityEngine;
public class NetworkBullet : NetworkBehaviour
{
public override async void OnNetworkSpawn()
{
if (!IsOwner) return;
await Awaitable.WaitForSecondsAsync(3);
DespawnObjectRpc(NetworkObject);
}
void Update()
{
transform.Translate(Vector3.up * Time.deltaTime * 10);
}
//서버에게 오브젝트 삭제 요청을 한다.
[Rpc(SendTo.Server)]
void DespawnObjectRpc(NetworkObjectReference target)
{
if (target.TryGet(out NetworkObject targetObject))
{
targetObject.Despawn();
}
}
}
구체인 오브젝트의 NetworkPlayer 스크립트를 아래와 같이 수정하면 됩니다.
그럼 A키를 누르면 이제 하늘을 향해 총알을 발사합니다.
// NetworkPlayer.cs
private void Update()
{
if (!IsOwner)
return;
if (Input.GetKeyUp(KeyCode.A))
{
SpawnObjectRpc(transform.position, Quaternion.identity);
}
}
/// <summary>
/// 인스펙터에는 bulletPrefab에 총알 프리팹을 넣습니다. Rpc는 서버에게 전송이 되며 Instantiate로 일반적인 게임오브젝트를 생성합니다. 그리고 NetworkObject 컴포넌트에서 생성과 함께 소유권을 자신의 클라이언트 아이디로 부여합니다.
/// </summary>
/// <param name="pos"></param>
/// <param name="rotation"></param>
[Rpc(SendTo.Server)]
void SpawnObjectRpc(Vector3 pos, Quaternion rotation)
{
Instantiate(bulletPrefab, pos, rotation)
.GetComponent<NetworkObject>().SpawnWithOwnership(OwnerClientId);
}

기본적으로 필요한 지식을 정리해 봤습니다!
이제 복잡한 로직 속에서 멀티 게임은 어떻게 작동하는지 틈틈이 공부한 것을 정리해 보겠습니다!
호오 엄청 기네요 성공하면 저 포르쉐 타이칸 한대 부탁드립니다