학원에서 팀 포트폴리오를 만들때 그냥 한번 해보고싶어서 포톤으로 허접한 1대1 전투를 만들어 봤었다.
알아두면 언젠간 쓸 일이 다 있기도 하고 이번에도 다시 한번 해보고싶기도 하고 해서 간단하게만 구현해봤다.
포톤 가입 & 어플 만들기
글로벌 크로스 플랫폼 실시간 게임 개발 | Photon Engine
포톤에 가입해서 우측 상단에 있는 새 어플리케이션을 PUN으로 만들어 주면 된다.
Photon Unity Networking 이라서 PUN 이다.
20CCU(동접자수) 까지 무료이니 멀티 연습으로 딱 좋다.
에셋스토어에서 포톤 PUN2 받기
https://assetstore.unity.com/packages/tools/network/pun-2-free-119922
다운받아서 프로젝트에 임포트 해주면 끝이다.
임포트하면 PUN Setup이 뜨는데, 포톤 사이트에서 내 어플리케이션ID를 복붙하고 Setup Project를 누르면 끝이다.
포톤 서버 접속하기
대충 타일맵을 깔고 , UI를 만들었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
public class NetWorkManager : MonoBehaviourPunCallbacks
{
public Text TitleTx;
public InputField Nick_Input;
public GameObject LoginPanel;
public GameObject Before_Connect;
public GameObject After_Connect;
private void Awake()
{
Screen.SetResolution(960, 540, false); //창모드
PhotonNetwork.SendRate = 60;
PhotonNetwork.SerializationRate = 30;
}
private IEnumerator Start()
{
yield return PhotonNetwork.ConnectUsingSettings();
if(PhotonNetwork.IsConnected)
{
Before_Connect.SetActive(false);
After_Connect.SetActive(true);
}
else
{
Application.Quit();
}
}
photon.Pun과 photon.Realtime을 using 하고,MonoBehaviourPunCallbacks 클래스를 상속받아야 포톤의 여러 기능들을 사용 가능하다.
Awake 함수에 창모드로 만드는거 말고는 잘 모르겠다. 저 부분은 고라니님 강의를 따라해봤다.
Start함수는 코루틴으로 사용이 가능하므로 접속 결과가 나오면 아래 코드가 실행되도록 코드를 작성했다.
접속에 성공했다면 닉네임을 입력하는 화면이 뜨고, 실패하면 꺼지게끔 만들었다.
닉네임 받아서 방에 들어가기
public void ClickBtn()
{
if (Nick_Input.text.Length > 1)
{
PhotonNetwork.NickName = Nick_Input.text;
PhotonNetwork.JoinLobby();
}
else TitleTx.text = "닉네임 2글자 이상!";
}
접속 버튼을 누르면 닉네임을 검사하고 포톤 네트워크의 닉네임에 인풋 필드에 적은 닉네임을 넣어줬다.
그리고 로비에 접속하게 했다.
public override void OnJoinedLobby()
{
if (PhotonNetwork.CountOfRooms == 0) PhotonNetwork.CreateRoom("1대1뜨실분");
else PhotonNetwork.JoinRandomRoom();
}
public override void OnJoinedRoom()
{
LoginPanel.SetActive(false);
PhotonNetwork.Instantiate("Mask_Dude", Vector2.zero, Quaternion.identity);
}
이 함수는 MonoBehaviourPunCallbacks 클래스를 상속받아야 사용 가능한 함수다.
재정의 해서 사용하면되는데 어려운건 없고 뜻 그대로 로비에 들어갔을때, 방에 들어갔을때 할 행동들을 적으면 된다.
난 따로 방 목록을 만들어서 띄울것이 아니기때문에 로비에 들어갔을때 룸이 0개면 방을 만들게했고,
방이 있다면 랜덤으로 방에 들어가게 했다. (어차피 1개밖에 없기 때문에)
CreateRoom 함수를 뜯어보면 여러가지 매개변수들을 가지고 있는데, 그중에 룸 옵션만 살펴본다면
보이는대로 여러가지를 설정 가능하다.
하지만 나는 꼭 적어야하는 방 이름만 적었다.
일단 로비에 들어가면 어떤 조건이더라도 방에 자동으로 들어가게끔 코드를 작성해놨는데,
그럼 2번째 함수인 OnJoinedRoom 함수가 바로 실행된다.
로그인 패널을 끄고, 플레이어를 생성 시켰다.
PhotonNetwork.Instantiate는 방에서만 실행 가능한 함수다.
그냥 Instantiate를 쓰면 안되고, recources 폴더를 만들어놓고 PhotonNetwork.Instantiate로 생성해야 한다.
나도 아직 포톤을 깊게 안파서 잘 모르겠지만 플레이어를 미리 만들어두면 소유권?이라고 해야할까..
멀티에서의 동기화에 문제가 생긴다.
이제 접속을 하고 로비-> 방에 들어가서 플레이어를 생성하는 과정은 끝이났다.
플레이어 - 포톤뷰
내가 만들어놓은 플레이어의 컴포넌트들이다.
다른 플레이어들이 보지 않아도 되는 동작이라면 상관 없지만, 움직이거나 애니메이션을 재생시키거나 충돌처리를 하거나 하는 모든 부분들을 photon view라는 컴포넌트로 동기화를 시켜줘야 한다.
기본적으로 포톤뷰 컴포넌트는 있어야하고, 동기화가 필요한 컴포넌트들을 추가로 넣어주면 된다.
난 트랜스폼(이동),리지드바디(충돌),애니메이터(애니메이션)의 동기화가 필요하기 때문에 포톤뷰를 달아줬다.
함수를 통해 이동하고, 애니메이션이 재생되기 때문에 플레이어 스크립트도 포톤뷰에 추가됐다.
오늘 다시 해보니 애니메이터 포톤뷰는 기본적으로 다 Disabled로 되어있다.
내가 필요한 파라미터만 바꿔주어 사용할수도 있다.
예전에 포폴을 만들때의 기억으로는 트리거를 사용할때 문제가 있었던거 같은데 기억이 안난다..
플레이어 닉네임 설정 - 포톤뷰 IsMine
멀티 환경은 하나의 클라이언트안에 여러명이 들어오는것이 아니라 각각의 클라이언트안에서 동기화를 시켜주는 것이다.
즉, 플레이어가 3명이면 캐릭터는 총 9개다.
그래서 내가 플레이하고있는 컴퓨터에서 아무 처리 없이 이동코드를 짠 후, 이동을 시키면 3명이 같이 움직인다.
동기화를 시켜주지않으면 그 3명도 내 컴퓨터에서만 움직이게 된다.
포톤서버에 접속해서 유니티로 포톤뷰 컴포넌트를 보면 IsMine이라는 항목이 있는데, 이게 내건지 남의것인지 가려주는 역할을 한다.
private void Awake()
{
if (photonView.IsMine)
{
myNick.text = PhotonNetwork.NickName;
myNick.color = Color.white;
}
else
{
myNick.text = photonView.Owner.NickName;
myNick.color = Color.red;
}
}
만약 내가 플레이를 하고있다가 누군가가 접속을 한다고 가정해보면, 접속한 새로운 플레이어의 컴퓨터에는 당연히 캐릭터가 새로 생성 되겠지만 내 컴퓨터에도 그 플레이어의 캐릭터가 생성되게 된다.
그러면 두 컴퓨터 다 상단의 코드가 실행이 될거고 그에 맞는 처리를 하게될텐데, 그 사람 입장에서 새로 생성된 캐릭터는 IsMine이 true이고 내 입장에서 새로 생성된 캐릭터의 IsMine은 false이다.
그래서 다시 코드를 보면 이 포톤뷰가 내것일때 닉네임 텍스트는 내가 설정해둔 닉네임, 닉네임 색은 하얀색으로 설정했고,
내것이 아니면 그 캐릭터의 닉네임은 그 포톤뷰 주인의 닉네임, 색은 빨간색으로 설정했다.
(닉네임 텍스트는 월드 스페이스 캔버스로 캐릭터 하위에 미리 넣어두었다)
빌드를 해서 접속을 해봤다. 내 의도에 맞게 닉네임과 색깔이 잘 설정된것을 볼 수 있다.
플레이어 이동 동기화 - PunRPC
private void Update()
{
if (photonView.IsMine && !MainChatPanel.activeSelf)
{
float x = Input.GetAxisRaw("Horizontal");
myRigid.velocity = new Vector2(4 * x, myRigid.velocity.y);
if( x != 0)
{
myAnim.SetBool("Run",true);
myView.RPC("FlipX", RpcTarget.AllBuffered, x);
}
else myAnim.SetBool("Run", false);
}
}
업데이트문에서 이동을 시키는 코드이다.
그냥 이동시켜도 포톤 트랜스폼 뷰 컴포넌트가 잘 동기화를 해주겠지만, 2D로 만들었다보니 방향을 틀었을때 이미지의 방향을 바꿔주는것 까지는 해주지 않는다.
내 화면에서만 실행을 하려면 굳이 동기화 시키지 않아도 되지만 방향을 바꾸는건 다른 플레이어들에게도 보여줘야 하기때문에 신경을 써야한다.
//사용 예시
myView.RPC("FlipX", RpcTarget.AllBuffered, x);
//RPC함수
[PunRPC]
public void FlipX(float x) => spriteRenderer.flipX = x == -1;
이런식으로 [PunRPC]를 쓰고 함수를 만들면 된다.
그리고 포톤뷰변수.RPC("함수이름",타겟(enum값),파라미터들) 이런식으로 사용한다.
요약하면 "내 캐릭터가 이 함수를 써서 무언가를 동작시켰으니 너희 컴퓨터에 있는 내 캐릭터도 이 함수 써! "라고 알려주는 셈이다.
RpcTarget을 보면 여러가지 값들이 있는데, AllBuffered가 모든 사람들에게 보낼 뿐만 아니라 새로 접속하는 플레이어에게도 적용이 되기 때문에 사용했다.
내 캐릭터가 방향 바꿨어요! 라고 알려줬기 때문에 오른쪽 클라이언트에서 입력한 움직임이 왼쪽 클라이언트에서도 잘 작동하는것을 볼 수 있다.
말풍선 채팅 - 변수 동기화 (OnPhotonSerializeView)
private void Update()
{
//엔터 입력
if(photonView.IsMine && Input.GetKeyDown(KeyCode.Return))
{
if (MainChatPanel.activeSelf)
{
MainChatPanel.SetActive(false);
if(Chat_Input.text.Length > 0)
{
myView.RPC("OpenChatBox", RpcTarget.AllBuffered);
Chat_Input.text = "";
StopAllCoroutines();
StartCoroutine(DelayCloseChatBox(3.0f));
}
}
else
{
MainChatPanel.SetActive(true);
Chat_Input.ActivateInputField();
}
}
}
엔터를 눌렀을때 채팅 입력창이 꺼져있으면 채팅 입력창을 키고 바로 채팅을 칠 수 있게 만들었고,
채팅입력창이 켜져있을때 엔터를 누르면 입력한 채팅이 말풍선에 찍히고 3초후에 말풍선을 끄게 만들었다.
처음에 조건으로 IsMine이 true일때를 안걸어놔서 ,, 2명의 플레이어가 접속해있을때 엔터를 한번 누르면 함수가 2번 실행되기 때문에 입력창이 켜지고 바로 꺼지는 일이 발생했다. 누군가 접속만하면 입력창이 안켜져서 몇 분 고생했다.
포톤뷰 RPC로 모든 플레이어들에게 내 캐릭터가 "OpenChatBox"를 사용한다는걸 알려주었다.
[PunRPC]
public void OpenChatBox()
{
ChatBox.SetActive(true);
ChatBox_Text.text = "<color=red>"+PhotonNetwork.NickName+"</color>"+" :\n"+ Chat_Input.text;
}
OpenChatBox 함수는 그냥 입력창에 입력해놓은 채팅을 말풍선과함께 띄워주는 함수다.
근데 입력창에 입력해둔 스트링값을 다른 플레이어들에게도 전달 해줘야하기 때문에 ..
여기서 변수 동기화가 필요하다.
IPunObservable 인터페이스를 상속받으면 OnPhotonSerializeView라는 함수를 정의해야 한다.
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if(stream.IsWriting)
{
stream.SendNext(ChatBox_Text.text);
}
else
{
ChatBox_Text.text = (string)stream.ReceiveNext();
}
}
if문 안의 코드는 내가 변수를 넘길때, 아래 코드는 내가 변수를 받을때 적용된다.
넘길떄는 stream.SendNext(Object 타입)로 넘겨주면 되고,
받을때는 변수값에 stream.ReceiveNext()를 이용해서 받는데 반환값이 Object 타입이기 때문에 그냥 받으면 에러가 난다.
항상 적합한 타입으로 형변환을 시켜서 받아야 한다.
IEnumerator DelayCloseChatBox(float t)
{
yield return new WaitForSeconds(t);
myView.RPC("CloseChatBox", RpcTarget.AllBuffered);
}
[PunRPC]
public void CloseChatBox()
{
ChatBox.SetActive(false);
}
코루틴으로 3초후에 내 말풍선 종료되니 다 종료해! 라는 신호를 보내주었다.
말풍선의 크기 처리는 딱히 해주지 않았지만 채팅이 잘 나오는것을 볼 수 있다.
AsickAsack/Photon_Practice: 포톤연습 프로젝트 (github.com)
허접한 코드지만 보고싶으시다면 위의 깃허브에서 보시면 될 것 같고,
내 코드보다는 포톤 고수이신 고라니님의 강의를 추천드린다.
'게임 엔진 > Unity' 카테고리의 다른 글
유니티 에셋공부 <DOTween Pro> - (2) (0) | 2023.01.17 |
---|---|
유니티 에셋공부 <DOTween Pro> - (1) (2) | 2023.01.15 |
유니티 박스(장애물) 높이별 점프 구현해보기 (0) | 2022.08.15 |
[유니티 공식 유튜브] IL2CPP란 무엇일까? (0) | 2022.08.09 |
[유니티 공식 유튜브] 모바일 게임 성능 최적화 - 2편 (0) | 2022.08.07 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!