일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 오큘러스
- ChatGPT
- meta
- 연습
- HAPTIC
- 모작
- 유니티 GUI
- 팀프로젝트
- meta xr
- 드래곤 플라이트 모작
- 포트폴리오
- XR
- OVR
- input system
- Oculus
- 팀 프로젝트
- 오브젝트 풀링
- 드래곤 플라이트
- CGV
- 유니티 UI
- 길건너 친구들
- 멀티플레이
- 앱 배포
- 개발
- 유니티 Json 데이터 연동
- VR
- 유니티
- 가상현실
- Photon Fusion
- 개발일지
- Today
- Total
EasyCastleUNITY
CGV 개발일지 14(시작 씬, 로비 씬, 방 씬 만들기) 본문
저희 프로젝트는, VR 플레이어가 호스트가 되어 방을 만들고,
PC 플레이어가 클라이언트가 되어 방에 입장하는 방식으로 됩니다.
그렇기에 이에 따른 기본 로직을 작성해보려고 합니다.
먼저 시작 씬입니다.
레퍼런스한 다비고 게임의 시작화면입니다. 이 모습과 비슷하게 만들어보겠습니다.
현재 사용자가 어떤 버튼에 가 있는지 알 수 있도록 했습니다.
그 다음으로는 Game 버튼을 누르면 나오는 모습입니다.
이렇게 거인과 기사를 선택할 수 있고, 거인을 선택하면 바로 방을 만들고, 기사를 선택하면, 방을 선택할 수 있는 로비씬으로 옮겨집니다.
Game 버튼을 누르면 거인을 플레이 할지 기사를 플레이 할지 나옵니다.
그리고 레퍼런스한 게임인 다비고와는 다르게 이 시점에서 닉네임을 입력하고 서버로 연결이 됩니다.
이에 관한 코드는 FirstMain이라고 작성하였고 아래의 코드 입니다.
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class FirstMain : MonoBehaviour
{
[Header("Start")]
[SerializeField] private GameObject startGo;
[SerializeField] private Button btnGame;
[SerializeField] private Button btnQuit;
[Header("Select Mode")]
[SerializeField] private GameObject selectModeGo;
[SerializeField] private Button btnGiant;
[SerializeField] private Button btnKnight;
[Header("NickName Input")]
[SerializeField] private TMP_InputField nickNameInputField;
// Start is called before the first frame update
void Start()
{
this.selectModeGo.SetActive(false);
this.ButtonExecutionEvents();
}
private void ButtonExecutionEvents()
{
this.btnQuit.onClick.AddListener(() =>
{
this.AppQuit();
});
this.btnGame.onClick.AddListener(() =>
{
this.startGo.SetActive(false);
this.selectModeGo.SetActive(true);
});
this.btnGiant.onClick.AddListener(() =>
{
if (string.IsNullOrEmpty(this.nickNameInputField.text))
{
Debug.Log("닉네임을 입력해주세요");
}
else
{
Debug.Log("<color=green>거인 선택! 방 만들기</color>");
NetworkManager.instance.ConnectVR(this.nickNameInputField.text);
//로딩창 실행
}
});
this.btnKnight.onClick.AddListener(() =>
{
if (string.IsNullOrEmpty(this.nickNameInputField.text))
{
Debug.Log("닉네임을 입력해주세요");
}
else
{
Debug.Log("<color=red>기사 선택! 로비 씬으로 이동 </color>");
NetworkManager.instance.ConnectPC(this.nickNameInputField.text); //서버 연결
//로딩창 실행
}
});
}
private void AppQuit()
{
Debug.Log("앱 종료");
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
Quit 버튼을 누르면 게임을 종료합니다.
그리고 Game 버튼을 누르면, Game버튼과 Quit 버튼을 비활성화 하고
모드를 선택하는 UI창을 띄웁니다.
또한 거인을 누르면 싱글톤 클래스인 NetworkManager의 ConnectVR을 호출하여 연결하고
기사를 누르면 NetworkManager의 ConnectPC를 호출합니다.
이렇게 모드를 선택한 순간 서버와 연결이 됩니다.
이제 서버가 연결된 이후를 살펴보겠습니다.
그 다음으로는 위 화면에서 거인을 선택하면 나오는 모습입니다.
거인을 선택하여 바로 방을 만들면 방을 선택하는 로비를 거치지 않고 바로 방을 만들고 방으로 들어오게 됩니다.
만들어본 룸 씬의 모습입니다.
그리고 아래는 동작영상입니다.
방 만들기를 누르고, 기다리면 서버와 연결이 되며, 랜덤한 방 번호를 가진 방이 생성되어 그대로 방으로 들어가게 됩니다.
서버와 연결된 순간 로비로 접속을 하게 됩니다.
이때, OnJoinedLobby가 호출이되며, bool 변수 isVR의 값에 따라 isVR이 true면 방을 만들고,
isVR이 false면 로비씬으로 넘어갑니다.
CreateRoom 함수를 통해, 최대 인원의 수가 5인 방을 만들고, 방의 이름을 Random을 통해 1000에서 9999사이의 숫자로 이름을 만듭니다.
이제 다음으로 PC에 순서를 보겠습니다.
기사를 선택하면 아래와 같은 화면이 나옵니다.
방의 리스트와 비공개 방으로 들어갈 수 있는 화면입니다.
저희는 이거와 다르게, 개인적인 방으로만 들어가는 것을 해보았습니다.
위 동영상에서 기사를 선택하면, 로비씬으로 넘어가게 되고, 거기서 방번호를 입력하고 방으로 들어가게 됩니다.
이때 정보가 갱신이 되어야 하는데, 이에 대한 처리는 RoomMain이라는 스크립트가 처리합니다.
using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class RoomMain : MonoBehaviour
{
[Header("Buttons")]
[SerializeField] private Button btnStart;
[SerializeField] private Button btnLeaveRoom;
[Header("Room Info Text")]
[SerializeField] private TMP_Text txtRoomCode;
[SerializeField] private TMP_Text txtRoomPlayerCount;
[Header("Player Nickname Text")]
[SerializeField] private TMP_Text txtGiantNickname;
[SerializeField] private TMP_Text[] txtKngihtNickname;
// Start is called before the first frame update
void Awake()
{
this.btnStart.onClick.AddListener(() =>
{
NetworkManager.instance.StartGame();
});
this.btnLeaveRoom.onClick.AddListener(() =>
{
NetworkManager.instance.CloseRoom();
});
this.txtRoomCode.text = PhotonNetwork.CurrentRoom.Name;
this.SetGiantNickname();
this.UpdatePcPlayerNickname();
NetworkManager.instance.onJoinRoomAction();
}
private void Start()
{
if (!PhotonNetwork.IsMasterClient)
{
this.btnStart.gameObject.SetActive(false);
}
}
private void UpdatePcPlayerNickname()
{
NetworkManager.instance.onJoinRoomAction = () =>
{
this.SetEnterKnightNickname();
this.txtRoomPlayerCount.text = string.Format("{0}/{1}",
PhotonNetwork.CurrentRoom.PlayerCount - 1, PhotonNetwork.CurrentRoom.MaxPlayers - 1);
};
NetworkManager.instance.onPlayerEnterRoomAction = () =>
{
this.SetEnterKnightNickname();
this.txtRoomPlayerCount.text = string.Format("{0}/{1}",
PhotonNetwork.CurrentRoom.PlayerCount - 1, PhotonNetwork.CurrentRoom.MaxPlayers - 1); //거인 제외
};
NetworkManager.instance.onPlayerLeaveRoomAction = () =>
{
this.SetLeaveKnightNickname();
this.txtRoomPlayerCount.text = string.Format("{0}/{1}",
PhotonNetwork.CurrentRoom.PlayerCount - 1, PhotonNetwork.CurrentRoom.MaxPlayers - 1); //거인 제외
};
}
private void SetGiantNickname()
{
this.txtGiantNickname.text = PhotonNetwork.MasterClient.NickName;
}
private void SetEnterKnightNickname()
{
List<Player> players = new List<Player>();
foreach (var pair in PhotonNetwork.CurrentRoom.Players)
{
var player = pair.Value;
if (player.IsMasterClient == false)
{
players.Add(player); //마스터 제외하고 리스트에 넣기
}
}
for (int i = 0; i < players.Count; i++)
{
var player = players[i];
this.txtKngihtNickname[i].text = player.NickName; //닉네임 세팅
}
}
private void SetLeaveKnightNickname()
{
foreach(var playerNickname in this.txtKngihtNickname)//전부 닉네임 초기화
{
playerNickname.text = string.Format("Empty");
}
//초기화 하고 위에서 부터 다시 세팅
List<Player> players = new List<Player>();
foreach (var pair in PhotonNetwork.CurrentRoom.Players)
{
var player = pair.Value;
if (player.IsMasterClient == false)
{
players.Add(player); //마스터 제외하고 리스트에 넣기
}
}
for (int i = 0; i < players.Count; i++)
{
var player = players[i];
this.txtKngihtNickname[i].text = player.NickName; //닉네임 세팅
}
}
}
들어오는 순간 거인의 닉네임 (정확히는 마스터 클라이언트)을 받아
화면 상에 적용하는 역할을 합니다.
그리고, PC 플레이어가 나가고 들어옴에 따라 화면을 갱신합니다.
또한 Leave Room 키를 누르면 서버와의 연결을 끊고, 처음 씬으로 돌아갑니다.
마지막으로 서버와의 연결을 담당하는 NetworkManager입니다.
using Photon.Pun;
using Photon.Realtime;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class NetworkManager : MonoBehaviourPunCallbacks
{
private readonly string version = "1.0.0";
public static NetworkManager instance;
public Action<string> onJoinRoomRequest;
public Action onJoinRoomAction;
public Action onPlayerEnterRoomAction;
public Action onPlayerLeaveRoomAction;
private bool isVR = false;
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(this.gameObject);
}
else
{
DontDestroyOnLoad(this.gameObject);
NetworkManager.instance = this;
}
this.onJoinRoomRequest = (roomCode) =>
{
PhotonNetwork.JoinRoom(roomCode);
};
}
public void ConnectPC(string nickName)
{
if (!PhotonNetwork.IsConnected)
{
PhotonNetwork.AutomaticallySyncScene = true;
PhotonNetwork.GameVersion = this.version;
PhotonNetwork.NickName = nickName;
PhotonNetwork.ConnectUsingSettings();
this.isVR = false;
}
}
public void ConnectVR(string nickName)
{
if (!PhotonNetwork.IsConnected)
{
Debug.Log("<color=red>VR 연결!</color>");
PhotonNetwork.AutomaticallySyncScene = true;
PhotonNetwork.GameVersion = this.version;
PhotonNetwork.NickName = nickName;
PhotonNetwork.ConnectUsingSettings();
this.isVR = true;
}
}
public override void OnConnectedToMaster()
{
Debug.Log("마스터 서버에 접속");
if(PhotonNetwork.IsConnected) PhotonNetwork.JoinLobby();
}
public override void OnJoinedLobby()
{
Debug.Log("로비에 접속");
if (this.isVR)
{
this.CreateRoom();
}
else
{
this.JoinLobbyScene();
}
}
public override void OnJoinedRoom()
{
Debug.Log("룸에 접속");
SceneManager.LoadSceneAsync("RoomScene");
Debug.LogFormat("방 이름: {0}", PhotonNetwork.CurrentRoom.Name);
Debug.LogFormat("방 접속자 수: {0}", PhotonNetwork.CurrentRoom.PlayerCount);
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
Debug.LogFormat("방에 접속한 인원:{0}", PhotonNetwork.CurrentRoom.PlayerCount);
this.onPlayerEnterRoomAction();
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
Debug.LogFormat("방을 나간 인원:{0}", PhotonNetwork.CurrentRoom.PlayerCount);
this.onPlayerLeaveRoomAction();
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
PhotonNetwork.Disconnect();
}
public override void OnDisconnected(DisconnectCause cause)
{
Debug.Log("Disconnected from server. Cause: " + cause);
Debug.Log("연결 끊김!");
SceneManager.LoadScene("FirstScene");
}
public void CloseRoom()
{
if (PhotonNetwork.IsConnected) PhotonNetwork.Disconnect();
}
private void JoinLobbyScene()
{
SceneManager.LoadSceneAsync("LobbyScene");
}
private void CreateRoom()
{
int rndRoomCode = UnityEngine.Random.Range(1000, 10000); //1000에서 9999까지의 숫자 랜덤 생성
RoomOptions options = new RoomOptions();
options.MaxPlayers = 5;
options.IsOpen = true;
options.IsVisible = false;
PhotonNetwork.CreateRoom(rndRoomCode.ToString(), options);
}
public void StartGame()
{
SceneManager.LoadSceneAsync("GameScene");
}
}
이렇게 방을 연결하는 로직이 완료 되었습니다.
'CGV(Castle Giant Virtual) 프로젝트 일지' 카테고리의 다른 글
CGV 개발일지 13 (중간정리) (0) | 2023.12.14 |
---|---|
CGV 개발일지 12 (맨손으로 pc 플레이어 치기) (0) | 2023.12.14 |
CGV 개발일지 11 (잡은 물체로 pc 플레이어 치기) (0) | 2023.12.14 |
CGV 개발일지 10 (잡은 플레이어 위치 동기화) (0) | 2023.12.14 |
CGV 개발일지 9(잡은 물체 위치 동기화) (0) | 2023.12.13 |