유니티에서 Azure PlayFab 활용하기 - PlayFab Multiplay

Published: Jul 4, 2022 by BeatChoi

Azure Playfab

이번 포스팅에서는 중앙서버를 활용한 멀티플레이를 구현해 봅니다.
이전 네트워크 포스팅 중 Mirror Network를 활용합니다.

Setting

이전 포스팅을 참고하여 Playfab for UnitySDK를 새로 생성한 프로젝트에 적용시킵니다.

로컬 네트워크 상에서의 멀티플레이가 아닌 Azure Playfab을 중앙서버로 두는 멀티플레이를 구현하기 위해 몇가지 설정을 해줍니다.
먼저
GSDK for Unity
위 깃허브 링크에서 GSDK를 다운로드 해줍니다.


<01. GSDK 다운로드 >

다운로드 받은 파일의 압축을 풀고 UnityGsdk -> Assets -> PlayFabSdk에 있는 MultiplayerAgent 폴더를 통채로 유니티 프로젝트 창의 PlayFabSDK 폴더로 복사합니다.


<02. GSDK 임포트 >

Player Settings창에서 Scripting Define Symbols항목을 찾아 ENABLE_PLAYFABSERVER_API을 추가해줍니다.


<03. ENABLE_PLAYFABSERVER_API >

Unity3D

Script

Configuration.cs 스크립트를 생성하고 다음과 같이 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Configuration : MonoBehaviour
{
	public BuildType buildType;
	public string buildId = "";
	public string ipAddress = "";
	public ushort port = 0;
	public bool playFabDebugging = false;
}

public enum BuildType
{
	REMOTE_CLIENT,
	REMOTE_SERVER
}
  • 해당프로젝트가 서버인 경우, 클라이언트인 경우 각각을 구분짓기 위한 스크립트 입니다.

UnityNetworkServer.cs스크립트를 생성하고 다음을 붙여넣습니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
namespace PlayFab.Networking
{
    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using Mirror;
    using UnityEngine.Events;

    public class UnityNetworkServer : NetworkManager
    {
        public static UnityNetworkServer Instance { get; private set; }

        public PlayerEvent OnPlayerAdded = new PlayerEvent();
        public PlayerEvent OnPlayerRemoved = new PlayerEvent();


        public List<UnityNetworkConnection> Connections {
            get { return _connections; }
            private set { _connections = value; }
        }
        private List<UnityNetworkConnection> _connections = new List<UnityNetworkConnection>();

        public class PlayerEvent : UnityEvent<string> { }

        // Use this for initialization
        public override void Awake()
        {
            base.Awake();
            Instance = this;
            NetworkServer.RegisterHandler<ReceiveAuthenticateMessage>(OnReceiveAuthenticate);
        }

        public void StartListen()
        {
            NetworkServer.Listen(maxConnections);
        }

        public override void OnApplicationQuit()
        {
            base.OnApplicationQuit();
            NetworkServer.Shutdown();
        }

        private void OnReceiveAuthenticate(NetworkConnection nconn, ReceiveAuthenticateMessage message)
        {
            var conn = _connections.Find(c => c.ConnectionId == nconn.connectionId);
            if (conn != null)
            {
                conn.PlayFabId = message.PlayFabId;
                conn.IsAuthenticated = true;
                OnPlayerAdded.Invoke(message.PlayFabId);
            }
        }

        public override void OnServerConnect(NetworkConnectionToClient conn)
        {
            base.OnServerConnect(conn);

            Debug.LogWarning("Client Connected");
            var uconn = _connections.Find(c => c.ConnectionId == conn.connectionId);
            if (uconn == null)
            {
                _connections.Add(new UnityNetworkConnection()
                {
                    Connection = conn,
                    ConnectionId = conn.connectionId,
                    LobbyId = PlayFabMultiplayerAgentAPI.SessionConfig.SessionId
                });
            }
        }

        public override void OnServerError(NetworkConnectionToClient conn, Exception ex)
        {
            base.OnServerError(conn, ex);

            Debug.Log(string.Format("Unity Network Connection Status: exception - {0}", ex.Message));
        }

        public override void OnServerDisconnect(NetworkConnectionToClient conn)
        {
            base.OnServerDisconnect(conn);

            var uconn = _connections.Find(c => c.ConnectionId == conn.connectionId);
            if (uconn != null)
            {
                if (!string.IsNullOrEmpty(uconn.PlayFabId))
                {
                    OnPlayerRemoved.Invoke(uconn.PlayFabId);
                }
                _connections.Remove(uconn);
            }
        }
    }

