RPC(원격 프로시저 호출)
LayerMask
Unity 백엔드 템플릿
https://github.com/Unity-Technologies/com.unity.services.samples.use-cases?tab=readme-ov-file
모든 일이 동시에 생겼을때, 각각의 객체에 생긴 일을 서버(HOST)로 모두 보내고,
클라이언트는 받아들이기만 해라 모든 일은 서버가 한다.
[ServerRpc]를 붙이면 서버에서 실행 된다.
함수 이름을 검사해서 함수 명이 ServerRpc로 끝나야 한다.
[ClientRpc]도 Server와 같은 형식을 가진다.
//PlayerNetwork.cs
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using Unity.Netcode;
using Unity.VisualScripting;
using Unity.Collections;
using System.Security.Cryptography;
public class PlayerNetwork : NetworkBehaviour
{
//// 모든 대상자가 읽을 수 있는 권한, 오너만 작성할 수 있는 권한
//NetworkVariable<int> randomNumber = new NetworkVariable<int>(1, NetworkVariableReadPermission.Everyone,
// NetworkVariableWritePermission.Owner);
struct SomeData : INetworkSerializable
{
public bool _bool;
public int _int;
public FixedString64Bytes _string;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref _bool);
serializer.SerializeValue(ref _int);
serializer.SerializeValue(ref _string);
}
}
public override void OnNetworkSpawn()
{
randomNumber.OnValueChanged += HandleRandomNumber;
}
public override void OnNetworkDespawn()
{
randomNumber.OnValueChanged -= HandleRandomNumber;
}
void HandleRandomNumber(SomeData oldValue, SomeData newValue)
{
Debug.Log("Owner id : " + OwnerClientId + ", random number : " + randomNumber.Value._int + ", " + randomNumber.Value._string);
}
NetworkVariable<SomeData> randomNumber = new NetworkVariable<SomeData>(new SomeData
{
_bool = true,
_int = 0
},
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Owner);
// Update is called once per frame
void Update()
{
Debug.Log("owner Id : " + OwnerClientId + "random number : " + randomNumber.Value._int);
// // 해당 오브젝트의 Owner가 아니면 작동하지 않는다.
if (!IsOwner)
{
return;
}
if(Input.GetKeyDown(KeyCode.Z))
{
TestServerRpc();
}
if(Input.GetKeyDown(KeyCode.T))
{
randomNumber.Value = new SomeData { _int = Random.Range(0, 100), _string = "32바이트 안넘게" };
//randomNumber.Value = Random.Range(0, 100);
//randomNumber.Value = new SomeData;
}
Vector3 moveDir = Vector3.zero;
moveDir.x = Input.GetAxis("Horizontal");
moveDir.z = Input.GetAxis("Vertical");
float moveSpeed = 3;
transform.Translate(moveDir * moveSpeed * Time.deltaTime);
}
[ServerRpc]
void TestServerRpc()
{
Debug.Log("Server RPC called");
}
}

//PlayerNetwork.cs
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using Unity.Netcode;
using Unity.VisualScripting;
using Unity.Collections;
using System.Security.Cryptography;
public class PlayerNetwork : NetworkBehaviour
{
//// 모든 대상자가 읽을 수 있는 권한, 오너만 작성할 수 있는 권한
//NetworkVariable<int> randomNumber = new NetworkVariable<int>(1, NetworkVariableReadPermission.Everyone,
// NetworkVariableWritePermission.Owner);
struct SomeData : INetworkSerializable
{
public bool _bool;
public int _int;
public FixedString64Bytes _string;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref _bool);
serializer.SerializeValue(ref _int);
serializer.SerializeValue(ref _string);
}
}
public override void OnNetworkSpawn()
{
randomNumber.OnValueChanged += HandleRandomNumber;
}
public override void OnNetworkDespawn()
{
randomNumber.OnValueChanged -= HandleRandomNumber;
}
void HandleRandomNumber(SomeData oldValue, SomeData newValue)
{
Debug.Log("Owner id : " + OwnerClientId + ", random number : " + randomNumber.Value._int + ", " + randomNumber.Value._string);
}
NetworkVariable<SomeData> randomNumber = new NetworkVariable<SomeData>(new SomeData
{
_bool = true,
_int = 0
},
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Owner);
// Update is called once per frame
void Update()
{
//Debug.Log("owner Id : " + OwnerClientId + "random number : " + randomNumber.Value._int);
Debug.Log("is server " + IsServer + " is client " + IsClient);
// // 해당 오브젝트의 Owner가 아니면 작동하지 않는다.
if (!IsOwner)
{
return;
}
if(Input.GetKeyDown(KeyCode.Z))
{
//TestServerRpc();
TestClientRpc();
}
if(Input.GetKeyDown(KeyCode.T))
{
randomNumber.Value = new SomeData { _int = Random.Range(0, 100), _string = "32바이트 안넘게" };
//randomNumber.Value = Random.Range(0, 100);
//randomNumber.Value = new SomeData;
}
Vector3 moveDir = Vector3.zero;
moveDir.x = Input.GetAxis("Horizontal");
moveDir.z = Input.GetAxis("Vertical");
float moveSpeed = 3;
transform.Translate(moveDir * moveSpeed * Time.deltaTime);
}
// Server Rpc
[ServerRpc]
void TestServerRpc()
{
Debug.Log("Server RPC called");
}
// 클라이언트 Rpc
[ClientRpc]
void TestClientRpc()
{
Debug.Log("Client RPC called");
}
}
게임에서 Z 키를 누르면 HOST, Client에 둘다 Client RPC가 호출된다.

