유니티 초대 기능 심화: 확장 가능하고 견고한 시스템 설계
게임의 규모가 커지고 다양한 플랫폼을 지원하게 될 경우, 단순히 네트워크 함수를 호출하는 방식으로는 한계에 부딪히게 됩니다. 웹상에 흔히 볼 수 있는 코드 스니펫을 넘어, 데이터와 로직, UI를 분리하는 견고한 시스템 설계에 대해 깊이 있게 설명해 드리겠습니다.
이 방식은 초대를 단순한 ‘함수 호출’이 아닌, 상태를 가진 객체로 다루고, 이벤트 기반 통신을 통해 각 모듈의 결합도를 낮추는 데 중점을 둡니다.
1. 핵심 설계 원칙: 책임의 분리와 추상화
초대 기능을 구현하기 위해 세 가지 핵심 모듈을 분리합니다.
- 초대 서비스 (InvitationService): 초대의 생성, 상태(대기, 수락, 거절) 관리, 네트워크 통신 등 모든 핵심 로직을 담당합니다.
- 플랫폼 어댑터 (IPlatformInviter): Steam, Photon, 자체 서버 등 실제 통신이 이루어지는 플랫폼별 코드를 추상화합니다.
InvitationService
는 이 인터페이스만 바라보므로, 플랫폼이 바뀌어도 코드를 수정할 필요가 없습니다. - 초대 UI (InvitationUI):
InvitationService
에서 발생하는 이벤트를 수신하고, 이를 시각적으로 사용자에게 보여주는 역할만 담당합니다. 핵심 로직과 완전히 분리되어 있어 UI 디자인이 변경되어도 로직에 영향을 주지 않습니다.
2. 핵심 컴포넌트 구현: 이벤트와 데이터 기반 아키텍처
2.1. 초대의 상태를 담는 데이터 모델
초대를 단순한 string
이 아닌, 상태를 담는 객체로 정의합니다.
C#
public enum InvitationStatus
{
Pending,
Accepted,
Rejected,
Expired
}
[System.Serializable]
public class Invitation
{
public string invitationId;
public string inviterId;
public string invitedPlayerId;
public InvitationStatus status;
}
2.2. 초대 로직을 관장하는 핵심 서비스
InvitationService
는 MonoBehaviour
를 상속받는 싱글톤 패턴으로 구현하여 게임 어디서든 접근 가능하게 만듭니다. 이 클래스는 초대 보내기
, 초대 수락하기
등의 핵심 로직을 담당합니다.
C#
using System.Collections.Generic;
using UnityEngine;
public class InvitationService : MonoBehaviour
{
// 싱글톤 인스턴스
public static InvitationService Instance { get; private set; }
// 플랫폼별 초대 기능을 추상화한 인터페이스
private IPlatformInviter _platformInviter;
// 현재 처리 중인 초대 목록
private Dictionary<string, Invitation> _activeInvites = new Dictionary<string, Invitation>();
public delegate void OnInviteReceivedHandler(Invitation invite);
public event OnInviteReceivedHandler OnInviteReceived;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
// 실제 플랫폼 구현체를 할당 (예시: SteamInviter, PhotonInviter 등)
_platformInviter = new CustomGameInviter();
}
public void SendInvite(string invitedPlayerId)
{
// 초대 ID 생성
string inviteId = System.Guid.NewGuid().ToString();
string myPlayerId = "LocalPlayerID"; // 로컬 플레이어 ID
Invitation newInvite = new Invitation
{
invitationId = inviteId,
inviterId = myPlayerId,
invitedPlayerId = invitedPlayerId,
status = InvitationStatus.Pending
};
_activeInvites[inviteId] = newInvite;
// 플랫폼 어댑터를 통해 실제 네트워크로 초대 발송
_platformInviter.SendInvite(newInvite);
}
public void AcceptInvite(string inviteId)
{
if (_activeInvites.ContainsKey(inviteId))
{
_activeInvites[inviteId].status = InvitationStatus.Accepted;
// 플랫폼 어댑터를 통해 초대 수락 응답 발송
_platformInviter.AcceptInvite(_activeInvites[inviteId]);
}
}
// 외부에서 초대를 수신했을 때 호출되는 함수
public void ReceiveInviteFromPlatform(Invitation invite)
{
_activeInvites[invite.invitationId] = invite;
// UI 모듈에 알림을 보내기 위해 이벤트 발생
OnInviteReceived?.Invoke(invite);
}
}
2.3. 플랫폼별 통신을 추상화하는 인터페이스
InvitationService
는 이 인터페이스만 바라봅니다.
C#
public interface IPlatformInviter
{
void SendInvite(Invitation invite);
void AcceptInvite(Invitation invite);
// ... 기타 플랫폼별 함수
}
// 예시 구현: 커스텀 게임 서버와 통신하는 방식
public class CustomGameInviter : IPlatformInviter
{
public void SendInvite(Invitation invite)
{
Debug.Log($"커스텀 서버로 {invite.invitedPlayerId}에게 초대 발송.");
// 여기에 실제 커스텀 백엔드 통신 로직 작성
}
public void AcceptInvite(Invitation invite)
{
Debug.Log($"커스텀 서버로 {invite.inviterId}의 초대 수락.");
// 여기에 실제 백엔드 통신 로직 작성
}
}
3. UI와 로직의 분리: 이벤트 기반의 UI 업데이트
InvitationUI
스크립트는 InvitationService
의 이벤트를 구독하여 화면을 갱신합니다. 이로써 UI 코드는 InvitationService
가 어떻게 작동하는지 전혀 알 필요가 없습니다.
C#
using UnityEngine;
public class InvitationUI : MonoBehaviour
{
public GameObject invitationPopupPrefab; // 초대 알림 UI 프리팹
void Start()
{
if (InvitationService.Instance != null)
{
// 초대 수신 이벤트를 구독
InvitationService.Instance.OnInviteReceived += OnInviteReceivedHandler;
}
}
private void OnInviteReceivedHandler(Invitation invite)
{
// 초대 알림 팝업 생성
GameObject popup = Instantiate(invitationPopupPrefab, transform);
popup.GetComponent<InvitationPopup>().Setup(invite); // 팝업 UI 설정
}
}
이 아키텍처의 장점
이러한 설계 방식은 다음과 같은 장점을 제공합니다.
- 유지보수 용이성: 새로운 플랫폼(예: Xbox)을 추가하고 싶다면,
IPlatformInviter
를 구현하는 새로운 클래스만 만들어InvitationService
에 연결하면 됩니다. - 높은 확장성: 초대에 ‘파티 인원’, ‘게임 모드’ 같은 추가 정보를 넣고 싶다면
Invitation
클래스만 수정하면 됩니다. 모든 모듈이 유기적으로 연동됩니다. - 견고한 코드: 각 모듈이 명확한 책임만 가지므로 버그 발생 시 문제의 원인을 파악하기 쉽고, 테스트 코드를 작성하기 용이합니다.
이러한 접근 방식은 단순히 기능을 구현하는 것을 넘어, 게임 시스템을 설계하는 단계부터 미래를 대비하는 개발자의 자세를 보여줍니다.