    [Serializable]
    public class UnityNetworkConnection
    {
        public bool IsAuthenticated;
        public string PlayFabId;
        public string LobbyId;
        public int ConnectionId;
        public NetworkConnection Connection;
    }

    public class CustomGameServerMessageTypes
    {
        public const short ReceiveAuthenticate = 900;
        public const short ShutdownMessage = 901;
        public const short MaintenanceMessage = 902;
    }

    public struct ReceiveAuthenticateMessage : NetworkMessage
    {
        public string PlayFabId;
    }

    public struct ShutdownMessage : NetworkMessage { }

    [Serializable]
    public struct MaintenanceMessage : NetworkMessage
    {
        public DateTime ScheduledMaintenanceUTC;
    }
}  
  • Mirror Network의 Network Manager 역할을 하는 스크립트입니다.

AgentListener.csClientStartUp.cs 스크립트를 생성하고 각각 다음과 같이 수정합니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System.Collections;
using UnityEngine;
using PlayFab;
using System;
using PlayFab.Networking;
using System.Collections.Generic;
using PlayFab.MultiplayerAgent.Model;
using Mirror;

public class AgentListener : MonoBehaviour
{

	public Configuration configuration;

	private List<ConnectedPlayer> _connectedPlayers;
	public UnityNetworkServer UNetServer;


	void Start()
	{
		if (configuration.buildType == BuildType.REMOTE_SERVER)
		{
			StartRemoteServer();
		}
	}


	private void StartRemoteServer()
	{
		Debug.Log("[ServerStartUp].StartRemoteServer");
		_connectedPlayers = new List<ConnectedPlayer>();
		PlayFabMultiplayerAgentAPI.Start();
		PlayFabMultiplayerAgentAPI.IsDebugging = configuration.playFabDebugging;
		PlayFabMultiplayerAgentAPI.OnMaintenanceCallback += OnMaintenance;
		PlayFabMultiplayerAgentAPI.OnShutDownCallback += OnShutdown;
		PlayFabMultiplayerAgentAPI.OnServerActiveCallback += OnServerActive;
		PlayFabMultiplayerAgentAPI.OnAgentErrorCallback += OnAgentError;

		UNetServer.OnPlayerAdded.AddListener(OnPlayerAdded);
		UNetServer.OnPlayerRemoved.AddListener(OnPlayerRemoved);

		StartCoroutine(ReadyForPlayers());
		StartCoroutine(ShutdownServerInXTime());
	}

	IEnumerator ShutdownServerInXTime()
	{
		yield return new WaitForSeconds(300f);
		StartShutdownProcess();
	}

	IEnumerator ReadyForPlayers()
	{
		yield return new WaitForSeconds(.5f);
		PlayFabMultiplayerAgentAPI.ReadyForPlayers();
	}

	private void OnServerActive()
	{
		UNetServer.StartServer();
		Debug.Log("Server Started From Agent Activation");
	}

	private void OnPlayerRemoved(string playfabId)
	{
		ConnectedPlayer player = _connectedPlayers.Find(x => x.PlayerId.Equals(playfabId, StringComparison.OrdinalIgnoreCase));
		_connectedPlayers.Remove(player);
		PlayFabMultiplayerAgentAPI.UpdateConnectedPlayers(_connectedPlayers);
		CheckPlayerCountToShutdown();
	}

	private void CheckPlayerCountToShutdown()
	{
		if (_connectedPlayers.Count <= 0)
		{
			StartShutdownProcess();
		}
	}

	private void OnPlayerAdded(string playfabId)
	{
		_connectedPlayers.Add(new ConnectedPlayer(playfabId));
		PlayFabMultiplayerAgentAPI.UpdateConnectedPlayers(_connectedPlayers);
	}

	private void OnAgentError(string error)
	{
		Debug.Log(error);
	}

	private void OnShutdown()
	{
		StartShutdownProcess();
	}

	private void StartShutdownProcess()
	{
		Debug.Log("Server is shutting down");
		foreach (var conn in UNetServer.Connections)
		{
			conn.Connection.Send(new ShutdownMessage());
		}
		StartCoroutine(ShutdownServer());
	}

	IEnumerator ShutdownServer()
	{
		yield return new WaitForSeconds(5f);
		Application.Quit();
	}

