구조 정리
이전 포스팅까지는 Unity 2D 프로젝트를 다뤄봤다. 현재 프로젝트에서는 거기서 작업하던 서버를 수정하고 3D에 맞게(이동 동기화 등) 변화를 주어 사용할 것이다. 또한 서버의 코드는 직접적으로 오픈하진 않고 대략적으로 서술할 것이다.
서버의 구조는 총 3개와 같다.
AccountServer <--> SharedDB <--> GameServer
AccountServer는 웹 서버로 초기에 로그인과 회원가입, 그리고 서버 목록들을 출력하기 위한 용도이다. 게임서버에서도 이를 한번에 관리하면 쉽겠지만 Account 자체가 실시간, 주기적으로 소통을 해야하는 구조는 아니기 때문에 게임서버의 부담을 줄이고 DB를 나누기 위함이다.
그리고 AccountServer에서 사용하는 계정에 대한 정보를 GameServer에서 이용하기 위해 SharedDB를 이용한다.
GameServer는 게임 내에서 컨텐츠 용도로 사용된다. GameServer 하나 당 한 개의 서버를 맡고 있으며, 게임 로직을 구성하는 메인쓰레드 1개, 네트워크 패킷을 모아서 클라에게 전달해 주는 쓰레드 1개, DB의 값을 저장하고 읽어오는 쓰레드 1개, SharedDB에 현재 서버에 대한 정보를 관리하기위한 쓰레드 1개로 구성되어있다.(SharedDB 쓰레드의 일은 주기가 매우 길기 때문에 메인 쓰레드에서 담당해도 무관하다.)
로그인, 회원가입 흐름
클라이언트 쪽에서 회원가입 요청을 하게되면(특정 버튼을 클릭) 입력한 값을 1차적으로 검증한다. 그리고 AccountServer로 값을 보내고 중복된 값(이미 가입된 회원)인지 확인하고 그 값을 클라에게 돌려주며 DB에 저장한다.
만약 로그인 요청을 보냈다면 입력한 값을 AccountServer에 보낸다. AccountServer에서는 DB에 값이 저장되어 있는지 확인하고 그 값이 정상적으로 존재한다면 클라에게 접근을 허용한다고 보내줌과 동시에 ShareDB에서 계정 토큰을 생성해 저장한다. 토큰은 Account의 pk값과 유효기간 등이 저장하고 있다. 이는 나중에 GameServer에서 정말 AccountServer를 통해 발급된 토큰인지 확인하고 보안을 위해 만든 것이다.
Title 화면
Title과 Login 씬을 나눌까했지만 하나의 씬안에서 작업하기로 했다. 배경은 RPG 게임에 맞게 구성해주고 DoTween을 이용해 글자가 fade되게 만들었다.
또한 파티클 시스템을 이용해 꽃이 떨어지도록 했다.
파티클을 UI 위에 뿌리기 위해서는 CanvasRenderMode를 Screen Space-Camera로 수정해주고 메인 카메라를 RenderCamera로 설정해 주어야 한다.
Title 프리팹에는 특별한 코드는 없다. 그냥 화면을 클릭하면 Login창이 뜨도록만 구성되어있다.
로그인 창은 이렇게 구성되어 있다.
그리고 코드를 살펴보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
public class UI_Login_Popup : UI_Popup
{
enum GameObjects
{
LoginInput,
PasswordInput,
BaseObj,
}
enum Buttons
{
LoginBtn,
RegisterBtn,
}
public bool _click { get; set; } = false;
public override void Init()
{
base.Init();
GetComponent<CanvasGroup>().alpha = 0f;
GetComponent<CanvasGroup>().DOFade(1f, 1f);
BindObject(typeof(GameObjects));
BindButton(typeof(Buttons));
GetButton((int)Buttons.LoginBtn).gameObject.BindEvent(OnClickLoginBtn);
GetButton((int)Buttons.RegisterBtn).gameObject.BindEvent(OnClickRegisterBtn);
}
public void OnClickLoginBtn(PointerEventData data)
{
if (_click == true) return;
_click = true;
string account = GetObject((int)GameObjects.LoginInput).GetComponent<TMP_InputField>().text;
string password = GetObject((int)GameObjects.PasswordInput).GetComponent<TMP_InputField>().text;
LoginAccountPacketReq packet = new LoginAccountPacketReq()
{
AccountName = account,
Passwrod = password
};
Managers.Web.SendPostRequest<LoginAccountPacketRes>("account/login", packet, res =>
{
Debug.Log(res.LoginOk);
GetObject((int)GameObjects.LoginInput).GetComponent<TMP_InputField>().text = "";
GetObject((int)GameObjects.PasswordInput).GetComponent<TMP_InputField>().text = "";
if (res.LoginOk)
{
Managers.Network.AccountId = res.AccountId;
Managers.Network.Token = res.Token;
GetObject((int)GameObjects.BaseObj).transform.DOLocalMoveY(-1500f, 1f).SetEase(Ease.OutQuad);
UI_ServerSelect_Popup popup = Managers.UI.ShowPopupUI<UI_ServerSelect_Popup>();
popup.ServerSetting(res.ServerList);
}
else
{
// 에러 팝업 띄우기
Managers.UI.ShowPopupUI<UI_Confirm_Popup>().Setting("아이디 또는 비밀번호를\n확인해주세요.");
}
_click = false;
});
}
public void OnClickRegisterBtn(PointerEventData data)
{
StartCoroutine(FadeDestroy());
}
IEnumerator FadeDestroy()
{
CanvasGroup cg = GetComponent<CanvasGroup>();
Tween tw = cg.DOFade(0f, 0.5f);
yield return tw.WaitForCompletion();
_click = false;
Managers.UI.ClosePopupUI(this);
Managers.UI.ShowPopupUI<UI_Register_Popup>();
}
}
여기서 중요한 건 OnClickLoginBtn 이다. 로그인 버튼을 클릭했을 때 동작하는 버튼으로 Login 패킷을 만들어 WebServer 즉, AccountServer로 보내는 작업을 한다. 로그인에 성공한다면 서버 선택창으로 이동하게 된다.
회원가입도 로그인과 비슷하게 생겼다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
public class UI_Register_Popup : UI_Popup
{
enum GameObjects
{
LoginInput,
PasswordInput,
PasswordCheckInput,
}
enum Buttons
{
RegisterBtn,
DeclineBtn,
}
public bool _click { get; set; } = false;
public override void Init()
{
base.Init();
GetComponent<CanvasGroup>().alpha = 0f;
GetComponent<CanvasGroup>().DOFade(1f, 1f);
BindObject(typeof(GameObjects));
BindButton(typeof(Buttons));
GetButton((int)Buttons.DeclineBtn).gameObject.BindEvent(OnClickDeclineBtn);
GetButton((int)Buttons.RegisterBtn).gameObject.BindEvent(OnClickRegisterBtn);
}
public void OnClickDeclineBtn(PointerEventData data)
{
if (_click) return;
_click = true;
DestroyRegisterUI();
}
public void OnClickRegisterBtn(PointerEventData data)
{
if (_click) return;
_click = true;
string account = GetObject((int)GameObjects.LoginInput).GetComponent<TMP_InputField>().text;
string password = GetObject((int)GameObjects.PasswordInput).GetComponent<TMP_InputField>().text;
string passwordCheck = GetObject((int)GameObjects.PasswordCheckInput).GetComponent<TMP_InputField>().text;
if(password.Equals(passwordCheck) == false)
{
Managers.UI.ShowPopupUI<UI_Confirm_Popup>().Setting("비밀번호가 다릅니다.");
_click = false;
}
else if (account == "" || password == "")
{
Managers.UI.ShowPopupUI<UI_Confirm_Popup>().Setting("아이디 또는 비밀번호를\n입력하세요.");
_click = false;
}
else if (password.Length < 8)
{
Managers.UI.ShowPopupUI<UI_Confirm_Popup>().Setting("비밀번호는 8자리 이상으로 입력하세요.");
_click = false;
}
else
{
CreateAccountPacketReq packet = new CreateAccountPacketReq()
{
AccountName = account,
Passwrod = password
};
Managers.Web.SendPostRequest<CreateAccountPacketRes>("account/create", packet, res =>
{
Debug.Log(res.CreateOk);
if(res.CreateOk)
DestroyRegisterUI();
else
{
Managers.UI.ShowPopupUI<UI_Confirm_Popup>().Setting("중복된 아이디입니다.");
_click = false;
}
});
}
}
public void DestroyRegisterUI()
{
StartCoroutine(FadeDestroy());
}
IEnumerator FadeDestroy()
{
CanvasGroup cg = GetComponent<CanvasGroup>();
Tween tw = cg.DOFade(0f, 0.5f);
yield return tw.WaitForCompletion();
_click = false;
Managers.UI.ClosePopupUI(this);
Managers.UI.ShowPopupUI<UI_Login_Popup>();
}
}
OnClickRegisterBtn을 통해 회원가입을 할 수 있다. 이 역시 WebServer와 연결해 통신한다.
이제 로그인을 하게 되면 Server 선택창이 나오게 된다. 이는 현재 SharedDB에서 현재 활동중인 서버의 목록을 보내줬기 때문에 그 값을 바탕으로 서버 화면을 구성하게된다.
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UI_ServerSelect_Popup : UI_Popup
{
List<UI_ServerList_Item> Items = new List<UI_ServerList_Item>();
enum GameObjects
{
BaseObj,
}
public override void Init()
{
base.Init();
BindObject(typeof(GameObjects));
GetObject((int)GameObjects.BaseObj).transform.DOLocalMoveY(0f, 1f).SetEase(Ease.OutQuad);
}
public void ServerSetting(List<ServerInfo> servers)
{
Items.Clear();
GameObject grid = GetComponentInChildren<GridLayoutGroup>().gameObject;
foreach (Transform child in grid.transform)
Destroy(child.gameObject);
for (int i = 0; i < servers.Count; i++)
{
var item = Managers.UI.MakeSubItem<UI_ServerList_Item>(grid.transform);
item.Setting(servers[i]);
Items.Add(item);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.UIElements;
public class UI_ServerList_Item : UI_Base
{
bool _init = false;
public ServerInfo Info;
enum Images
{
BackgroundImage,
ServerIconImage,
}
enum Texts
{
ServerNameText,
BusyScoreText,
}
public override void Init()
{
BindText(typeof(Texts));
BindImage(typeof(Images));
_init = true;
GetImage((int)Images.BackgroundImage).gameObject.BindEvent(OnClickServer);
}
public void UpdateUI()
{
if (_init == false)
return;
GetImage((int)Images.ServerIconImage).sprite = Managers.Resource.Load<Sprite>($"UI/ServerIcon/{Info.Name}");
GetText((int)Texts.ServerNameText).text = Info.Name;
string _serverState;
if (Info.BusyScore >= 7) _serverState = "<color=red>혼잡</color>";
else if (Info.BusyScore >= 4) _serverState = "<color=yellow>보통</color>";
else _serverState = "<color=green>원활</color>";
GetText((int)Texts.BusyScoreText).text = _serverState;
}
public void OnClickServer(PointerEventData data)
{
// 서버 접속
//Managers.Network.ConnectToGame(Info);
//Managers.Scene.LoadScene(Define.Scene.Game);
Managers.UI.CloseAllPopupUI();
}
public void Setting(ServerInfo info)
{
Info = info;
UpdateUI();
}
}
지금까지 작업물의 영상이다. 아주 만족스럽다.
AccountServer를 열어둔 상태이다.
'Unity > 온라인 RPG' 카테고리의 다른 글
[Unity 3D] Navigation을 이용한 캐릭터 이동 (0) | 2024.04.17 |
---|---|
[Unity 3D] 캐릭터 선택창 및 캐릭터 생성 (0) | 2024.04.13 |
[Unity 2D] 대형 구조 관리 - 서버 구조 개선 작업 (0) | 2024.04.02 |
[Unity 2D] DB 연동 - Reward 와 아이템 착용 (0) | 2024.03.29 |
[Unity 2D] Db 연동 - Item과 Inventory (0) | 2024.03.27 |