1.4. \Assets\Photon\PhotonUnityNetworking\Resources\PhotonServerSettings 에 ID 넣기
2. Photon View
멀티플레이 게임을 만들 때 네트워크를 통해 동기화할 오브젝트를 지정
프리팹에 Photon View 추가
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
// MonoBehaviourPunCallbacks -> 매니저일 때 사용
// MonoBehaviourPun -> 못들음!
public class Simple_NetworkManager : MonoBehaviourPunCallbacks
{
private string gameVersion = "1";
void Awake()
{
Screen.SetResolution(1920, 1080, false); // 해상도 설정, false = Full Screen 사용 여부
PhotonNetwork.SendRate = 60; // 내 컴퓨터 게임 정보에 대한 전송률
PhotonNetwork.SerializationRate = 30; // Photon View 관측 중인 대상에 대한 전송률
PhotonNetwork.GameVersion = gameVersion;
}
void Start()
{
Connect();
}
private void Connect()
{
PhotonNetwork.ConnectUsingSettings(); // App ID 기반으로 접속
Debug.Log("서버 접속");
}
public override void OnConnectedToMaster()
{
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions { MaxPlayers = 20}, null);
Debug.Log("서버 접속 완료");
}
public override void OnJoinedRoom()
{
// 네트워크 상에 생성 (/Assets/Resource 폴더에 있는 "Player" 이름의 오브젝트 생성)
PhotonNetwork.Instantiate("Player", Vector3.up, Quaternion.identity);
Debug.Log("캐릭터 생성");
}
}
Awake()
Screen.SetResolution(1920, 1080, false) : 해상도를 고정해서 테스트 환경을 일정하게 맞춰줌
PhotonNetwork.SendRate = 60 : 내 컴퓨터에서 발생하는 입력이나 움직임 정보를 초당 60번 서버로 전송
PhotonNetwork.SerializationRate = 30 : Photon View가 붙은 오브젝트의 상태(위치, 애니메이션 등)를 초당 30번 동기화
PhotonNetwork.GameVersion = gameVersion : 같은 버전끼리만 매칭되도록 설정
OnJoinedRoom()
PhotonNetwork.Instantiate("Player", Vector3.up, Quaternion.identity) : 네트워크 상에 Player Prefab을 생성
이때 Player Prefab에 Photon View가 붙어 있어야 다른 클라이언트와도 공유됨
/Assets/Resources 폴더 안에 있는 Prefab 이름을 지정해야 정상적으로 불러올 수 있음
MonoBehaviour
PunCallbacks : 네트워크 매니저, 로비/룸 관리, 매칭등 매니저 역할 스크립트에서 사용
Pun : 씬 위에서 동작하는 개별 오브젝트 스크립트에서 사용
3. Multiplayer Play Mode
멀티플레이 테스트를 도와주는 패키지
Window > Multiplayer > Multiplayer Play Mode 메뉴를 통해서 테스트용 창을 열 수 있다.
여기에서 새로운 Player를 추가할 수 있고 Active로 설정하면 하나의 에디터 안에서 두 명 이상의 플레이어를 동시에 실행할 수 있다.
그러나 Photon이 Pun 타입으로 생성되었기 때문에 기본 상태에서는 Player2 창에서 내 움직임이 바로 반영되지 않는다.
4. 동기화
네트워크에서 게임 오브젝트는 크게 두 가지로 나눌 수 있다.
Local 오브젝트 : 내가 직접 조작하는 내 캐릭터
Remote 오브젝트 : 다른 플레이어가 조작하는 캐릭터
Photon에서는 이를 아래와 같이 구분한다.
photonView.IsMine → 내가 조종하는 내 오브젝트
!photonView.IsMine → 다른 플레이어의 오브젝트
using Photon.Pun;
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))] // 필수 컴포넌트 표시
public class Simple_PlayerController : MonoBehaviourPun
{
private CharacterController cc;
private Vector3 moveInput;
[SerializeField] private float moveSpeed = 2f;
[SerializeField] private float turnSpeed = 10f;
void Start()
{
cc = GetComponent<CharacterController>();
}
void Update()
{
if (photonView.IsMine)
{
Move();
Turn();
}
}
private void Move()
{
cc.Move(moveInput * moveSpeed * Time.deltaTime);
}
void OnMove(InputValue value)
{
var moveValue = value.Get<Vector2>();
moveInput = new Vector3(moveValue.x, 0, moveValue.y);
}
void Turn()
{
if (moveInput == Vector3.zero)
return;
Quaternion targetRot = Quaternion.LookRotation(moveInput);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, turnSpeed * Time.deltaTime);
}
}
플레이어의 움직임을 InputSystem을 활용해서 구현한 코드이다.
void Update()
{
if (photonView.IsMine)
{
Move();
Turn();
}
}
위와 같이 IsMine 조건을 넣어주면내 캐릭터만 입력을 받아서 움직이고 다른 사람 캐릭터는 입력을 무시하게 된다.
만약 IsMine 처리를 하지 않으면씬에 있는 모든 플레이어가 동시에 똑같이 움직이는 문제가 생긴다.
여기서 Photon Transform View 컴포넌트가 중요한 역할을 한다.
내 캐릭터(Local)는 내가 입력을 처리하고 그 좌표값을 네트워크에 전송
다른 사람 캐릭터(Remote)는 입력은 하지 않고 전송받은 좌표값을 반영
즉, 내가 움직이면 Photon Transform View가 위치/회전 정보를 패킷으로 보내고 → 다른 플레이어 화면에서도 내 캐릭터가 동일하게 움직이는 모습을 볼 수 있다.
3. Photon 실습
1. 닉네임 동기화
1.1. Intro 씬 생성 및 Scene List 설정
1.2. Intro 씬 UI 배치
1.3. Player 오브젝트에 text 추가
1.4. 스크립트
public class NetworkManager : MonoBehaviourPunCallbacks
{
private string gameVersion = "1";
[SerializeField] private TMP_InputField nickNameField;
[SerializeField] private Button connectButton;
void Awake()
{
Screen.SetResolution(1920, 1080, false); // 해상도 설정, false = Full Screen 사용 여부
PhotonNetwork.SendRate = 60; // 내 컴퓨터 게임 정보에 대한 전송률
PhotonNetwork.SerializationRate = 30; // Photon View 관측 중인 대상에 대한 전송률
PhotonNetwork.GameVersion = gameVersion;
}
void Start()
{
connectButton.onClick.AddListener(Connect);
}
private void Connect()
{
PhotonNetwork.NickName = nickNameField.text;
PhotonNetwork.ConnectUsingSettings(); // App ID 기반으로 접속
Debug.Log("서버 접속");
}
public override void OnConnectedToMaster()
{
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions { MaxPlayers = 20 }, null);
Debug.Log("서버 접속 완료");
}
public override void OnJoinedRoom()
{
PhotonNetwork.LoadLevel(1);
Debug.Log("방 입장");
}
}
// 플레이어 오브젝트 생성
public class Simple_GameManager : MonoBehaviour
{
// 방에 입장하기 전에 작동되면 에러
IEnumerator Start()
{
yield return null; // 동기화 과정에서의 타이밍 조절용?
PhotonNetwork.Instantiate("Player", Vector3.up, Quaternion.identity);
}
}
// PlayerController.cs 추가된 내용
public class Simple_PlayerController : MonoBehaviourPun
{
[SerializeField] private TextMeshPro nickName;
void Start()
{
if (photonView.IsMine)
{
nickName.text = PhotonNetwork.NickName;
nickName.color = Color.black;
}
}
}
인트로 씬에서 TMP_InputField로 닉네임을 입력하고 버튼을 누르면 해당 닉네임이 PhotonNetwork.NickName에 저장된 뒤 서버에 접속
PhotonNetwork.LoadLevel(1)을 호출해서 플레이어가 동일한 씬(1번 씬)으로 이동
PhotonNetwork.Instantiate("Player")로 Player 오브젝트를 네트워크 상에 생성
photonView.IsMine을 체크해서 본인 캐릭터에만 닉네임을 세팅
1.5. 인스펙터 연결
1.6 타인 닉네임 동기화
// PlayerController.cs
void Start()
{
cc = GetComponent<CharacterController>();
if (photonView.IsMine) // 내 이름 불러오기
{
nickName.text = PhotonNetwork.NickName;
nickName.color = Color.black;
}
else // 다른 플레이어의 이름도 불러오기
{
nickName.text = photonView.Owner.NickName;
nickName.color = Color.green;
}
}
photonView가 !IsMine일 때에는 그 소유자(Owner)의 닉네임을 불러오도록 설정
위 영상을 보면 Player2의 닉네임이 Player1 화면에서 정상적으로 동기화되는 것을 확인할 수 있다.
2. 이벤트 동기화
2.1. 새로운 InputSystem 이벤트 등록
2.2. Player 모자 생성
2.3. 모자 동기화 전 / 후
public class Simple_PlayerController : MonoBehaviourPun
{
[SerializeField] private GameObject hat;
void OnHat()
{
if (photonView.IsMine)
{
hat.SetActive(!hat.activeSelf);
}
}
}