Devlog) Taxi Game 3D) 25) 서버 게시, 게임 빌드
몽고DB 아틀라스 설정
몽고DB 아틀라스에 접속하여 무료 클러스터(저장소?)를 추가하였습니다.
동시에 보안 설정에서 제 PC의 IP주소를 등록하였습니다.
MongoDB Compass를 이용하여 새로 추가된 클러스터에 잘 접속 되는지 확인해 보았고, 잘 접속 되었습니다.
다음으로 제가 만든 서버도 DB주소를 바꿔도 잘 되는지 확인해 보았습니다.
서버 프로젝트에서 appsettings.AzureDev.json 파일 추가한 후, DB 주소를 새로 만든 클러스터의 주소로 수정하였습니다.
서버를 실행했을 때 appsettings.AzureDev.json 파일을 이용하도록 만들기 위해 Properties/launchSettings.json 파일의 ASPNETCORE_ENVIRONMENT 수정하였습니다.
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "AzureDev"
}
appsettings.AzureDev.json 파일에는 민감한 정보가 있기 때문에 .gitignore 파일에 등록하여 깃허브에 업로드 되지 않도록 않도록 처리하였습니다.
.vs
TaxiGame3D.Server/bin
TaxiGame3D.Server/obj
TaxiGame3D.Server/appsettings.AzureDev.json
서버를 실행한 후, 파이썬 스크립트를 이용하여 모든 템플릿을 DB에 등록해보았습니다.
새로 추가한 클러스터에 아무 문제 없이 DB가 추가되었습니다.
Azure 설정
https://portal.azure.com/ 에 접속하여 무료 웹 앱을 새로 추가하였습니다.
Azure 웹 앱에서도 MongoDB Atlas에 잘 접속할 수 있도록 아웃바운드 IP주소를 등록하였습니다.
Azure 웹 앱에 배포된 서버는 appsettings.AzureDev.json 파일에 입력된 설정을 사용하도록 ASPNETCORE_ENVIRONMENT 설정하였습니다.
Visiual Studio 에서 서버 게시를 할 수 있도록 게시 프로필 생성하였습니다.
민감한 정보 때문에 .gitignore 파일에 게시 프로필 경로를 입력하여 깃허브에 업로드 되지 않도록 처리하였습니다.
.vs
TaxiGame3D.Server/bin
TaxiGame3D.Server/obj
TaxiGame3D.Server/appsettings.AzureDev.json
TaxiGame3D.Server/Properties/PublishProfiles
TaxiGame3D.Server/Properties/ServiceDependencies
Azure에 게시된 서버에서도 Swagger를 이용하여 테스트할 수 있도록 Program.cs 수정하였습니다.
//if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Azure에 서버를 게시한 후 Swagger를 이용하여 서버와 DB가 잘 동작하는지 확인하였습니다.
/Template/Versions를 실행하여 미리 등록했던 템플릿 버전확인을 해 보았습니다.
클라이언트 수정/테스트
접속할 서버를 변경할 때마다 ClientManager 프리팹에서 서버 주소를 직접 수정하지 않도록 ClientSettings 스크립트 추가하였습니다.
ClientSettings는 ScriptableObject를 상속받아 .asset 파일로 만들 수 있도록 구현하였습니다.
[CreateAssetMenu(menuName = "TaxiGame/ClientSettings")]
public class ClientSettings : ScriptableObject
{
[field: SerializeField]
public string Enviroment
{
get;
private set;
}
[field: SerializeField]
public string ServerUri
{
get;
private set;
}
}
ClientSettings 타입의 AzureDev.asset, LocalDev.asset 파일을 추가하여 기존에 사용하던 로컬주소와 Azure 웹앱 주소를 등록하였습니다.
ClientManager 프리팹은 ClientSettings 파일을 이용하여 접속할 서버를 선택할 수 있도록 수정하였습니다.
public class ClientManager : MonoBehaviour
{
[SerializeField]
ClientSettings settings;
public static ClientManager Instance
{
get;
private set;
}
public string Enviroment => settings.Enviroment;
public HttpContext Http
{
get;
private set;
}
public AuthService AuthService
{
get;
private set;
}
public UserService UserService
{
get;
private set;
}
public TemplateService TemplateService
{
get;
private set;
}
void Awake()
{
Instance = this;
DontDestroyOnLoad(gameObject);
Http = new(settings.ServerUri);
AuthService = gameObject.AddComponent<AuthService>();
UserService = gameObject.AddComponent<UserService>();
TemplateService = gameObject.AddComponent<TemplateService>();
}
public static void CreateInstance()
{
if (Instance != null)
return;
Instantiate(Resources.Load(nameof(ClientManager)));
}
[ContextMenu("Reset Template Versions")]
void ResetTemplateVersions() => TemplateService.ResetTemplateVersions(Enviroment);
[ContextMenu("Reset Saved Auth")]
void ResetSavedAuth() => AuthService.ResetSavedAuth();
}
Azure에 게시했던 서버에 잘 접속 되는지 테스트해봤습니다.
자동 로그인 실패 후 로그인 UI가 출력되지 않는 현상 발생하여 로그를 추가하여 원인을 찾아 보았습니다.
제 PC에서 실행한 서버와 통신하였을 때는 Exception이 발생해도 결과값을 잘 반환하였지만 Azure에 게시했던 서버와 통신하였을 때는 Exception이 발생하면 결과값을 반환하지 못 했습니다.
로그를 계속 입력하여 확인했을 때 UniTask 에서 다르게 처리하는 것 같다고 의심이 되었지만 더 자세히 확인하지 않고, HttpContext 스크립트에서 서버에 요청하는 구간은 try~catch로 감싸줘서 어떤 문제가 발생하여도 결과값은 반환하도록 수정하였습니다.
public async UniTask<HttpStatusCode> Get(string subUri)
{
using (var req = UnityWebRequest.Get($"{BaseUri}{subUri}"))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (HttpStatusCode)req.responseCode;
}
}
public async UniTask<(HttpStatusCode, T)> Get<T>(string subUri)
{
using (var req = UnityWebRequest.Get($"{BaseUri}{subUri}"))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (
(HttpStatusCode)req.responseCode,
FromJsonSafety<T>(req.downloadHandler.text)
);
}
}
public async UniTask<HttpStatusCode> Post(string subUri, object data)
{
using (var req = UnityWebRequest.Post($"{BaseUri}{subUri}", ToJsonSafety(data), "application/json"))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (HttpStatusCode)req.responseCode;
}
}
public async UniTask<(HttpStatusCode, T)> Post<T>(string subUri, object data)
{
using (var req = UnityWebRequest.Post($"{BaseUri}{subUri}", ToJsonSafety(data), "application/json"))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (
(HttpStatusCode)req.responseCode,
FromJsonSafety<T>(req.downloadHandler.text)
);
}
}
public async UniTask<HttpStatusCode> Put(string subUri, object data)
{
using (var req = UnityWebRequest.Put($"{BaseUri}{subUri}", ToJsonSafety(data)))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
req.SetRequestHeader("Content-Type", "application/json");
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (HttpStatusCode)req.responseCode;
}
}
public async UniTask<(HttpStatusCode, T)> Put<T>(string subUri, object data)
{
using (var req = UnityWebRequest.Put($"{BaseUri}{subUri}", ToJsonSafety(data)))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
req.SetRequestHeader("Content-Type", "application/json");
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (
(HttpStatusCode)req.responseCode,
FromJsonSafety<T>(req.downloadHandler.text)
);
}
}
public async UniTask<HttpStatusCode> Delete(string subUri)
{
using (var req = UnityWebRequest.Delete($"{BaseUri}{subUri}"))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (HttpStatusCode)req.responseCode;
}
}
public async UniTask<(HttpStatusCode, T)> Delete<T>(string subUri)
{
using (var req = UnityWebRequest.Delete($"{BaseUri}{subUri}"))
{
try
{
foreach (var h in Headers)
req.SetRequestHeader(h.Key, h.Value);
await req.SendWebRequest();
}
catch (Exception e)
{
Debug.LogException(e);
}
return (
(HttpStatusCode)req.responseCode,
FromJsonSafety<T>(req.downloadHandler.text)
);
}
}
클라이언트를 빌드하기 전에 Azure 웹 앱은 무료버전으로 만들 경우 30분 이상 통신하지 않으면 슬립모드가 되기 때문에 최소 비용을 지불하도록 요금제를 변경했던 경험이 떠올랐습니다.
그래서 40분 정도 대기했다가 게임을 실행해보았고, 처음 실행할 때 응답속도가 엄청 느리다는 것 외에는 큰 문제 없어 다른 처리를 하지 않아 안드로이드로 빌드 하였습니다.
빌드 후 테스트를 해보니 화면 터치를 해도 자동차가 이동하지 않았습니다.
InputSystem을 적용할 때 마우스 입력만 추가하였기 때문인 것 같습니다.
원래 모바일에서도 잘 돌아가면 프로젝트를 마무리 할 계획이었지만 입력 문제와 그 밖에 빌드 후 발생하는 다른 문제들도 찾아서 수정한 후 프로젝트를 마무리하겠습니다.