이번 글에서는 포톤을 이용하여 멀티플레이를 구현하는 방법을 정리해보도록 하겠다.
베이스라인이 되는 게임은 다음의 간단한 탁구게임을 이용하였다.
Pong 게임 https://github.com/seoksii/Pong
포톤을 프로젝트에 Import하는 등의 설정은 다음의 게시글을 참고하자.
로비 씬 만들기
먼저 로비 씬을 만들기 위해 포톤에서 제공해주는 데모 씬을 복사할 것이다.
그 후 이름을 LobbyScene으로 바꾸자.
복제해온 LobbyScene은 기본적으로 방을 만드는 기능, 랜덤한 방에 참가하는 기능, 생성된 방의 리스트를 불러오는 기능이 이미 구현이 되어 있다.
Hierarchy에서 MainPanel
을 보면 LobbyMainPanel.cs
스크립트가 부착되어 있는데, 이것을 약간 수정해보자.
LobbyMainPanel.cs
// MaxPlayers를 2로 수정 public override void OnJoinRandomFailed(short returnCode, string message) { string roomName = "Room " + Random.Range(1000, 10000); RoomOptions options = new RoomOptions {MaxPlayers = 2}; PhotonNetwork.CreateRoom(roomName, options, null); } // GameStart 버튼을 눌렀을 때 SampleScene을 로딩하도록 수정 public void OnStartGameButtonClicked() { PhotonNetwork.CurrentRoom.IsOpen = false; PhotonNetwork.CurrentRoom.IsVisible = false; PhotonNetwork.LoadLevel("SampleScene"); }
빌드 세팅에서 LobbyScene과 SampleScene을 추가하는 것도 잊지 말자.
게임 씬 동기화
이제 원래 게임이 구현되어 있던 씬으로 넘어오자.
다음 게임에서 Ball 오브젝트에
PhotonView
라고 하는 컴포넌트를 부착할 것이다.
PhotonView
에 대한 설명은 다음 게시글에 간단하게 정리되어 있다.
Player1과 Player2의 Paddle.cs
는
아예 컴포넌트를 제거해주고
새로운 스크립트를 만들어 부착해줄 것이다.
NetPaddle
이라는 이름의 스크립트를 하나 생성해서 다음과 같이 작성해주자.
NetPaddle.cs
using Photon.Pun; using UnityEngine; public class NetPaddle : MonoBehaviourPun { public float speed = 10f; void Update() { if(photonView.IsMine) { float move = Input.GetAxis("Vertical") * speed * Time.deltaTime; transform.Translate(0, move, 0); } } }
그 후 Player1과 Player2에 이 NetPaddle.cs를 추가해주고,
PhotonView
와 PhotonTransformView
라는 컴포넌트를 부착해주자.
이 때 PhotonTransformView
의 Synchronize Options에서 Rotation은 동기화를 꺼주도록 하자.
그 후 Ball과 Player1을 프리팹화 해주자.
나머지 스크립트들도 멀티플레이에 맞춰 동기화가 되어야 하기 때문에 다음과 같이 수정해주자.
Ball.cs
using Photon.Pun; using UnityEngine; public class Ball : MonoBehaviourPun, IPunObservable { public float speed; public Rigidbody2D rigidbody; private void Awake() { rigidbody = GetComponent<Rigidbody2D>(); } void Start() { if(!photonView.AmOwner) { return; } Launch(); } private void Launch() { if (!photonView.AmOwner) { return; } float x = Random.Range(0, 2) == 0 ? -1 : 1; float y = Random.Range(0, 2) == 0 ? -1 : 1; rigidbody.velocity = new Vector2(x* speed, y* speed); } public void Reset() { rigidbody.velocity = Vector2.zero; transform.position = Vector2.zero; Invoke("Launch", 1); } public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.IsWriting) { stream.SendNext(rigidbody.position); stream.SendNext(rigidbody.velocity); } else { rigidbody.position = (Vector2)stream.ReceiveNext(); rigidbody.velocity = (Vector2)stream.ReceiveNext(); } } }
Goal.cs
private void OnTriggerEnter2D(Collider2D collision) { if(collision.name.Contains("Ball")) { if(isPlayer1Goal) '''생략
GameManager.cs
using Photon.Pun; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviourPunCallbacks { [Header("Ball")] public Ball ball; [Header("Player 1")] public Paddle player1Paddle; public Goal player1Goal; [Header("Player 2")] public Paddle player2Paddle; public Goal player2Goal; [Header("UI")] public TextMeshProUGUI player1Text; public TextMeshProUGUI player2Text; private int player1Score; private int player2Score; private void Start() { SpawnPaddle(); if(photonView.AmOwner) SpawnBall(); } private void SpawnPaddle() { int idx = PhotonNetwork.LocalPlayer.ActorNumber; GameObject prefab = Resources.Load<GameObject>("Paddle"); if(idx == 1) { PhotonNetwork.Instantiate(prefab.name, new Vector3(-12, 0, 0), Quaternion.identity); } else { PhotonNetwork.Instantiate(prefab.name, new Vector3(12, 0, 0), Quaternion.identity); } } private void SpawnBall() { GameObject prefab = Resources.Load<GameObject>("Ball"); GameObject go = PhotonNetwork.Instantiate(prefab.name, Vector3.zero, Quaternion.identity); ball = go.GetComponent<Ball>(); } public void Player1Scored() { if (photonView.AmOwner) { player1Score++; ResetPosition(); photonView.RPC("UpdateScore", RpcTarget.All, player1Score, player2Score); } } public void Player2Scored() { if (photonView.AmOwner) { player2Score++; ResetPosition(); photonView.RPC("UpdateScore", RpcTarget.All, player1Score, player2Score); } } [PunRPC] public void UpdateScore(int score1, int score2) { player1Text.text = score1.ToString(); player2Text.text = score2.ToString(); if (score1 > 5 || score2 > 5) PhotonNetwork.LeaveRoom(); } private void ResetPosition() { ball.Reset(); } public override void OnLeftRoom() { SceneManager.LoadScene("LobbyScene"); } }
다음과 같이 되면 거의 완성이다.
추가로 게임을 하다가 로비씬으로 돌아오게 되면 이미 Connection이 세팅된 상태이기 때문에 Star버튼이 활성화가 안될 것이다.
해당 문제를 해결하기 위해 LobbyMainPanel.cs에 다음 부분을 추가해주자.
LobbyMainPanel.cs
private void Start() { if (PhotonNetwork.NetworkClientState == ClientState.Joined) this.SetActivePanel(SelectionPanel.name); }
빌드해서 테스트해보기
해상도 960x540정도로만 설정해주고 빌드를 해보았다.
다음과 같이 네트워크에 접속하여, 방 만드는 기능까지 완성하였다.
지금까지 포톤으로 멀티플레이를 구현해보았다.
실제 게임에선 Rigidbody와 Animator까지 동기화를 해줘야할 것이기 때문에 좀 더 어려운 부분이 많을 것이다.
해당 예제를 베이스로 멀티플레이를 제대로 구현해보자.
Uploaded by N2T
'개발 > Unity 내일배움캠프 TIL' 카테고리의 다른 글
Unity 포톤 동기화 작업 시 주의할 점들 (1) | 2023.10.19 |
---|---|
Unity 기존 게임을 포톤 PUN2로 동기화하기 (1) | 2023.10.18 |
Unity2D 사이드뷰 게임의 좋은 움직임 만들기 (1) | 2023.10.16 |
Visual Studio 단축키 (0) | 2023.10.13 |
유니티 포톤 (Photon) 셋업하기 (0) | 2023.10.12 |