EasyCastleUNITY

CGV 개발일지 6 (멀티, 랜덤한 방 번호 만들기) 본문

CGV(Castle Giant Virtual) 프로젝트 일지

CGV 개발일지 6 (멀티, 랜덤한 방 번호 만들기)

EasyCastleT 2023. 11. 29. 14:28

Photon Fusion 세팅

기존에 자주 사용되던 Pun이 Legacy가 됨에 따라, 최근 Photon 측에서 밀고 있는 Fusion을 통해 멀티를 구현해보고자 합니다. 

 

먼저 Photon Fusion 환경을 세팅해보겠습니다. 

https://www.photonengine.com/

 

Multiplayer Game Development Made Easy | Photon Engine

EssentialPhoton Details Discover a summary of our product range, notable features, the power of the Photon Cloud, and our cost-effective pricing plans. HAVE A LOOK

www.photonengine.com

위 링크로 들어가 상단의 SDK를 클릭합니다. 

위 사진처럼 설정하고 

Get Fusion SDK 버튼을 클릭합니다. 

클릭하여 넘어가면 

위와 같은 화면이 나오고, 해당 sdk를 다운로드 합니다. 

그리고 다운 받은 SDK를 유니티 프로젝트에 Import 합니다. 

Import를 마치면, 위 사진 처럼 Fusion Hub 창이 생기는데, 

이 창에서, 자신이 만든 Fusion APP id를 등록하여 사용하면 됩니다. 

 

앱을 만드는 방법은, 

https://www.photonengine.com/ko-kr

 

글로벌 크로스 플랫폼 실시간 게임 개발 | Photon Engine

EssentialPhoton Details Discover a summary of our product range, notable features, the power of the Photon Cloud, and our cost-effective pricing plans. HAVE A LOOK

www.photonengine.com

로 들어가, 로그인 한 다음 

관리화면으로 이동을 클릭하면 

위와 같은 창이 나오고, 새 애플리케이션 만들기를 누른다음, 

위 사진처럼 Photon 종류를 Fusion을 선택하고 애플리케이션 이름을 작성하고 작성하기 버튼을 클릭합니다. 

그러면 

위 사진처럼 App이 만들어지게 됩니다. 이 app의 id를 복사하여 Fusion Hub의 Setup에 붙여넣으면 fusion 세팅이 완료됩니다. 


https://doc.photonengine.com/ko-kr/fusion/current/tutorials/host-mode-basics/2-setting-up-a-scene

 

2 - 씬 설정하기 | Photon Engine

Fusion 102에서는 기본적인 네트워크된 씬 설정 방법에 대해서 설명합니다. 이 섹션이 끝나면, 프로젝트는 다음 사항들이 포함되어 있게 됩니다: 네트워크 입력의 상세한 설명에 대한 매뉴얼을 참

doc.photonengine.com

세팅이 완료된 후, 위 링크에 있는 기술문서를 참고하며, 만들어보았습니다. 

 

저희 프로젝트에서는 VR 플레이어가 호스트인데, vr 플레이어가 방을 만들면, 랜덤한 방 번호가 지정됩니다. 

이 랜덤한 방 번호를 만들고 접속하는 것을 R&D 해보겠습니다. 

 

먼저 Fusion에서는 실행하기 위해, StartGame 메서드가 NetworkRunner에서 호출이 되어야 합니다. 

또한 저는 Host와 Client가 시작하는 방법이 다르기에 각각 다른 방법으로 연결할 수 있도록 만들어 보겠습니다. 

먼저 공통적으로 가지는 NetworkManager라는 스크립트를 작성하였습니다. 

 

먼저 INetworkRunnerCallbacks 인터페이스를 상속 받아 구현해주었습니다. 

그 다음 어디서나 접근이 가능하게 싱글톤으로 만들어 줍니다. 

그리고 NetworkRunner를 받아오고 있으므로 NetworkManager 게임 오브젝트의 NetworkRunner 컴포넌트를 assign합니다. 

그리고 플레이어 들어옴과 나가는 것을 파악하기 위해 OnPlayerJoined와 OnPlayerLeft를 사용하여, 로그를 찍습니다. 