	private void OnMaintenance(DateTime? NextScheduledMaintenanceUtc)
	{
		Debug.LogFormat("Maintenance scheduled for: {0}", NextScheduledMaintenanceUtc.Value.ToLongDateString());
		foreach (var conn in UNetServer.Connections)
		{
			conn.Connection.Send(new MaintenanceMessage()
			{
				ScheduledMaintenanceUTC = (DateTime)NextScheduledMaintenanceUtc
			});
		}
	}
}  
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayFab;
using System;
using PlayFab.ClientModels;
using PlayFab.MultiplayerModels;
using Mirror;

public class ClientStartUp : MonoBehaviour
{
	public static ClientStartUp Instance;
	public Configuration configuration;
	public AgentListener serverStartUp;
	public NetworkManager networkManager;
	public kcp2k.KcpTransport KCPTransport;
	public TelepathyTransport telepathyTransport;
	//public ApathyTransport apathyTransport;
	string playfabID;

	private void Awake()
	{
		Instance = this;
	}

		public void OnLoginUserButtonClick()
	{
		if (configuration.buildType == BuildType.REMOTE_CLIENT)
		{
			if (configuration.buildId == "")
			{
				throw new Exception("A remote client build must have a buildId. Add it to the Configuration. Get this from your Multiplayer Game Manager in the PlayFab web console.");
			}
			else
			{
				LoginRemoteUser();
			}
		}
	}

	public void SignInWithEmail(string emailaddress, string password)
	{
		PlayFabClientAPI.LoginWithEmailAddress(new LoginWithEmailAddressRequest()
		{
			Email = emailaddress,
			Password = password
		},
		response =>
		{
			Debug.Log($"Successful Account Login: {emailaddress}");
			playfabID = response.PlayFabId;
			/*if(string.IsNullOrEmpty(CharacterSave.charactername)) //At first, choose a character from the character scene. If there's a character, it's a mega scene
			{
				UnityEngine.SceneManagement.SceneManager.LoadScene("ChooseCharacter");
			}
			else
			{
				UnityEngine.SceneManagement.SceneManager.LoadScene("MegaVRse_Multiplay");
			}*/
			UnityEngine.SceneManagement.SceneManager.LoadScene("MegaVRse_Multiplay");
			OnPlayFabLoginSuccess(response);
		},
		error =>
		{
			Debug.Log($"Unsuccessful Account Creation: {emailaddress} \n {error.ErrorMessage}");
		}
		);
	}

	public void SignIn(string username, string password)
	{
		PlayFabClientAPI.LoginWithPlayFab(new LoginWithPlayFabRequest()
		{
			Username = username,
			Password = password
		},
		response =>
		{
			Debug.Log($"Successful Account Login: {username}");
			playfabID = response.PlayFabId;
			UnityEngine.SceneManagement.SceneManager.LoadScene("MegaVRse_Multiplay");

            OnPlayFabLoginSuccess(response);
			
		},
		error =>
		{
			Debug.Log($"Unsuccessful Account Creation: {username} \n {error.ErrorMessage}");
		});
	}



	public void LoginRemoteUser()
	{
		Debug.Log("[ClientStartUp].LoginRemoteUser");
		
		//We need to login a user to get at PlayFab API's. 
		LoginWithCustomIDRequest request = new LoginWithCustomIDRequest()
		{
			TitleId = PlayFabSettings.TitleId,
			CreateAccount = true,
			CustomId = GUIDUtility.getUniqueID()
		};

		PlayFabClientAPI.LoginWithCustomID(request, OnPlayFabLoginSuccess, OnLoginError);
	}

	private void OnLoginError(PlayFabError response)
	{
		Debug.Log(response.ToString());
	}

	private void OnPlayFabLoginSuccess(LoginResult response)
	{
		Debug.Log(response.ToString());
		if (configuration.ipAddress == "")
		{   //We need to grab an IP and Port from a server based on the buildId. Copy this and add it to your Configuration.
			RequestMultiplayerServer(); 
		}
		else
		{
			ConnectRemoteClient();
		}
	}

	private void RequestMultiplayerServer()
	{
		Debug.Log("[ClientStartUp].RequestMultiplayerServer");
		RequestMultiplayerServerRequest requestData = new RequestMultiplayerServerRequest();
		requestData.BuildId = configuration.buildId;
		requestData.SessionId = System.Guid.NewGuid().ToString();
		requestData.PreferredRegions = new List<string> { "EastUs" };
		Debug.Log(requestData.BuildId);
		Debug.Log(requestData.SessionId);
		Debug.Log(requestData.PreferredRegions[0]);
		PlayFabMultiplayerAPI.RequestMultiplayerServer(requestData, OnRequestMultiplayerServer, OnRequestMultiplayerServerError);
	}

