
기존에 배운 로딩 > 닉네임설정 > 로비 > 방 을 전환하는 씬에서
게임시작 버튼을 누르면 같은 방에 있는 플레이어들과 씬을 전환하는 기능을 추가하여
간단한 미니게임을 만들었다.
컨셉은 바람의나라 선필로 3판 2선승제로 먼저 공격하면 이기는 방식이다.
public void OnStartBtn()
{
_startBtn.interactable = PhotonNetwork.IsMasterClient;
if (!PhotonNetwork.LocalPlayer.IsMasterClient)
{
Debug.Log("방장만 시작할 수 있음");
return;
}
if (PhotonNetwork.CurrentRoom.PlayerCount < 2)
{
Debug.Log("사람이 부족함");
return;
}
foreach (var kv in PhotonNetwork.CurrentRoom.Players)
{
// 로컬클라 패스
if (kv.Value == PhotonNetwork.LocalPlayer) continue;
// 레디 키가 없거나 레디안하면 게임시작불가
if (!kv.Value.CustomProperties.TryGetValue("Ready", out var value) || !(bool)value)
{
Debug.Log($"{kv.Value.NickName} 레디 안함");
return;
}
}
Debug.Log("게임 시작");
PhotonNetwork.LoadLevel("GameScene");
}
게임시작 버튼 onClick에 이벤트로 연결했고, 조건은 세가지다.
- 방장일 것
- 최소 2명 이상일 것
- 로컬 클라이언트(방장)을 제외한 플레이어가 준비 상태일 것
LoadLevel("씬이름") 함수가 네트워크에서 씬을 전환하는 함수로,
PhotonNetwork.AutomaticallySyncScene 를 true로 하면 방에 있는 인원들과 한번에 씬을 전환할 수 있다.
다만 LoadLevel() 함수 위에 적으면 타이밍이 안맞는지 Start() 이벤트 함수에서 true로 바꿔줬다.

1:1이다보니 스폰 위치를 두 곳 지정해줬다.
신기한건 씬을 전환하더라도 네트워크 연결은 유지되어 있어서 추가로 연결은 하지 않아도 된다.
string nickname = PhotonNetwork.NickName;
var player = PhotonNetwork.Instantiate("Player", 생성 위치, 생성 방향, 0, new object[] { nickname });
그래서 Start() 함수에서 PhotonNetwork.Instantiate() 함수를 사용하여 플레이어 오브젝트를 스폰한다.
포톤의 인스턴스함수를 사용하면 생성됐을 때 콜백함수를 받을 수 있는데,
public void OnPhotonInstantiate(PhotonMessageInfo info)
{
// 포톤 프리팹 인스턴스화시
object[] data = info.photonView.InstantiationData;
if (data.Length > 0)
{
string nickname = (string)data[0];
PlayerInit(nickname);
}
}
IPunInstantiateMagicCallback 인터페이스를 추가한 뒤
public void OnPhotonInstantiate(PhotonMessageInfo info) 함수를 구현하면 된다.

