상태(state) : 플레이어의 체력 · 점수 · 닉네임 (계속 유지됨)
-> NetworkVariable, NetworkList
이벤트(event) : 공격 · 이펙트 · 채팅 메시지 (한 번만 발생하고 끝)
-> ServerRpc, ClientRpc
| 구분 | NetworkVariable | RPC |
|---|---|---|
| 성격 | 상태 (State) | 이벤트 (Event) |
| 값 유지 | 값이 계속 유지됨 | 호출 시점에만 실행 |
| 늦은 접속 | 나중에 접속해도 최신값 수신 | 이미 발생한 이벤트는 수신 불가 |
| 전송 빈도 | 값이 변경될 때만 자동 전송 | 호출할 때마다 전송 |
사용 예시
| 데이터 | 적합한 방식 | 이유 |
|---|---|---|
| 플레이어 HP | NetworkVariable | 늦은 접속자도 현재 HP 를 알아야 함 |
| 플레이어 닉네임 | NetworkVariable | 한 번 설정 후 유지되는 상태 |
| 공격 요청 | ServerRpc | 서버가 판정해야 함 (치팅 방지) |
| 피격 이펙트 | ClientRpc | 일회성 연출. 값 유지 불필요 |
| 채팅 메시지 | ServerRpc → ClientRpc | 서버 경유 후 전체 전달 |
| 매 프레임 위치 갱신 | NetworkTransform | NetworkVariable 도 RPC 도 아님. NetworkTransform |
using Unity.Netcode;
public class PlayerStats : NetworkBehaviour
{
// 체력 : 서버만 변경 가능 (기본값)
private NetworkVariable<int> _health = new NetworkVariable<int>(100);
// 닉네임 : 소유자가 직접 변경 가능
private NetworkVariable<FixedString64Bytes> _nickname = new NetworkVariable<FixedString64Bytes>(
default,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Owner);
}
권한 조합
| 읽기 | 쓰기 | 사용 사례 |
|---|---|---|
| Everyone | Server | 체력 · 점수 (권위 서버 패턴) |
| Everyone | Owner | 닉네임 (소유자가 자기 정보를 직접 설정) |
| Owner | Server | 비공개 정보 (다른 플레이어에게 숨기는 인벤토리) |
public override void OnNetworkSpawn()
{
_health.OnValueChanged += OnHealthChanged;
// Late Join 대응: 현재 값으로 UI 를 한 번 초기화
OnHealthChanged(0, _health.Value);
}
public override void OnNetworkDespawn()
{
_health.OnValueChanged -= OnHealthChanged;
}
private void OnHealthChanged(int previous, int current)
{
_healthBar.value = current;
}
| 사용 가능 | 사용 불가 |
|---|---|
| int, float, bool, ulong 등 기본 타입 | string (참조 타입) |
| Vector2, Vector3, Quaternion, Color | List, Dictionary<K,V> |
| enum | class 인스턴스 |
| FixedString32Bytes ~ FixedString4096Bytes | |
| 필드가 모두 unmanaged 인 struct |
FixedString 을 사용한다.Unity.Collections 가 제공하는 고정 크기 문자열 타입
이름의 숫자는 총 바이트 수, 헤더 2 바이트를 제외한 만큼을 실제로 사용 가능
한글은 UTF-8 기준 한 글자당 3 바이트
| 타입 | 총 바이트 | 사용 가능 바이트 | 한글 최대 길이 | 용도 |
|---|---|---|---|---|
| FixedString32Bytes | 32 | 29 | 9자 | 짧은 상태 태그 ("Ready", "Alive") |
| FixedString64Bytes | 64 | 61 | 20자 | 플레이어 닉네임 |
| FixedString128Bytes | 128 | 125 | 41자 | 짧은 채팅 메시지 |
| FixedString512Bytes | 512 | 509 | 169자 | 긴 메시지 · 설명 |
NetworkVariable 의 리스트 버전
요소의 추가 · 제거 · 변경을 네트워크로 자동 동기화하며, 변경 이벤트를 수신할 수 있음
| 항목 | NetworkVariable | NetworkList |
|---|---|---|
| 용도 | 단일 값 동기화 | 가변 길이 목록 동기화 |
| 이벤트 | OnValueChanged | OnListChanged |
| 쓰기 권한 | Server / Owner 선택 가능 | Server 전용 |
public class ScoreBoard : NetworkBehaviour
{
private NetworkList<int> _scores;
private void Awake()
{
_scores = new NetworkList<int>(); // 필드 선언이 아닌 Awake 에서
}
public override void OnNetworkSpawn()
{
_scores.OnListChanged += OnScoresChanged;
}
public override void OnNetworkDespawn()
{
_scores.OnListChanged -= OnScoresChanged;
}
private void OnScoresChanged(NetworkListEvent<int> ev)
{
switch (ev.Type)
{
case NetworkListEvent<int>.EventType.Add: /* 추가 처리 */ break;
case NetworkListEvent<int>.EventType.Remove: /* 제거 처리 */ break;
case NetworkListEvent<int>.EventType.Value: /* 값 변경 처리 */ break;
case NetworkListEvent<int>.EventType.Clear: /* 전체 초기화 처리 */ break;
}
}
}
NetworkList 의 변경 작업(Add · Remove · Clear 등) 은 서버에서만 가능. 클라이언트에서 변경이 필요하면 ServerRpc 로 서버에 요청
| 규칙 | 설명 |
|---|---|
| ServerRpc 접미사 | 메서드 이름이 반드시 ServerRpc 로 끝나야 함 |
| ClientRpc 접미사 | 메서드 이름이 반드시 ClientRpc 로 끝나야 함 |
[ServerRpc] / [ClientRpc] 어트리뷰트 | 대응하는 어트리뷰트가 필수 |
| NetworkBehaviour 상속 필수 | RPC 는 NetworkBehaviour 를 상속한 클래스에서만 사용 가능 |
[ServerRpc]는 기본적으로 소유자만 호출할 수 있음.RequireOwnership = false 를 지정// 소유자만 호출 가능 (기본값)
[ServerRpc]
private void FireServerRpc() { }
// 누구든 호출 가능
[ServerRpc(RequireOwnership = false)]
private void SendChatServerRpc(string message) { }
ServerRpcParams 를 추가하면 호출한 클라이언트의 ID 를 알 수 있음.[ServerRpc(RequireOwnership = false)]
private void RequestKickServerRpc(ulong targetId, ServerRpcParams rpcParams = default)
{
// 서버는 항상 rpcParams.Receive.SenderClientId 로 발신자를 식별해야 함 (치팅 방지)
ulong senderId = rpcParams.Receive.SenderClientId;
if (!IsAdmin(senderId))
{
Debug.LogWarning($"비관리자가 강퇴 시도: {senderId}");
return;
}
NetworkManager.Singleton.DisconnectClient(targetId);
}
// 서버에서 이 메서드를 호출하면 모든 클라이언트가 똑같은 로그를 출력
[ClientRpc]
public void BroadcastMessageClientRpc(string message)
{
Debug.Log($"[전체 공지] {message}");
}
if (IsClient && !IsServer) 로 가드ClientRpcParams.Send.TargetClientIds를 채우면 일부 클라이언트에게만 전달할 수 있음.public void SendToClient(string message, ulong targetClientId)
{
if (!IsServer) return; // 서버에서만 호출 가능
ClientRpcParams rpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = new ulong[] { targetClientId }
}
};
SendPrivateMessageClientRpc(message, rpcParams);
}
[ClientRpc]
private void SendPrivateMessageClientRpc(string message, ClientRpcParams clientRpcParams = default)
{
Debug.Log($"[개인 메시지] {message}");
}
public class Player : NetworkBehaviour
{
// HP 는 NetworkVariable 로 상태 유지
private NetworkVariable<int> _hp = new NetworkVariable<int>(100);
// 서버 = 계산 + 검증 + 상태 결정(권한) + 동기화 책임
[ServerRpc]
private void AttackServerRpc()
{
// 서버에서 판정
_hp.Value -= 10; // 상태 변경 → 자동 동기화
PlayHitEffectClientRpc(); // 이벤트 → 일회성 연출
}
// 클라 = 입력(요청) + 표현
[ClientRpc]
private void PlayHitEffectClientRpc()
{
// 이펙트 재생
}
}
using UnityEngine;
using Unity.Netcode;
public class RpcPractice : NetworkBehaviour
{
[SerializeField] private Renderer _renderer;
private int _score;
public override void OnNetworkSpawn()
{
_renderer = GetComponentInChildren<Renderer>();
}
private void Update()
{
if (!IsOwner) return;
// E 키: 서버에 점수 추가 요청 (ServerRpc)
if (Input.GetKeyDown(KeyCode.E))
{
RequestAddScoreServerRpc(10);
}
// Q 키: 서버에 색상 변경 요청 (ServerRpc → ClientRpc 조합)
if (Input.GetKeyDown(KeyCode.Q))
{
float r = Random.Range(0f, 1f);
float g = Random.Range(0f, 1f);
float b = Random.Range(0f, 1f);
RequestChangeColorServerRpc(r, g, b);
}
}
// --- ServerRpc ---
// 점수 추가 요청 (클라이언트 → 서버)
[ServerRpc]
private void RequestAddScoreServerRpc(int amount)
{
// 서버에서 점수 처리
_score += amount;
Debug.Log($"[Server] {OwnerClientId} 번 플레이어 점수: {_score}");
// 모든 클라이언트에게 결과 알림
NotifyScoreUpdatedClientRpc(OwnerClientId, _score);
}
// 색상 변경 요청 (클라이언트 → 서버)
[ServerRpc]
private void RequestChangeColorServerRpc(float r, float g, float b)
{
// 서버가 검증 후 모든 클라이언트에 색상 변경 지시
ApplyColorClientRpc(r, g, b);
}
// --- ClientRPC ---
// 점수 갱신 알림 (서버 → 모든 클라이언트)
[ClientRpc]
private void NotifyScoreUpdatedClientRpc(ulong clientId, int newScore)
{
Debug.Log($"[Client] {clientId} 번 플레이어 점수가 {newScore} 으로 변경되었습니다.");
}
// 색상 적용 (서버 → 모든 클라이언트)
[ClientRpc]
private void ApplyColorClientRpc(float r, float g, float b)
{
if (_renderer == null) return;
_renderer.material.color = new Color(r, g, b);
}
}