	private void OnRequestMultiplayerServer(RequestMultiplayerServerResponse response)
	{
		Debug.Log(response.ToString());
		ConnectRemoteClient(response);
	}

	private void ConnectRemoteClient(RequestMultiplayerServerResponse response = null)
	{
		if(response == null) 
		{
			networkManager.networkAddress = configuration.ipAddress;
			//KCPTransport.Port = configuration.port;
			telepathyTransport.port = configuration.port;
			//.port = configuration.port;
			//apathyTransport.port = configuration.port;
		}
		else
		{
			Debug.Log("**** ADD THIS TO YOUR CONFIGURATION **** -- IP: " + response.IPV4Address + " Port: " + (ushort)response.Ports[0].Num);
			networkManager.networkAddress = response.IPV4Address;
			//KCPTransport.Port = (ushort)response.Ports[0].Num;
			telepathyTransport.port = (ushort)response.Ports[0].Num;
			//telepathyTransport.port = (ushort)response.Ports[0].Num;
			//apathyTransport.port = (ushort)response.Ports[0].Num;
		}

		networkManager.StartClient();
	}

	private void OnRequestMultiplayerServerError(PlayFabError error)
	{
		Debug.Log(error.ErrorDetails);
	}
}                                    

GUIDUtility.csPlayerPrefsUtility.cs 스크립트를 생성하고 각각 다음과 같이 작성합니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

public static class GUIDUtility
{

    public static string getUniqueID(bool generateNewIDState = false)
    {
        string uniqueID;

        if (PlayerPrefsUtility.hasKey("guid") && !generateNewIDState)
        {
            uniqueID = PlayerPrefsUtility.getString("guid");
        }
        else
        {
			uniqueID = generateGUID();
			PlayerPrefsUtility.setString("guid", uniqueID);
        }

        return uniqueID;
    }

	public static string generateGUID()
	{
		var random = new System.Random();
		DateTime epochStart = new System.DateTime(1970, 1, 1, 8, 0, 0, System.DateTimeKind.Utc);
		double timestamp = (System.DateTime.UtcNow - epochStart).TotalSeconds;

		string uniqueID = String.Format("{0:X}", Convert.ToInt32(timestamp))                //Time
						+ "-" + String.Format("{0:X}", random.Next(1000000000))                   //Random Number
						+ "-" + String.Format("{0:X}", random.Next(1000000000))                 //Random Number
						+"-" + String.Format("{0:X}", random.Next(1000000000))                  //Random Number
						+"-" + String.Format("{0:X}", random.Next(1000000000));                  //Random Number

		Debug.Log(uniqueID);

		return uniqueID;
	}

}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

public static class PlayerPrefsUtility
{

    public static void setString(string key, string value)
    {
        PlayerPrefs.SetString(key, value);
        PlayerPrefs.Save();
    }

    public static void setInt(string key, int value)
    {
        PlayerPrefs.SetInt(key, value);
        PlayerPrefs.Save();
    }

    public static void setFloat(string key, float value)
    {
        PlayerPrefs.SetFloat(key, value);
        PlayerPrefs.Save();
    }

    public static void setBool(string key, bool value)
    {
        int intValue = 0; //Default is FALSE
        if (value) intValue = 1; //If value is TRUE, then it's a 1.
        PlayerPrefs.SetInt(key, intValue);
        PlayerPrefs.Save();
    }


    //The methods below are silly, but the above ones have a bit more validity, so I wanted to have everything wrapped at that point.
    public static string getString(string key)
    {
        return PlayerPrefs.GetString(key);
    }

    public static float getFloat(string key)
    {
        return PlayerPrefs.GetFloat(key);
    }

    public static int getInt(string key)
    {
        return PlayerPrefs.GetInt(key);
    }

    public static bool getBool(string key)
    {
        int intValue = PlayerPrefs.GetInt(key);
        if (intValue >= 1) return true; //If it's 1, then TRUE. The greater than is just a sanity check.
        return false; //Default is FALSE which should be 0.
    }

    public static bool hasKey(string key)
    {
        return PlayerPrefs.HasKey(key);
    }


}  

Scene

