스타터스에 들어와서 부터는 코딩으로 게임을 직접 만들 수 있어서 너무 행복하지만, 반대로 게임을 하는 시간을 가지기가 쉽지 않다.
그나마 하는 시간은 친구들하고 같이 하는 시간 뿐인데, 그러다보니 문득 멀티플레이 게임을 만들고 싶다는 생각이 들었었다.
그런 생각으로 포톤을 시작했던 것은 사실 2주~3주 전이었던 걸로 기억하는데..
방을 만들고, 게임을 시작하는 딱 그 부분까지 만드는데 너무나 힘이 드는 것인지 그 때에 처음 알고, 도망쳐나왔다. ㅠㅠ
하지만, 이번주에 다시 수업으로 멀티플레이를 마주하게 되었고, 피할 수 없게 되었다면, 그냥 즐겨버리자는 마인드로 포톤을 정복하려 하고 있다.
본 게시글에는 이번 주 배운 내용들을 기록할 예정이고, 다음 주에 발표를 하면서 만든 프로젝트까지 같이 올리도록 하겠다.
Starters 18주차 - 'Mirror'
미러와 포톤은 모두 네트워크 통신을 통해서 유니티에서 멀티플레이가 가능하게끔 하는 패키지이다.
둘 다 Unet에서 시작해서 그런지 비슷한 점이 꽤 있는데, 특히나 서버 통신을 하기 위해 한 오브젝트에 컴포넌트를 달아 사용한다던가, 권한을 부여하고 조작을 한다는 등의 점이 완전히 똑같다.
이번 수업 시간에는 이런 기능들(핵심적인 기능이기도 하다)을 위주로 배웠고, 나는 이를 조금씩 비교하면서 정리하려고 한다.
Network Manager / Network Room Manager
미러에서 통신을 위해 사용하는 컴포넌트는 Network Manager이다.
신기한 것은 꼭 이 컴포넌트만 사용해야 하는 것이 아니라, Network RoomManager라는 클래스를 상속받은 클래스면 모든 이런 기능을 할 수 있다는 점이다.
실제로 아래의 두 사진은 각각 Network Manager 역할을 하는 컴포넌트이자 클래스이고, 그 중 두 번째는 Network RoomManager 클래스를 상속받은 커스텀 클래스이다.
이 컴포넌트들은 서버에서 사용될 씬과 프리팹들을 넣을 수 있다.
이 부분은 선택이 아니라 필수로, Scene Management, Player Object, Room Settings에 각각 해당하는 씬들과 플레이어 프리팹들을 잘 등록해야 정상적으로 작동한다.
그리고, 그 외에 서버에서 생성할 모든 프리팹 역시 맨 아래 Registerd Spawnable Prefabs에 미리 넣어놓아야 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror; // 미러를 사용하기 위함
// NetworkRoomManager를 상속받음
public class RoomManager : NetworkRoomManager
{
public NetworkConnectionToClient me;
public bool isStart = false;
// 방에 입장시에 실행되는 내장 함수 override
public override void OnRoomServerConnect(NetworkConnectionToClient conn)
{
// 기본으로 실행되는 코드 실행을 먼저 하고, 내 코드들을 실행
base.OnRoomServerConnect(conn);
// 로비 플레이어 프리팹을 생성 --> 서버에서도 생성 (미리 컴포넌트에 등록해야 함)
var lobbyPlayer = Instantiate(spawnPrefabs[0]);
NetworkServer.Spawn(lobbyPlayer, conn);
}
// 방의 모든 플레이어가 준비 되었을 때에 실행되는 내장 함수 override
public override void OnRoomServerPlayersReady()
{
base.OnRoomServerPlayersReady();
isStart = true;
}
}
스크립트로는 다음과 같이 게임 상황에 맞춰 코드를 실행시킬 수 있다.
미러 그리고 포톤에서는 각각 상황에 따라 자동으로 실행하는 기본 함수들이 있는데, 이를 override하면, 그 상황에서 추가로 실행할 코드들을 작성할 수 있다.
캐릭터 생성하기 - Network Identity
플레이어가 조작을 통해 캐릭터를 이동할 때에는 어떻게 할까?
만약에 생성한 캐릭터 중에 어떤 것이 내 캐릭터인지 알지 않고 그냥 실행하면, 조작 시에 모든 플레이어가 움직이게 될 것이다.
그래서 존재하는 것이 Network Identity 클래스로, 어떤 플레이어가 권한을 가지고 있는지 알려준다.
public void Move()
{
if (hasAuthority && isMoveAble)
{
// 길이 제한 = 어느 방향으로든 1f --> 즉 방향 가져옴
Vector3 dir = Vector3.ClampMagnitude(new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")), 1f);
// Vector3 dir = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")).normalized;
transform.position += dir * speed * Time.deltaTime;
}
}
실제로 컴포넌트에서 이 컴포넌트의 hasAuthority를 통해 내가 해당 오브젝트에 권한이 있는지 확인할 수 있다.
위의 스크립트에서는 내 캐릭터일 때에만 조작을 통해 움직일 수 있도록 하였다.
캐릭터 transform, animator를 컴포넌트로 동기화 하기
멀티플레이에서 가장 중요한 것은 무엇보다 동기화이다.
내가 아무리 조작을 해도, 다른 컴퓨터에서 그대로 동기화가 안 된다면, 그건 싱글플레이나 다름없기 때문이다.
상호작용을 하기 위해서는 이 부분이 필수적이다.
미러에서나 포톤에서나 기본적으로 transform, animator는 컴포넌트를 달기만 하는 것으로도 동기화가 자동으로 이루어진다.
미러에서는 Network Transform(Animator)가 바로 그것이다.
물론 스크립트로도 이를 전달하고 전달받을 수도 있다.
이 부분은 쉽게 설명하기 어려운데.. 일단 Command를 사용하면, 클라이언트가 서버에 요청을 할 수 있고,
[Command]
public void CmdGetDrawers()
{
foreach (GameObject d in roomManager.drawers)
drawers.Add(d);
drawLine = drawers[0].GetComponent<DrawLine>();
Invoke("RpcUpdateTurn", 2f);
}
ClientRpc를 통해서는 반대로 서버에서 클라이언트들에게 동작을 시킬 수 있다.
[ClientRpc]
public void RpcUpdateTurn(int i)
{
foreach (GameObject d in drawers)
d.SetActive(false);
drawers[i].SetActive(true);
}
이 부분은 포톤에도 있는 부분으로 다음주에 추가로 이야기 하면서 더 정리하도록 하겠다.
Starters 18주차 - 'Photon - Pun2'
Photon View / Photon Transform(Animator) View
포톤 역시 기본적으로 위와 같이 서버 통신을 도와주는 여러 컴포넌트들을 제공한다.
Photon View는 미러에서 Network Identity와 같은 기능을 하고,
Photon Transform(Animator) View는 Network Transform(Animator)와 같다고 보면 된다.
Photon View에서는 해당 오브젝트가 서버에서 관찰되는 것들을 목록으로 직접 볼 수 있는데, Photon Transform(Animator) 컴포넌트를 통해 위치, 회전, 크기, 애니메이터 그리고, IPunObservable을 상속받는 컴포넌트의 변수들 역시 관찰되어진다.
Photon으로 로그인 --> 로비 --> 방 접속하기
...
using Photon.Pun;
using Photon.Realtime;
// 포톤을 통해 통신하기 위해서는 반드시 MonoBehaviourPunCallbacks를 상속 받아야 함
public class ConnectManager : MonoBehaviourPunCallbacks
{
public GameObject LoginPanel;
public InputField InputField;
public Text inGameLog;
public bool isLoaded = false;
public string myName = "";
// 서버에 접속했을 때에 실행
public override void OnConnectedToMaster()
{
// 서버에 들어오자마자 바로 방을 만들거나 참여하게 함
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions(), TypedLobby.Default);
}
// 방에 접속할 때에 자동으로 실행
public override void OnJoinedRoom()
{
// PhotonNetwork.MasterClient는 해당 방의 마스터 플레이어를
// PhotonNetwork.LocalPlayer는 자기 자신을 알려준다.
if (PhotonNetwork.MasterClient != PhotonNetwork.LocalPlayer)
GameObject.Find("StartBtn").GetComponent<Button>().interactable = false;
Invoke("OnJoinedLobbyDelay", 1);
}
void OnJoinedLobbyDelay()
{
isLoaded = true;
// 포톤에는 플레이어마다 닉네임을 설정하고, 접속하는데 이를 가져와서 닉네임에 띄울 수 있다.
PhotonNetwork.LocalPlayer.NickName = InputField.text;
myName = PhotonNetwork.LocalPlayer.NickName;
var pos = new Vector3(Random.Range(-3f, 3f), 0.2f, Random.Range(-3f, 3f));
// 서버에 캐릭터를 생성하는 메서드이다.
// 반드시 Resources 폴더에 프리팹을 넣어주어야 한다.
PhotonNetwork.Instantiate("Avatar", pos, Quaternion.identity);
LoginPanel.SetActive(false);
}
// 어떤 플레이어든 방에 들어오면 실행하는 함수
public override void OnPlayerEnteredRoom(Player newPlayer)
{
Invoke("AddScoreList", 1);
}
// 어떤 플레이어든 방에서 나가면 실행하는 함수
public override void OnPlayerLeftRoom(Player otherPlayer)
{
Invoke("AddScoreList", 1);
}
}
기본적으로 스크립트에서 Photon.Pun과 Photon.Realtime을 using해야 통신이 가능하다.
그리고, MonoBehaviour 대신에 MonoBehaviourPunCallbacks를 상속시켜주어야 한다.
스크립트에 주석이 달린대로 포톤(Pun) 역시 상황별로 실행되는 메서드들이 기본적으로 주어진다.
MonoBehaviourPunCallbacks를 상속받으면, 해당 메서드들을 override를 할 수 있게 된다.
본 스크립트에서는 버튼을 통해 로비에 접속하게 되고, 그 즉시 바로 방에 들어가게 된다.
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions(), TypedLobby.Default);
이 코드가 방을 만들거나 있는 방에 참여하는 코드인데, 여기서 첫 번째 인수가 방이름으로 없으면, 생성하고, 있으면 해당하는 방에 접속한다.
두번째 인수는 방의 옵션이다. 실제로 방의 옵션을 설정하면, maxPlayer, minPlayer등을 설정할 수 있다.
마지막 인수는 사실 잘 모르겠다. ㅠ
캐릭터 프리팹 생성, 조작하기
캐릭터를 생성하기 위해서는 반드시 캐릭터 프리팹에 Photon View 컴포넌트를 넣어야 한다.
이는 생성시에 권한을 부여받기 때문이다. (만약 컴포넌트에서 Ownership Transfer를 Take Over로 하면, 권한을 다른 유저가 코드로 뺐어올 수도 있다.)
그리고, 포톤에서는 프리팹을 Resources 폴더에 꼭 넣어주어야 한다.
마치 미러에서 컴포넌트에 사전 등록하는 것처럼 이 역시 정확하게 생성하기 위해서는 반드시 필요한 작업이다.
...
using Photon.Pun;
using Photon.Realtime;
public class PlayerMove : MonoBehaviourPunCallbacks, IPunObservable // 관찰할 수 있도록 함
{
public PhotonView pv;
...
void Update()
{
// PhotonView.IsMine을 통해 해당 오브젝트에 권한이 있는지 알 수 있다.
if (pv.IsMine)
{
GetInput();
Move();
Jump();
Attack();
}
}
// 관찰할 변수들을 서버에 보내고, 가져온다.
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
// 변경한 사람만 실행하는 코드로 변수의 변화를 서버에 보낸다.
if (stream.IsWriting)
{
stream.SendNext(transform.position);
stream.SendNext(isSpawn);
stream.SendNext(equipWeapon.meleeArea.enabled);
stream.SendNext(equipWeapon.trailEffect.enabled);
}
// 다른 사람들은 변경 값을 보낸 순서대로 받는다.
else
{
curPos = (Vector3)stream.ReceiveNext();
isSpawn = (bool)stream.ReceiveNext();
equipWeapon.meleeArea.enabled = (bool)stream.ReceiveNext();
equipWeapon.trailEffect.enabled = (bool)stream.ReceiveNext();
}
}
}
스크립트에서는 다음과 같이 권한이 있는지 확인하고, 동작하게끔 하면 된다.
그리고, 관찰할 수 있도록 IPunObserveable을 상속 받고, OnPhotonSerializeView 함수를 통해 변수들 역시 동기화 할 수 있다.
유데미 코리아 바로가기 :
Udemy Korea - 실용적인 온라인 강의, 글로벌 전문가에게 배워보세요. | Udemy Korea
유데미코리아 AI, 파이썬, 리엑트, 자바, 노션, 디자인, UI, UIX, 기획 등 전문가의 온라인 강의를 제공하고 있습니다.
www.udemykorea.com
💡 본 포스팅은 유데미-웅진씽크빅 취업 부트캠프 유니티 1기 과정 후기로 작성되었습니다.
'Starters 부트캠프 > B - log' 카테고리의 다른 글
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 20주차 학습 일지 (0) | 2022.11.05 |
---|---|
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 19주차 학습 일지 (0) | 2022.10.30 |
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 17주차 학습 일지 (0) | 2022.10.16 |
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 16주차 학습 일지 (0) | 2022.10.08 |
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 15주차 학습 일지 (0) | 2022.10.01 |