if (Input.GetKeyDown(KeyCode.Z))
{
TestServerRpc("string 가능");
//TestClientRpc();
}
>[ServerRpc]
void TestServerRpc(string str)
{
Debug.Log("Server RPC called" +str);
}
지속적으로 변하는 상태를 동기화 -> NetworkVariable
- 상태 데이터의 동기화 ( 캐릭터 HP와 같은 것 )
- 자주 변경되는 값 ( 키입력이 계속 바뀌는 ex.캐릭터의 현재 위치(Vector3) )
- 지속적 상태 공유 ( 아이템 획득 여부, 타이머 등 )
특정 이벤트나 함수 호출 -> RPC
- 이벤트 처리 (특정 스킬)
- 클라이언트-서버 명령
( 클라이언트가 서버로 이동 명령을 보내거나, 서버가 클라이언트들에게 "게임 시작"을 알리는 경우.)
- 비동기 작업 (즉각적인 동기화가 필요 없는 일회성 작업.)
if (Input.GetKeyDown(KeyCode.Z)) { TestServerRpc(new ServerRpcParams()); //TestClientRpc(); } [ServerRpc] void TestServerRpc(ServerRpcParams rpcParams) { Debug.Log($"Server RPC called {rpcParams.Receive.SenderClientId}"); }클라이언트 관점의 파라미터
if (Input.GetKeyDown(KeyCode.Z)) { //TestServerRpc(new ServerRpcParams()); TestClientRpc(new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new List<ulong> { 1 } // 1번 클라이언트만 받거라. } }); } [ClientRpc] void TestClientRpc(ClientRpcParams rpcParams) { Debug.Log($"Client RPC called"); }서버 관점의 파라미터
[SerializeField] GameObject spawnedObjectPrefab;
...
if (Input.GetKeyDown(KeyCode.X))
{
GameObject go = Instantiate(spawnedObjectPrefab);
go.GetComponent<NetworkObject>().Spawn(true); // Spawn의 권한은 서버에 있다.
}
...
Client에서 실행시 오류가 생성됨.

GameObject spawnedObject;
if (Input.GetKeyDown(KeyCode.X))
{
spawnedObject = Instantiate(spawnedObjectPrefab);
spawnedObject.GetComponent<NetworkObject>().Spawn(true);
}
if(Input.GetKeyDown(KeyCode.C))
{
Destroy(spawnedObject);
}
if(Input.GetKeyDown(KeyCode.C))
{
spawnedObject.GetComponent<NetworkObject>().Despawn();
//Destroy(spawnedObject);
}
캡슐대신 ThirdPerson Assets을 사용해서 캐릭터를 변경
namespace StarterAssets
{
...
public class ThirdPersonController : NetworkBehaviour
{
...
public override void OnNetworkSpawn()
{
if (!IsOwner)
{
GetComponent<PlayerInput>().enabled = false;
}
}
...
private void Update()
{
//if (!IsOwner) { return; }
_hasAnimator = TryGetComponent(out _animator);
JumpAndGravity();
GroundedCheck();
Move();
}
...
}
}

https://mentum.tistory.com/724
네이버 블로그를 보면서 이해를 했다. 뭔가 적고 하기에는 좀 애매해서 링크로 첨부한다.