계층구조창에서 Configuration이라는 이름의 빈 게임 오브젝트를 생성합니다.
해당 오브젝트에 Configuration.cs스크립트를 연결시켜 인스턴스화 해줍니다.


<04. Configuration >

계층구조창에서 UnityNetworkServer라는 이름의 빈 게임 오브젝트를 생성합니다.
해당 오브젝트에 UnityNetworkServer.cs스크립트를 연결시켜 인스턴스화 해줍니다.


<05. UnityNetworkServer >

UnityNetworkServer.cs스크립트를 인스턴스 시키면 아래에 KCP Transport라는 컴포넌트도 같이 생성됩니다.
해당 트랜스포트는 UDP 네트워크의 일종입니다.
해당 컴포넌트를 제거하고 Telepathy Transport를 추가해줍니다. TelepathyTCP/IP의 일종입니다.


<06. Telepathy Transport >

UnityNetworkServer 오브젝트의 하위에 AgentListener라는 이름의 빈게임 오브젝트를 생성합니다. 해당 오브젝트에 AgentListener.cs 스크립트를 인스턴스화 해줍니다.


<07. AgentListener >

AgentListener오브젝트를 선택하고 우측 인스펙터 창의 AgentListner.cs 스크립트 컴포넌트를 확인합니다.
Configuration, UNetServer항목에 각각 계층구조창의 Configuration오브젝트 및 UnityNetworkServer 오브젝트를 연결합니다.


<08. 컴포넌트 확인 >

UnityNetworkServer오브젝트 하위에 ClientStartUp이라는 이름의 빈 게임 오브젝트를 생성합니다.
해당 오브젝트에 ClientStartUp.cs스크립트를 인스턴스화 시켜줍니다.


<09. Client Start Up >

ClientStartUp오브젝트를 선택하고 우측 인스펙터창의 ClientStartUp컴포넌트를 확인합니다.
빈 항목들을 다음과 같이 채워넣습니다.


<10. Client Start Up Component >

마지막으로 Player 캐릭터 프리펩을 만들겠습니다.
계층구조창에서 Cube오브젝트를 생성하고 해당 오브젝트에 Network Identiy컴포넌트를 추가합니다.


<11. Player Prefab >

Cube 오브젝트를 프리펩으로 만들고 해당 프리펩을 UnityNetworkServer오브젝트의 Player Prefab항목에 끌어놓습니다.
계층구조창의 Cube 오브젝트는 제거합니다.


<12. Player 추가 >

Unity Server Build

Playfab Multiplayer 항목에 업로드할 Server Build를 진행합니다.
해당 프로젝트의 Build Setting창에서 Platform항목의 Dedicated Server플랫폼을 선택하고 변경합니다.
해당 플랫폼은 Unity Hub에서 미리 설치바랍니다.


<13. Dedicated Server Build >

계층구조창에서 Configuration오브젝트를 선택하고 인스펙터창의 Configuration에서 Build TypeREMOTE_SERVER인지 확인합니다.
나머지 칸은 비워둡니다.


<14. Configuration >

확인되면 빌드를 진행합니다.


<15. Build >

빌드가 완료되면 해당폴더 안에 모든 파일들을 압축시켜줍니다.


<16. Zip >

Playfab Dashboard

Playfab Dashboard에 로그인을 합니다.
본인의 프로젝트에 들어가서 좌측 Build항목에 Multiplayer항목을 클릭합니다.


<17. Playfab Dashboard Multiplayer >

Multiplayer항목을 활성화 시키기 위해서는 Add Credit Card버튼을 눌러 신용카드를 등록해야합니다.
한달에 750시간 미만으로 활용한다면 무료로 쓸 수 있습니다.
활성화를 완료하면 Multiplayer항목을 클릭했을 때 다음과 같은 메뉴가 나타나게 됩니다.


<18. Multiplayer 항목 활성화 >

좌측 New build버튼을 눌러 서버를 생성합니다.


<19. Playfab Server Build_01 >

먼저 Build Name에 빌드 이름을 지어주고 Virtual Machine 항목을 Dasv4 2Core로 선택합니다.
그리고 아래로 내려와 Asset항목에 아까 유니티에서 빌드하고 압축했던 합축 파일을 업로드합니다.
그 하단에 Start command에는 C:\Assets\서버빌드.exe 경로를 작성해야 하는데 서버빌드.exe 는 아까 압축했던 파일 중 실행파일의 이름을 작성하면 됩니다.
보통 유니티 디폴트값으로 프로젝트이름.exe로 되어있습니다.