플레이어 오브젝트는 동기화를 위해 Photon View 뿐만 아니라
Transform, Rigidbody, Animator View 들도 추가했다.
애니메이터의 동기화 파라미터는 3개의 옵션이 있어서 상황에 맞게 설정하면 된다.
- Disabled : 동기화 안함
- Continuous : 매 프레임 동기화
- Discrete : 초당 10회 동기화
void OnTriggerEnter(Collider other)
{
if (!photonView.IsMine) return;
if (other.CompareTag("Enemy"))
{
var pv = other.GetComponent<PhotonView>();
if (pv != null && !pv.IsMine)
{
pv.RPC("TakeDamage", pv.Owner);
}
}
}
플레이어의 공격은 무기에 콜라이더를 생성하여 OnTriggerEnter() 로 감지하는 방식이다.
공격시 애니메이션이 실행되는데 애니메이션 이벤트 함수에서 콜라이더를 활성화,
공격이 끝나가면 비활성화하여 공격 대상을 감지한다.
공격 대상이 PhotonView 를 가지고 있고, 내 포톤뷰가 아닐 경우,
대상의 포톤뷰에 "TakeDamage" 함수를 전달한다.
[PunRPC]
public void TakeDamage(PhotonMessageInfo info)
{
if (IsDead) return;
Dead();
// 공격자, 피격자 아이디
int attackerId = info.Sender.ActorNumber;
int defenderId = PhotonNetwork.LocalPlayer.ActorNumber;
if (PhotonNetwork.IsMasterClient)
{
GameManager.Instance.PlayerDead(attackerId, defenderId);
}
else
{
GameManager.Instance.photonView.RPC("PlayerDead", RpcTarget.MasterClient, attackerId, defenderId);
}
}
TakeDamage() 함수에서 공격 대상, 피격 대상의 아이디를 쉽게 구할 수 있고,
이 아이디로 방장 클라이언트에서 피격처리를 하는 방식이다.
Player attacker = PhotonNetwork.CurrentRoom.GetPlayer(attackerId);
Player defender = PhotonNetwork.CurrentRoom.GetPlayer(defenderId);
게임매니저에서는 전달받은 아이디 매개변수로 현재 룸에 있는 플레이어를 구할 수 있다.
foreach (PhotonView view in FindObjectsOfType<PhotonView>())
{
var pc = view.gameObject.GetComponent<PlayerController>();
if (pc != null)
{
view.RPC("UnlockCursor", view.Owner);
}
}
// 방으로 이동
PhotonNetwork.LoadLevel("LobbyScene");
한 플레이어가 2점을 따면 이전 씬으로 돌아가야 하는데,
무식하지만 PhotonView 를 가진 오브젝트를 찾고, PlayerController 를 가진 플레이어를 다시 찾아
마우스 잠금해제같은 방으로 돌아갔을 때 해야할 코드를 실행한 뒤,
PhotonNetwork.LoadLevel() 함수를 통해 다시 씬전환을 한다.
// Start()
if (PhotonNetwork.IsConnected == false)
{
// 맨처음이면 연결시도
PhotonNetwork.ConnectUsingSettings();
}
else
{
// 게임씬에서 넘어왔을 때 == 연결되어있으면 룸 초기화 후 패널 비활성화
_roomManager.PlayerPanelSpawn();
// 모든 레디 풀기
_roomManager.ClearAllReady();
_loadingPanel?.SetActive(false);
_nicknamePanel?.SetActive(false);
_lobbyPanel?.SetActive(false);
}
알아야 할 점은 현재까지 씬 이동은 로비씬 > 게임씬 > 로비씬 으로,
로비씬으로 이동하면 NetworkManager 의 Start() 함수가 실행될텐데
위에서 말했듯이 네트워크 연결 후 씬을 전환하더라도 네트워크는 연결이 끊기지 않아
한번 더 연결을 하면 에러 디버그가 출력되서 예외처리를 추가했다.
이런 식으로 완전 처음 시작한 클라이언트와 여러 씬을 전환 후 로비씬으로 왔을 때를 구분할 수 있다.
또한 네트워크는 유지중이더라도 게임오브젝트는 그대로기에 다시 초기화를 진행해야 한다.
네트워크 타이밍이 너무 다양해서 타이밍을 잡는 것뿐만 아니라
흐름을 생각하여 초기화 함수들도 미리 만들어야 한다고 생각하니 머리아프다...
그리고 RPC를 실행하는 거는 보내는 곳과 받는 곳에서
Photon View가 있어야 한다는 건 알았는데MSW와 다르게 특정 클라이언트를 지정하는 방식이 감이 잘 안 잡혔다.
또한 RPC로 실행하는 함수가 string이다 보니 VS에서 참조로 나오질 않아서
복잡해지면 헷갈릴 것 같은데 어떻게 코드 규칙을 정해야 할지 잘 모르겠다....