Home > Unity > Netcode > Unity Netcode 따라하기 Golden Path One

Unity Netcode 따라하기 Golden Path One
Unity Netcode Unet Networkk

Golden Path One

Golden Path One 학습 문서
본 게시글은 해당 문서를 학습하며 정리한 게시글 입니다.

Netcode 설치하기
해당 게시글에 Netcode 설치 및 프로젝트 생성까지 소개 한다.

Hello World 학습하기
해당 게시글에 선행 프로젝트인 Hello World 학습 및 구현 한다.

*앞선 게시글 부터 순차적으로 진행하는 로드맵입니다 선행 프로젝트를 먼저 학습해 주세요.

Player 움직이게 하기

지난 프로젝트에서는 생성된 오브젝트가 움직이지 않아 확인도 어렵고 재미도 없었던거 같다.
이번에는 생성한 Player를 움직이게 해보자.

지난번 만들었던 [Scripts] 폴더에 새로운 C# 파일을 생성한다.
이름은 HelloWorldPlayer로 하였다.

해당 코드는 아래와 같다.

기능은 무작위한 위치로 Palyer를 이동하거나 생성하는 코드다.
ServerClient, Host의 기능이 다르다.

HelloWorldPlayer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
namespace HelloWorld // HelloWorld 관련 코드들을 묶어줬다.
{
    public class HelloWorldPlayer : NetworkBehaviour // NetworkBehaviour는 MonoBehaviour를 상속받는 클래스다.
    {
        public NetworkVariable<Vector3> Position = new NetworkVariable<Vector3>();

        public override void OnNetworkSpawn() // NetworkObject가 생성된 후 호출 된다.
        {
            if (IsOwner) // 생성된 NetworkObject가 자신인지 아닌지 확인 후 다음 구문 실행
            {
                Move();
            }
        }

        public void Move()
        {
            if(NetworkManager.Singleton.IsServer) // 현재 실행중인 프로그램이 Server인지 확인
            {
                var randomPosition = GetRandomPositionOnPlane();
                transform.position = randomPosition;
                Position.Value = randomPosition;
            }
            else
            {
                SubmitPositionRequestServerRpc();
            }
        }

        [ServerRpc]
        void SubmitPositionRequestServerRpc(ServerRpcParams rpcParams = default) // Server로 값을 보내는 메서드
        {
            Position.Value = GetRandomPositionOnPlane();
        }

        static Vector3 GetRandomPositionOnPlane()
        {
            return new Vector3(Random.Range(-3f, 3f), 1f, Random.Range(-3f, 3f));
        }

        private void Update()
        {
            transform.position = Position.Value; // 언제 값이 바뀔지 몰라 Update를 해준다.
        }
    }

}

간단한 설명은 주석에 적어두었다. 이제 하나 하나 살펴 볼 차례이다.

OnNetworkSpawn() 메서드

OnNetworkSpawn()
public override void OnNetworkSpawn() // NetworkObject가 생성된 후 호출 된다.
{
    if (IsOwner) // 생성된 NetworkObject가 자신인지 아닌지 확인 후 다음 구문 실행
    {
       Move();
    }
}  

해당 메서드는 NetworkBehaviour에 선언된 메서드이다.
주석에 적혀있듯이 NetworkObject가 생성된 후 호출되는 메서드인데, 조건문이 있다.
해당 조건문은 생성된 NetworkObject가 해당 클라이언트에서 생성된 Object인지 확인해준다.
해당 Property 또한 NetworkBehaviour에 선언된 Property이다.

해당 조건문이 있는 이유는 새로운 NetworkObject생성될 때 마다 모든 클라이언트에서 OnNetworkSpawn()호출되기 때문이다.
조건문이 없었다면 N번째 생성된 Player는 N번 움직이게 될 것이다.(정말로 N번 움직이지는 않는다. Serverposition 값을 보내고 Update()문이 실행 되고 하는 사이에 간격의 차이로 한번만 움직인다.)
하지만 코드의 Move() 메서드가 N번 실행되는 것은 맞다.

내가 확인해본 방법은 생성한 Client ID 와 현재 Client ID 를 출력해 비교해 보는 방법이다.

Move();
Debug.Log($"생성된 객체가 내거야? : {IsOwner}, 생성한 ClientID : {OwnerClientId}, 현재 ClientID : {NetworkManager.LocalClientId}");

OnNetworkSpawn() 메서드의 기존 내용을 주석 처리 한 뒤 해당 내용을 넣어 Build해준다.
EditorBuild된 실행파일을 실행해 테스트 해보면 아래와 같은 결과가 나온다.

Host Log
Netcode_Log_Host
Client Log
Netcode_Log_Client