using Fusion;
using Fusion.Sockets;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NetworkManager : MonoBehaviour, INetworkRunnerCallbacks
{
    public static NetworkManager Instance {  get; private set; }
    public NetworkRunner Runner { get => runner;}

    private NetworkRunner runner;

    private void Awake()
    {
        //있으면서, 있는 것이 내가 아니면(다른애임)
        if (Instance != null && Instance !=this) Destroy(this.gameObject);
        else Instance = this;

        runner = GetComponent<NetworkRunner>();
        runner.AddCallbacks(this);
    }

    public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
    {
        if (runner.IsServer && player == runner.LocalPlayer)
        {
            Debug.Log("Host 들어옴");
        }
        else
        {
            Debug.Log("Client 들어옴");
        }
    }

    public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
    {
        if (runner.IsServer && player == runner.LocalPlayer)
        {
            Debug.Log("Host 나감");
        }
        else
        {
            Debug.Log("Client 나감");
        }
    }

    #region Not use Runner Callbacks
    public void OnConnectedToServer(NetworkRunner runner)
    {
        
    }

    public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason)
    {
       
    }

    public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token)
    {
        
    }

    public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data)
    {
        
    }

    public void OnDisconnectedFromServer(NetworkRunner runner)
    {
        
    }

    public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken)
    {
        
    }

    public void OnInput(NetworkRunner runner, NetworkInput input)
    {
        
    }

    public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input)
    {
        
    }

    
    public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data)
    {
        
    }

    public void OnSceneLoadDone(NetworkRunner runner)
    {
        
    }

    public void OnSceneLoadStart(NetworkRunner runner)
    {
       
    }

    public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
    {
       
    }

    public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
    {
    
    }

    public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message)
    {
      
    }
    #endregion
}

이렇게 기본적인 NetworkManager가 만들어집니다. 

 

그리고 Host와 Client가 달라야 되기에 StartGame하는 방식을 다르게 해보겠습니다. 

 

Host StartGame

using Fusion;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class HostMain : MonoBehaviour
{
    [SerializeField] private GameMode gameMode;
    [SerializeField] private Button btnEnterRoom;
    [SerializeField] private string roomName;
    [SerializeField] private TMP_Text txtRoomNum;
    // Start is called before the first frame update
    void Start()
    {
        this.btnEnterRoom.onClick.AddListener(() =>
        {
            Debug.LogFormat("Enter Room : {0}", gameMode);
            StartGame();
        });
    }

    private async void StartGame()
    {
        Debug.Log("StartGame");
        int roomNumber = UnityEngine.Random.Range(1000, 10000); //1000~9999
        this.roomName = roomNumber.ToString();
        this.txtRoomNum.text = roomNumber.ToString();
        var args = new StartGameArgs()
        {
            GameMode = gameMode,
            SessionName = roomName,
            SceneManager = this.gameObject.AddComponent<NetworkSceneManagerDefault>(),
            Scene = SceneManager.GetActiveScene().buildIndex
        };
        var result = await NetworkManager.Instance.Runner.StartGame(args);
        if (result.Ok)
        {
            Debug.Log("OK");
        }
        else
        {
            Debug.Log(result.ErrorMessage);
        }
    }
}

HostMain을 위와 같이 작성하고, Scene을 

이 상태로 만들었습니다. 

 

Client StartGame

using Fusion;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class ClientMain : MonoBehaviour
{
    [SerializeField] private GameMode gameMode;
    [SerializeField] private Button btnEnterRoom;
    [SerializeField] private string roomName;
    [SerializeField] private TMP_InputField input;
    // Start is called before the first frame update
    void Start()
    {
        this.btnEnterRoom.onClick.AddListener(() =>
        {
            Debug.LogFormat("Enter Room : {0}", gameMode);
            this.roomName = this.input.text;
            StartGame();
        });
    }

    private async void StartGame()
    {
        Debug.Log("StartGame");
 
        var args = new StartGameArgs()
        {
            GameMode = gameMode,
            SessionName = roomName,
            SceneManager = this.gameObject.AddComponent<NetworkSceneManagerDefault>(),
            Scene = SceneManager.GetActiveScene().buildIndex
        };
        var result = await NetworkManager.Instance.Runner.StartGame(args);
        if (result.Ok)
        {
            Debug.Log("OK");
        }
        else
        {
            Debug.Log(result.ErrorMessage);
        }
    }


}

Client  Main을 위와 같이 작성하고 

이 상태로 만들었습니다. 

 

그리고 시험해보기 위해, Client 모드인 상태로 빌드를 하였습니다. 

 

 

이렇게 들어가면 랜덤한 방 번호로 방이 생성되고, Client가 들어가고 나오는 것이 찍히는 것을 확인할 수 있었습니다.