이번 겨울 방학에 유니티 게임 프로젝트를 진행하게 되었다. 1, 2주차에는 각자 개인 스터디를 진행하였고 3주차부터 본격적인 개발을 시작하였다. 나는 대기실 및 캐릭터 작업 중 룸의 준비 기능과 게임 시작 기능 구현을 맡았다.
유니티는 이번 프로젝트를 통해 처음 사용하게 되어 부족한 지식은 여러 블로그를 참조하며 개발하였다. 네트워크 솔루션으로는 Photon PUN2를 사용하였다.
아래는 개발하며 개인적으로 찾아본 내용이다.
공부하며 참고한 사이트이다.
https://memmaeranger.tistory.com/4
https://crazy-lazy.tistory.com/79
https://velog.io/@ttangu5510/Unity-PUN2
https://velog.io/@yangju058/Photon-3.-%EB%A3%B8-%EC%83%9D%EC%84%B1-%EC%8B%9C%EC%8A%A4%ED%85%9C
넷코드 vs 포톤
| 기능 | Netcode (책) | Photon (PUN2) |
|---|---|---|
| 네임스페이스 | using Unity.Netcode; | using Photon.Pun; using Photon.Realtime; |
| 상속 | NetworkBehaviour | MonoBehaviourPunCallbacks |
| 내 고유 ID | NetworkManager.LocalClientId | photonView.Owner.ActorNumber |
| 서버/방장 확인 | IsServer | PhotonNetwork.IsMasterClient |
| RPC 속성 | [ServerRpc], [ClientRpc] | [PunRPC] (하나로 통일) |
| RPC 호출 | 함수이름ClientRpc() | photonView.RPC("함수이름", 타겟, 매개변수) |
| 접속 감지 | OnClientConnectedCallback | override void OnPlayerEnteredRoom(Player newPlayer) |
콜백 이벤트
override 키워드를 붙여야 자동으로 콜백 함수가 실행된다.

커스텀 프로퍼티
[Header("UI 연결")]
[SerializeField] private Text statusText; // 준비 여부를 표시할 UI 텍스트
[SerializeField] private Button readyButton;
[SerializeField] private Button startButton;
void Start()
{
// 버튼 이벤트 리스너 등록
readyButton.onClick.AddListener(OnClickReadyButton);
startButton.onClick.AddListener(GameStart);
// 시작 버튼은 초기에는 숨김 처리
startButton.gameObject.SetActive(false);
}
// 방 입장 성공 콜백 이벤트
public void OnJoinedRoom()
{
Debug.Log("방 입장 성공!");
// 플레이어마다 시작하자마자 본인의 IsReady 상태값을 false로 설정 후
Hashtable initialProps = new Hashtable() { { "IsReady", false } };
// 다른 모든 플레이어들에게 본인이 false임을 동기화
PhotonNetwork.LocalPlayer.SetCustomProperties(initialProps);
}
Hashtable initialProps = new Hashtable() {{"IsReady", false}};는 전체 규칙을 만드는 게 아니라 본인의 속성 값을 정의하는 코드이다. 유니티 스크립트는 각자의 컴퓨터에서 따로따로 돌아가기 때문에 위 코드만으로는 본인에게만 적용된다. 따라서 접속한 모든 플레이어에게 상태를 동기화하려면 SetCustomProperties를 반드시 실행해야 한다.
또한, 이 로직은 플레이어가 방 입장에 성공한 직후 실행되어야 하므로 Start()가 아닌 OnJoinedRoom()에서 호출해야 한다.
// 프로퍼티 변경 시 콜백
public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
if (changedProps.ContainsKey("IsReady"))
{
// UI 업데이트: V 표시 갱신(보류)
// 방장은 게임 시작 조건 확인(버튼 이벤트 함수에 넣으면 안됨)
// -> 남이 눌렀을 때도 확인해줘야 하므로
if (PhotonNetwork.IsMasterClient)
{
if (CheckGameStartCondition())
{
startButton.gameObject.SetActive(true);
startButton.interactable = true;
}
else
{
startButton.gameObject.SetActive(false);
}
}
}
}
UI의 interactable 속성은 이전 상태를 기억하지 못하기 때문에, SetActive(true)로 버튼을 보여줄 때 interactable 상태도 함께 설정해 주어야 한다.
포톤 PUN 2는 방장이 방을 나가거나 연결이 끊기면, 남은 플레이어 중 가장 낮은 Actor Number를 가진 클라이언트를 자동으로 새로운 방장으로 지정한다.
방장이 바뀌면 start button을 보여줘야 하므로 방장 변경 감지 콜백 함수 사용했다.
public override void OnMasterClientSwitched(Player newMasterClient)
{
// 방장이 바뀌면 바뀐 방장에게 start button 보이게 처리.
if (newMasterClient == PhotonNetwork.LocalPlayer)
{
startButton.gameObject.SetActive(true);
startButton.interactable = CheckGameStartCondition();
}
}
방에 있다가 leave 버튼을 누르면 다시 로비창으로 돌아와야 한다.
public void OnClickLeaveButton()
{
PhotonNetwork.LeaveRoom();
}
// Leave 버튼 누른 후 방에서 완전히 퇴장했을 때 호출됨
public void OnLeftRoom()
{
// 플레이어를 다시 로비창으로 이동시켜주기
Debug.Log("방 퇴장 완료. 로비창으로 돌아갑니다.");
// 로비씬의 이름이 Lobby라고 가정
SceneManager.LoadScene("Lobby");
}
⇒ 위 코드에서 중요한 점은 OnClickLeaveButton()에서 LeaveRoom()과 LoadScene()을 한꺼번에 하면 안 된다는 점이다. 위 설명처럼 LeaveRoom은 비동기 방식이므로 모든 처리를 마무리하는데 시간이 좀 걸린다. 그러므로 OnLeftRoom()에서 씬 이동 처리를 해야 한다.
// 게임 시작 조건 확인 함수
public bool CheckGameStartCondition()
{
int curPlayerCnt = PhotonNetwork.CurrentRoom.PlayerCount; // 현재 인원
int maxPlayer = PhotonNetwork.CurrentRoom.MaxPlayers; // 방의 최대 인원
if (curPlayerCnt == maxPlayer) // 6명 있고
{
// 모두 준비 상태인지 확인
foreach (Player p in PhotonNetwork.PlayerList)
{
object isReadyValue;
if (p.CustomProperties.TryGetValue("IsReady", out isReadyValue))
{
// IsReady 값이 있는데 하나라도 false라면
if ((bool)isReadyValue == false) return false;
}
else return false; // IsReady 값이 없으면(아직 로딩중!)
}
return true; // 모든 조건 통과 시 true
}
return false; // 6명 아니면 무조건 false
}
foreach는 배열로 돌아줘야 하므로 PlayerList를 가져와서 한명씩 준비했는지 체크해줘야 한다. 여기서 혹시 IsReady가 아직 로딩중으로 없을 수 있으니 안전하게 TryGetValue()를 사용했다.
처음에는 Start()에 방장을 확인한 후 Game Start 버튼이 보이게 하는 코드를 넣어놨는데, 서버 접속에 시간이 걸리다 보니 방장임이 false로 되어 start 버튼이 나타나지 않았다. 이 문제를 해결하기 위해 방 입장이 확실히 완료된 후 실행되는 OnJoinedRoom() 콜백으로 해당 로직을 이동하여 해결하였다.


잘 작동하는 것을 확인할 수 있다!