Debug.Log를 사용해 출력해 보면 두번째 NetworkObject가 생성될 때 호출된 클래스의 OnNetworkSpawn() 메서드가 각각 다른 것을 알 수 있다.

BuildDebug.Log를 확인하는 방법은 [C드라이브] - [Users] - [사용자 이름] - [AppData] - [LocalLow] - [설정한 CompanyName] 폴더에 있다.
(AppData 폴더는 숨김 폴더이므로 숨김폴더 해제 해야 한다.)

SubmitPositionRequestServerRpc() 메서드

Move() 메서드와 GetRandomPositionOnPlane() 메서드는 따로 설명할 필요를 못 느껴 건너뛰었다.

SubmitPositionRequestServerRpc()
[ServerRpc]
void SubmitPositionRequestServerRpc(ServerRpcParams rpcParams = default)
{
    Position.Value = GetRandomPositionOnPlane();
}        

이 메서드는 Netcode에서 지원하는 속성인 ServerRpc(Remote Prosedure Call)을 부여한 메서드다.
Client에서 Server정보를 보낼 수 있으며, Client에서만 호출이 가능하다.
Server에서만 호출이 가능한 ClientRpc 속성이 있다.
해당 속성을 사용하는 메서드는 반드시 이름의 마지막에 ServerRpc가 들어가거나, [ServerRpc] 속성을 할당해야 한다.

해당 메서드는 Server 또는 Host에 동일 객체에 있는 동일한 이름의 메서드를 호출한다.
즉, Client에서 해당 메서드를 호출하면
Server 또는 Host에 존재하는 호출한 NetworkObject의 메서드가 실행 된다.

프로그램 상태 설정하기(Server, Client, Host)

앞서 cmd로 계속해서 상태를 변경하느라 고생이었다..
이제 프로그램 시작 시 상태를 설정할 수 있게 기능을 추가해 보자.

지난번 만들었던 [Scripts] 폴더에 새로운 C# 파일을 생성한다.
이름은 HelloWorldManager로 하였다.

해당 코드는 아래와 같다.

기능은 앞서 말했듯이 Server, Client, Host로 상태를 설정해주는 기능과 Player가 생성된 후 Move() 메서드를 호출 할 수 있도록 UI를 추가해주는 기능이다.

HelloWorldManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
namespace HelloWorld
{
    public class HelloWorldManager : MonoBehaviour
    {
        private void OnGUI() // UI를 코드로 만들어 프로그램 실행 시 띄어준다.
        {
            GUILayout.BeginArea(new Rect(10, 10, 300, 300));
            if (!NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsServer) // 상태가 정해졌는지 조건문
            {
                StartButtons();
            }
            else
            {
                StatusLabels();

                SubmitNewPosition();
            }

            GUILayout.EndArea();
        }

        static void StartButtons()
        {
            if (GUILayout.Button("Host")) NetworkManager.Singleton.StartHost();
            if (GUILayout.Button("Server")) NetworkManager.Singleton.StartServer();
            if (GUILayout.Button("Client")) NetworkManager.Singleton.StartClient();
        }

        static void StatusLabels()
        {
            var mode = NetworkManager.Singleton.IsHost ?
                "Host" : NetworkManager.Singleton.IsServer ? "Server" : "Client";

            GUILayout.Label("Transport: " +
                NetworkManager.Singleton.NetworkConfig.NetworkTransport.GetType().Name);
            GUILayout.Label("Mode: " + mode);
        }

        static void SubmitNewPosition()
        {
            if(GUILayout.Button(NetworkManager.Singleton.IsServer ? "Move" : "Request Position Change"))
            {
                if(NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient)
                {
                    // 서버에 접속한 클라이언트의 Player 전부 Move()메서드를 호출한다.
                    foreach (ulong uid in NetworkManager.Singleton.ConnectedClientsIds) 
                        NetworkManager.Singleton.SpawnManager.GetPlayerNetworkObject(uid).GetComponent<HelloWorldPlayer>().Move();
                }
                else
                {
                    var playerObject = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject();
                    var player = playerObject.GetComponent<HelloWorldPlayer>();
                    player.Move();
                }
            }
        }
    }
}

현재는 잘 사용하지 않는 OnGUI()를 사용해서 구현한 Management 코드다.
UGUI로 구현해도 상관 없다. 편한 방식을 사용하면 된다.
해당 코드는 적혀 있는 주석만으로 추가 설명은 필요 없을 것 같다.

참고로 OnGUI()를 사용해 만든 UIScene View에서는 볼 수 없다.

모든 준비가 끝났다! 혹시나 중간에 테스트하려고 추가 또는 수정한 코드가 이상하지 않은지 다시 확인해보고 Build를 진행한다.

Netcode_Result