<20. Playfab Server Build_02 >

하단에 Region항목에 +Add Region버튼을 누르고 Stanby ServerMax Server항목을 각각 1로 지정합니다.
지역은 East US로 유지합니다.
완료되면 Add build버튼을 눌러줍니다.


<21. Playfab Server Build_03 >

빌드가 업로드 중이라면 Playfab Dashboard에서 Multiplayer 항목을 클릭했을 때 다음과 같이 Deploying 상태일 것입니다.
성공적으로 업로드가 완료되면 Deployed로 변경됩니다.


<22. Playfab Server Build_04 >


<23. Playfab Server Build_05 >

LogIn 구현

멀티플레이 서버에 접속하기위해서는 이전 포스팅의 로그인을 구현해야 합니다.
https://beatchoi.github.io/unity3d/fundamentals/2022/05/26/Playfab03/를 참조해서 구현하면 됩니다.

테스트

빌드 업로드 까지 완료가 되었으면 유니티에서 세팅을 해봅니다.
유니티 씬의 Configuration 오브젝트를 선택하고 Configuration 컴포넌트의 Build Type항목을 REMOTE_CLIENT로 변경합니다.
그리고 하단 Build Id부분에는 Playfab의 Multiplayer항목을 클릭한 창의 빌드된 서버 하단의 문자열을 붙여넣어 줍니다.
일단 IP Address와 Port 항목은 비워둡니다.
상단 플레이 버튼을 눌러봅니다.


<24. Configuration >

아이디와(이메일) 비밀번호를 넣고 로그인에 성공하게 되면 다음과 같은 로그창이 띄워지게 됩니다.
접속해야 할 IP와 Port번호를 확인하고 플레이를 끈 후 Configuration 항목에 작성합니다.


<25. Final >

로그인에 성공하고 Player오브젝트인 Cube가 성공적으로 띄워지면 Remote Server 접속 성공입니다.

Latest Posts

콘텐츠 개발 기법 - URP 환경에서 Bloom 및 Volume 포함한 화면 캡처
콘텐츠 개발 기법 - URP 환경에서 Bloom 및 Volume 포함한 화면 캡처

화면 캡처 :: Screen Capture

일반적으로 URP환경에서 Bloom 등의 Volume 이펙트들이 함께 캡처되지 않는 현상이 일어납니다.
두가지 원인이 있는데 첫번째는 저장할 Texture 및 Texture2D의 크기가 작아 모든 텍스쳐를 저장할 수 없는 경우와
두번째는 Linear color space의 픽셀을 텍스쳐에 저장 할 수 없는 경우가 있습니다.
이번 포스팅에서는 URP 환경에서 해당 이펙트들을 함께 캡쳐하는 방법을 알아봅니다.

콘텐츠 개발 기법 - UI 안나오게 화면 캡처(Screen Capture without UI)
콘텐츠 개발 기법 - UI 안나오게 화면 캡처(Screen Capture without UI)

화면 캡처 :: Screen Capture

UI 없이 화면을 캡쳐하는 방법을 알아봅니다.
해당 방법을 통해 사진 앱에서 사진을 찍거나 게임 내에서 UI를 제거한 스크린샷을 구현할 수 있습니다.

유니티3D 에디터에서

스크립트 작성

LightshipAR SDK 활용하기 - LightshipAR VPS의 활용
LightshipAR SDK 활용하기 - LightshipAR VPS의 활용

LightshipAR SDK

이번 강좌에서는 Visual Positioning System (VPS) 기능을 활용하여 콘텐츠를 개발해 봅니다.
VPS는 GPS정보와 타겟 매핑 정보를 정합하여 해당 타겟을 인식하는 기능입니다.
따라서 객체나 환경의 변화 및 이동이 잦지 않은 타겟이 스캔 대상이 됩니다.
주로 동상, 건물 입구, 가로등 등 위치의 변경이 없는 타겟들이 좋은 타겟입니다.

해당 타겟들을 인식시키고 주변에 가상의 오브젝트들을 배치하고 해당 위치를 저장하는 방법을 알아봅니다.

공간 매핑

Niantic Wayfarer

공간을 인식시키기 위해서 먼저 공간을 매핑해야 합니다.
이 과정은 IOS 어플리케이션으로 진행을 할 수 있는데 아래 링크에서

  1. Testflight
  2. Wayfarer

어플리케이션을 다운로드 및 설치합니다.

링크 : https://testflight.apple.com/join/VXu1F2jf