Unity Version Used: 2021.3.13f1
목차
Unity Netcode for Gameobject
https://www.youtube.com/watch?v=3yuBOB3VrCk
Import Netcode package
Windows - Package Manager - Unity Registry - Netcode for GameObjects를 Install한다.
Create Platform
간단하게 바닥과 벽정도만 platform을 만들어주자
NetworkManager
Create Empty로 빈 오브젝트를 만들고 NetworkManager를 추가한다.
경고문(?)에 Multiplayer Tools가 필요하다고 한다.
PackageManager에서 설치한다.
Transport를 Unity Transport로 선택한다.
추가하면 아래 Unity Transport라는 스크립트가 자동으로 추가된다.
Player
Hierachy에서 빈 오브젝트를 만들어 NetworkObject를 추가한 뒤 Capsule를 자식 오브젝트로 추가한다.
Player 오브젝트를 Prefab으로 만들고 NetworkManager의 Player Prefab에 넣는다.
또한 NetworkPrefabs에도 Player Prefab을 추가한다.
테스트1
여기까지 하고 Play하면 아무것도 안 뜨는 것이 정상이다.
그 이유는 어떤 Connection이 있어어 Player Prefab을 생성하기 때문이다.
Hierachy에서 DontDetroyOnLoad에 있는 NetworkManager에서 Start Host를 누르면 Player가 생성된다.
NetworkMangerUI.cs
위에서 NetworkManager Script 내에서 했던 Start Host 등의 작업을 UI로 할 수 있도록 하자
먼저 Canvas를 만들고 아래와 같이 Empty Parent Object와 Button들을 추가한다.
다음은 Script를 작성한다.
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;
public class NetworkManagerUI : MonoBehaviour
{
[SerializeField] private Button serverBtn;
[SerializeField] private Button hostBtn;
[SerializeField] private Button clientBtn;
private void Awake()
{
serverBtn.onClick.AddListener(() =>
{
NetworkManager.Singleton.StartServer();
});
hostBtn.onClick.AddListener(() =>
{
NetworkManager.Singleton.StartHost();
});
clientBtn.onClick.AddListener(() =>
{
NetworkManager.Singleton.StartClient();
});
}
}
이 스크립트는 NetworkManagerUI에 추가한 뒤에 Button들을 각각 할당한다.
Build 후 테스트2
Build 후 실행해서 Editor에서 Host를, Build에서 Client를 하면 Player가 2개 생성된다.
영상에서는 Quantum Console이라는 에셋을 사용했지만 $40짜리 라서 사용할 수는 없을 것 같다.
PlayerNetwork.cs
간단하게 Player를 움직일 코드를 작성한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class PlayerNetwork : MonoBehaviour
{
[SerializeField] private float moveSpeed = 3f;
private void Update()
{
Vector3 moveDir = new Vector3(0f, 0f, 0f);
if (Input.GetKey(KeyCode.W)) moveDir.z = +1f;
if (Input.GetKey(KeyCode.S)) moveDir.z = -1f;
if (Input.GetKey(KeyCode.A)) moveDir.x = -1f;
if (Input.GetKey(KeyCode.D)) moveDir.x = +1f;
transform.position += moveDir * moveSpeed * Time.deltaTime;
}
}
Script를 Player Prefab에 추가한다.
테스트3
Build 후 실행해보면 Player가 움직이는 것을 확인할 수 있다.
그러나 Player 2개가 같이 움직인다.
각 Host 또는 Client가 어떤 Player Object를 생성했는지 확인하여 그것만 움직이게 해야한다.
IsOnwer Check
PlayerNetwork.cs 스크립트의 Update에 아래 내용을 추가한다.
if(!IsOwner) return;
이후 다시 실행해보면 자신의 Plyaer Object만 움직이는 것을 볼 수 있다.
그러나 각 Client에서 위치가 동기화되지 않고 있다
NetworkTransform
NetworkTransform Script는 Transform과 관련된 정보를 동기화해준다.
Player Prefab에 NetworkTransform을 추가하고 Position의 x,z만 동기화하도록 선택한다.
Build 후 실행해보면 Host에서는 정상적으로 동기화되어 움직이는 것을 볼 수 있다.
그러나 Client로 접속한 곳에서는 움직이지 않는다...
영상 설명에 의하면 Client는 Server에 대한 Ownership이 없다고 한다.
기본적으로 Client의 패킷을 신뢰하지 않는 것이다.
ClientNetworkTransform
아래 URL을 복사하여 Package Manager의 Add package from git URL로 붙여넣고 추가한다.
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git?path=/Packages/com.unity.multiplayer.samples.coop#main
그리고 Player Prefab에 있는 NetworkTransform을 지우고 ClientNetworkTransform을 추가한다.
Build 후 테스트해보면 Client의 Player도 Transform이 동기화된다.
NetworkVariable
NetworkVariable은 각 Client끼리 동기화되는 변수다.
PlayerNetwork에 아래 코드를 추가한다
private NetworkVariable<int> randomNumber = new NetworkVariable<int>(1);
...
Debug.Log(OwnerClientId + "; randomNumber: " + randomNumber.Value);
if(Input.GetKeyDown(KeyCode.T))
{
randomNumber.Value = Random.Range(0, 100);
}
T를 누르면 randomNumber를 바꾸고 Debug.Log로 출력한다.
Build 후 테스트해보면 아까와 마찬가지로 Host에서는 되지만 Client에서는 안된다.
영상에 나온 에러메세지는 Client는 NetworkVariable를 수정할 권한이 없다고 한다.
NetworkVariable의 Constructor의 argument에는 Permission 관련 argument가 있다.
randomNumber의 Constructor를 아래와 같이 수정한다.
private NetworkVariable<int> randomNumber = new NetworkVariable<int>(1, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
그러면 두 randomNumber 모두 바뀌는 것을 볼 수 있다.
Custom Data Type
C#에서 value type인 int, float, enum, bool, struct만 Network Variable이 가능하다고 한다.
class, object, array, string은 reference type이라 불가능하다.
아마 C#에서 type에 따라 참조 등이 다르게 정의되어있는 것 같다.
CustomData라는 struct 하나 만들고 randomNumber로 만들자
private NetworkVariable<CustomData> randomNumber = new NetworkVariable<CustomData>(new CustomData { _int = 56, _bool = true }, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
public struct CustomData
{
public int _int;
public bool _bool;
}
그리고 OnNetworkSpawn에서 OnValueChanged에 넣는다.
public override void OnNetworkSpawn()
{
randomNumber.OnValueChanged += (CustomData previousValue, CustomData newValue) =>
{
Debug.Log(OwnerClientId + "; " + newValue._int + " | " + newValue._bool);
};
}
Build 후 테스트해보면 Client가 접속할 때 이런 에러가 발생한다.
CustomData는 NetworkVariable이 지원하지 않는다고 한다.
이건 struct에 INetworkSerializable를 implement해서 해결할 수 있다.
public struct CustomData : INetworkSerializable
{
public int _int;
public bool _bool;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref _int);
serializer.SerializeValue(ref _bool);
}
}
CustomData에 INetworkSerializable을 implement한 뒤 Interface도 implement해준다.
FixedString
reference type인 string 대신 value type인 FixedString를 사용하면 CustomData struct에 string을 추가하여 Serialize할 수 있다.
public struct CustomData : INetworkSerializable
{
public int _int;
public bool _bool;
public FixedString128Bytes message;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref _int);
serializer.SerializeValue(ref _bool);
serializer.SerializeValue(ref message);
}
}
public override void OnNetworkSpawn()
{
randomNumber.OnValueChanged += (CustomData previousValue, CustomData newValue) =>
{
Debug.Log(OwnerClientId + "; " + newValue._int + " | " + newValue._bool + " | " + newValue.message);
};
}
ServerRPC
그렇다고 한다.
대충 Client에서도 Server의 함수를 실행할 수 있게 하는 것 같다.
간단한 ServerRpc 함수를 추가한다.
이때 함수 이름에 ServerRpc가 들어가야 한다.
[ServerRpc]
private void TestServerRpc()
{
Debug.Log("TestServerRpc" + OwnerClientId);
}
Client에서 T를 눌렀을 때 Debug.Log가 정상적으로 실행된다.
다음은 ServerRpcParams를 이용해 Sender의 ClientId를 확인할 수도 있다.
if (Input.GetKeyDown(KeyCode.T))
{
TestServerRpc(new ServerRpcParams());
}
[ServerRpc]
private void TestServerRpc(ServerRpcParams serverRpcParams)
{
Debug.Log("TestServerRpc " + OwnerClientId + ": " + serverRpcParams.Receive.SenderClientId);
}
Build에서 log를 확인하기 귀찮아서 정확하게 확인하지는 않았지만
ServerRpc의 Log는 Server, Host 쪽에서만 뜬다.
ClientRpc
이건 반대로 Sever에서 Client의 함수를 실행할 수 있는 것 같다.
마찬가지로 T를 누르면 실행되도록 해둔다.
[ClientRpc]
private void TestClientRpc()
{
Debug.Log("TestClientRpc");
}
ServerRpc와는 반대로 Client는 이 함수를 실행할 수 없다.
ServerRpc는 Server에서 실행할 수 없고...
다음은 특정 id를 가진 Client에게만 ClientRpc를 보내는 방법이다.
TestClientRpc(new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new List<ulong> { 1 } } });
private void TestClientRpc(ClientRpcParams clientRpcParams)
{
Debug.Log("TestClientRpc");
}
ClientId가 1인 Client에게만 Debug.Log("TestClientRpc")가 실행된다.
Spawning and Despawning NetworkObject
테스트할 Object를 적당히 만들고 Prefab으로 만든다.
만든 Prefab에는 NetworkObject를 추가한다.
또한 NetworkManager에 Prefab를 추가한다.
T를 누르면 저 Object를 생성하도록 한다.
Transform spawnedObjectTransform = Instantiate(spawnedObjectPrefab);
그러나 T를 눌렀을 때 local에는 잘 Spawn되었지만 다른 Client에는 Spawn되지 않았다.
아래 코드를 추가해서 해결한다.
spawnedObjectTransform.GetComponent<NetworkObject>().Spawn(true);
그러나 Client에서는 local에서만 Spawn되고 Server에는 Spawn이 되지 않는다.
이는 Spawn 함수를 ServerRpc로 만들면 된다고 한다.
Despawn도 마찬가지로 ServerRpc로 만들어야한다.
[ServerRpc]
private void SpawnSphereServerRpc()
{
spawnedObjectTransform = Instantiate(spawnedObjectPrefab);
spawnedObjectTransform.GetComponent<NetworkObject>().Spawn(true);
}
[ServerRpc]
private void DeSpawnSphereServerRpc()
{
spawnedObjectTransform.GetComponent<NetworkObject>().Despawn(true);
}
NetworkAnimator
영상에 나온 것과 같이 Animation도 그냥 동기화되지 않는다
NetworkAnimator를 추가하고 해당 Animator를 추가해야한다.
근데 또 이건 Host의 Animation은 되는데 Client의 Animation은 안된다...
OwnerNetworkAnimator.cs를 만들고 이걸 대신 넣어준다.
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode.Components;
using UnityEngine;
public class OwnerNetworkAnimator : NetworkAnimator
{
protected override bool OnIsServerAuthoritative()
{
return false;
}
}
IP로 연결
NetworkManager Object에 Unity Transport 수정
127.0.0.1은 localhost이다
같은 공유기, 인터넷 안이라면 192.168.0.x와 같은 내부 IP로도 접속이 가능하다.
'작업일지 > Unity' 카테고리의 다른 글
[Unity] Hexagon TileMap 만들기 (0) | 2023.05.10 |
---|---|
[Unity] New Input System 테스트 (0) | 2023.01.02 |
[Unity] Photon Fusion Multiplayer 구현 테스트 (0) | 2022.11.15 |
[Unity] Git, GitHub와 Sourcetree를 이용한 Unity Project Setup (0) | 2022.11.10 |
[Unity] Unity Editor 2021.x에서 URP로 전환하기 (2) | 2022.07.18 |