저번에는 뒤끝 서버와 연결하여 회원가입, 로그인 기능까지 구현해 봤습니다.
이번 글에선 로그인 후 게임내 데이터를 서버에 저장시키는 기능을 구현해 보도록 하겠습니다.
데이터 저장 구현
1. 뒤끝 홈페이지에 접속, 로그인 후 게임 정보 탭을 클릭합니다.
2. 상단의 테이블 생성을 클릭합니다.
클릭하면 나오는 테이블 생성 창에서 테이블 명을 기입 후 확인 버튼을 눌러 주세요.
(유니티 내에서 테이블 명으로 테이블을 찾습니다.)
3. 이전에 작업했던 프로젝트를 켠 후, BackendManager.cs를 아래와 같이 수정합니다.
설명은 아래 코드에 주석으로 해놨습니다.
using BackEnd;
using System;
using UnityEngine;
public enum BackendState
{
Failure,
Maintainance,
Retry,
Success,
}
public class BackendManager : MonoBehaviour
{
private static BackendManager instance;
public static BackendManager Instance
{
get
{
if (!instance)
{
GameObject obj;
obj = GameObject.Find(typeof(BackendManager).Name);
if (!obj)
{
obj = new GameObject(typeof(BackendManager).Name);
instance = obj.AddComponent<BackendManager>();
}
else
{
instance = obj.GetComponent<BackendManager>();
}
}
return instance;
}
}
private void Awake()
{
DontDestroyOnLoad(this);
//BackendReturnObject는 통신의 결과로 넘어오는 값을 저장하는 클래스
//뒤끝 SKD를 이용하여 서버로 요청한 모든 기능은 BackendReturnObject클래스 형태로 리턴
//SDK를 초기화 후 BackendReturnObject클래스 리턴
BackendReturnObject bro = Backend.Initialize(true);
//초기화가 성공했을 경우?
if (bro.IsSuccess())
{
//성공일 경우 statusCode 204 Success
Debug.LogFormat("초기화 성공: {0}", bro);
}
else
{
// 실패일 경우 statusCode 400대 에러 발생
Debug.LogFormat("초기화 실패: {0}", bro);
}
}
private void Update()
{
//SDK가 초기화되어있을때
if(Backend.IsInitialized)
{
//뒤끝 비동기 함수 사용 시, 메인쓰레드에서 콜백을 처리해주는 Dispatch을 실행
Backend.AsyncPoll();
}
}
/// <summary> 차트 ID와 반복 횟수, 연결이 됬을 경우 실행할 함수를 받아 서버 GameData란에 정보를 추가하는 함수 </summary>
public void GameDataInsert(string selectedProbabilityFileId, int maxRepeatCount, Param param, Action<BackendReturnObject> onCompleted = null)
{
if (!Backend.IsLogin)
{
Debug.LogError("뒤끝에 로그인 되어있지 않습니다.");
return;
}
if (maxRepeatCount <= 0)
{
Debug.LogErrorFormat("{0} 게임 정보를 추가하지 못했습니다.", selectedProbabilityFileId);
return;
}
Backend.GameData.Insert(selectedProbabilityFileId, param, callback =>
{
switch (ErrorCheck(callback))
{
case BackendState.Failure:
Debug.LogError("연결 실패");
break;
case BackendState.Maintainance:
Debug.LogError("서버 점검 중");
break;
case BackendState.Retry:
Debug.LogWarning("연결 재시도");
GameDataInsert(selectedProbabilityFileId, maxRepeatCount - 1, param, onCompleted);
break;
case BackendState.Success:
Debug.Log("정보 추가 성공");
onCompleted?.Invoke(callback);
break;
}
});
}
/// <summary> 차트 ID와 반복 횟수, 연결이 됬을 경우 실행할 함수를 받아 서버 GameData란에 정보를 추가하는 함수 </summary>
public void GameDataUpdate(string selectedProbabilityFileId, string inDate, int maxRepeatCount, Param param, Action<BackendReturnObject> onCompleted = null)
{
if (!Backend.IsLogin)
{
Debug.LogError("뒤끝에 로그인 되어있지 않습니다.");
return;
}
if (maxRepeatCount <= 0)
{
Debug.LogErrorFormat("{0} 게임 정보를 수정하지 못했습니다.", selectedProbabilityFileId);
return;
}
Backend.GameData.UpdateV2(selectedProbabilityFileId, inDate, Backend.UserInDate, param, callback =>
{
switch (ErrorCheck(callback))
{
case BackendState.Failure:
Debug.LogError("연결 실패");
break;
case BackendState.Maintainance:
Debug.LogError("서버 점검 중");
break;
case BackendState.Retry:
Debug.LogWarning("연결 재시도");
GameDataUpdate(selectedProbabilityFileId, inDate, maxRepeatCount - 1, param, onCompleted);
break;
case BackendState.Success:
Debug.Log("정보 수정 성공");
onCompleted?.Invoke(callback);
break;
}
});
}
/// <summary> 서버와 연결 상태를 체크하고 BackendState값을 반환하는 함수 </summary>
public BackendState ErrorCheck(BackendReturnObject bro)
{
if (bro.IsSuccess())
{
Debug.Log("요청 성공!");
return BackendState.Success;
}
else
{
if (bro.IsClientRequestFailError()) // 클라이언트의 일시적인 네트워크 끊김 시
{
Debug.LogError("일시적인 네트워크 끊김");
return BackendState.Retry;
}
else if (bro.IsServerError()) // 서버의 이상 발생 시
{
Debug.LogError("서버 이상 발생");
return BackendState.Retry;
}
else if (bro.IsMaintenanceError()) // 서버 상태가 '점검'일 시
{
//점검 팝업창 + 로그인 화면으로 보내기
Debug.Log("게임 점검중");
return BackendState.Maintainance;
}
else if (bro.IsTooManyRequestError()) // 단기간에 많은 요청을 보낼 경우 발생하는 403 Forbbiden 발생 시
{
//단기간에 많은 요청을 보내면 발생합니다. 5분동안 뒤끝의 함수 요청을 중지해야합니다.
Debug.LogError("단기간에 많은 요청을 보냈습니다. 5분간 사용 불가");
return BackendState.Failure;
}
else if (bro.IsBadAccessTokenError())
{
bool isRefreshSuccess = RefreshTheBackendToken(3); // 최대 3번 리프레시 실행
if (isRefreshSuccess)
{
Debug.LogError("토큰 발급 성공");
return BackendState.Retry;
}
else
{
Debug.LogError("토큰을 발급 받지 못했습니다.");
return BackendState.Failure;
}
}
return BackendState.Retry;
}
}
/// <summary> 뒤끝 토큰 재발급 함수 </summary>
/// maxRepeatCount : 서버 연결 실패시 재 시도할 횟수
public bool RefreshTheBackendToken(int maxRepeatCount)
{
//만약 반복 횟수가 끝났으면?
if (maxRepeatCount <= 0)
{
Debug.Log("토큰 발급 실패");
return false;
}
BackendReturnObject callback = Backend.BMember.RefreshTheBackendToken();
if (callback.IsSuccess())
{
Debug.Log("토큰 발급 성공");
return true;
}
else
{
if (callback.IsClientRequestFailError()) // 클라이언트의 일시적인 네트워크 끊김 시
{
return RefreshTheBackendToken(maxRepeatCount - 1);
}
else if (callback.IsServerError()) // 서버의 이상 발생 시
{
return RefreshTheBackendToken(maxRepeatCount - 1);
}
else if (callback.IsMaintenanceError()) // 서버 상태가 '점검'일 시
{
//점검 팝업창 + 로그인 화면으로 보내기
return false;
}
else if (callback.IsTooManyRequestError()) // 단기간에 많은 요청을 보낼 경우 발생하는 403 Forbbiden 발생 시
{
//너무 많은 요청을 보내는 중
return false;
}
else
{
//재시도를 해도 액세스토큰 재발급이 불가능한 경우
//커스텀 로그인 혹은 페데레이션 로그인을 통해 수동 로그인을 진행해야합니다.
//중복 로그인일 경우 401 bad refreshToken 에러와 함께 발생할 수 있습니다.
Debug.Log("게임 접속에 문제가 발생했습니다. 로그인 화면으로 돌아갑니다\n" + callback.ToString());
return false;
}
}
}
}
4. BackendGameData.cs(게임 정보 관리 스크립트)를 생성 후 아래와 같이 코드를 작성 합니다.
자세한 설명은 코드란에 주석 처리 해놨습니다.
using System.Collections.Generic;
using UnityEngine;
using BackEnd;
public class UserData
{
public int Int = 1;
public float Float = 3.5f;
public string Str = string.Empty;
public Dictionary<string, int> Dic = new Dictionary<string, int>();
public List<string> List = new List<string>();
}
public class BackendGameData
{
public UserData UserData = new UserData();
/// <summary>유저 데이터를 서버에 저장시키는 함수</summary>
public void SaveUserData(int maxRepeatCount)
{
string selectedProbabilityFileId = "UserData";
//만약 서버에 로그인이 되어있지 않은 상태라면 빠져나간다.
if (!Backend.IsLogin)
{
Debug.LogError("뒤끝에 로그인 되어있지 않습니다.");
return;
}
//연결 횟수를 초과할 동안 서버 연결이 되지 않으면 빠져나간다.
if (maxRepeatCount <= 0)
{
Debug.LogErrorFormat("{0} 차트의 정보를 받아오지 못했습니다.", selectedProbabilityFileId);
return;
}
//서버 게임 정보 데이터베이스에 있는 자신의 데이터를 받아온다.
BackendReturnObject bro = Backend.GameData.Get(selectedProbabilityFileId, new Where());
switch (BackendManager.Instance.ErrorCheck(bro))
{
case BackendState.Failure:
Debug.LogError("초기화 실패");
break;
case BackendState.Maintainance:
Debug.LogError("서버 점검 중");
break;
case BackendState.Retry:
Debug.LogWarning("연결 재시도");
SaveUserData(maxRepeatCount - 1);
break;
case BackendState.Success:
//게임 정보DB에 자신의 정보가 존재할 경우
if (bro.GetReturnValuetoJSON() != null)
{
//DB에는 존재하나 그 속에 데이터량이 0줄일 경우
if (bro.GetReturnValuetoJSON()["rows"].Count <= 0)
{
//게임 정보DB에 자신의 데이터를 추가한다.
InsertUserData(selectedProbabilityFileId);
}
else
{
//게임 정보DB에 있는 자신의 데이터를 갱신한다.
UpdateUserData(selectedProbabilityFileId, bro.GetInDate());
}
}
//게임 정보DB에 존재하지 않을 경우
else
{
//게임 정보DB에 자신의 데이터를 추가한다.
InsertUserData(selectedProbabilityFileId);
}
Debug.LogFormat("{0}정보를 저장했습니다..", selectedProbabilityFileId);
break;
}
}
/// <summary>게임 정보DB에 자신의 데이터를 추가하는 함수</summary>
public void InsertUserData(string selectedProbabilityFileId)
{
Param param = GetUserDataParam();
Debug.LogFormat("게임 정보 데이터 삽입을 요청합니다.");
BackendManager.Instance.GameDataInsert(selectedProbabilityFileId, 10, param);
}
/// <summary>게임 정보DB에 존재하는 자신의 데이터를 갱신하는 함수</summary>
public void UpdateUserData(string selectedProbabilityFileId, string inDate)
{
Param param = GetUserDataParam();
Debug.LogFormat("게임 정보 데이터 수정을 요청합니다.");
BackendManager.Instance.GameDataUpdate(selectedProbabilityFileId, inDate, 10, param);
}
/// <summary> 서버에 전달할 유저 정보를 가진 Param클래스를 반환하는 함수 </summary>
public Param GetUserDataParam()
{
Param param = new Param();
param.Add("Int", UserData.Int);
param.Add("Float", UserData.Float);
param.Add("Str", UserData.Str);
param.Add("Dic", UserData.Dic);
param.Add("List", UserData.List);
return param;
}
}
5. LoginManager.cs를 수정할 차례입니다.
아래의 빨간 박스 부분의 코드를 추가시켜줍니다.
using BackEnd;
using UnityEngine;
using UnityEngine.UI;
public class LoginManager : MonoBehaviour
{
[Tooltip("아이디 입력 필드")]
[SerializeField] private InputField _idInput;
[Tooltip("비밀번호 입력 필드")]
[SerializeField] private InputField _passwordInput;
[Tooltip("로그인 버튼")]
[SerializeField] private Button _loginButton;
[Tooltip("회원가입 버튼")]
[SerializeField] private Button _signupButton;
[Tooltip("로그인 성공 텍스트")]
[SerializeField] private Text _loginText;
void Start()
{
_loginText.gameObject.SetActive(false);
//버튼 클릭 이벤트 추가
_loginButton.onClick.AddListener(Login);
_signupButton.onClick.AddListener(SignUp);
}
/// <summary>로그인 함수</summary>
private void Login()
{
string id = _idInput.text;
string pw = _passwordInput.text;
Debug.Log("로그인을 요청합니다.");
BackendReturnObject bro = Backend.BMember.CustomLogin(id, pw);
if (bro.IsSuccess())
{
Debug.Log("로그인이 성공했습니다. : " + bro);
HideLoginUI();
UserDataUpdate();
}
else
{
Debug.LogError("로그인이 실패했습니다. : " + bro);
}
}
/// <summary>회원가입 함수</summary>
private void SignUp()
{
string id = _idInput.text;
string pw = _passwordInput.text;
Debug.Log("회원가입을 요청합니다.");
BackendReturnObject bro = Backend.BMember.CustomSignUp(id, pw);
if (bro.IsSuccess())
{
Debug.Log("회원가입에 성공했습니다. : " + bro);
}
else
{
Debug.LogError("회원가입에 실패했습니다. : " + bro);
}
}
/// <summary>로그인 성공시 로그인 관련 ui들을 비활성화 시키는 함수</summary>
private void HideLoginUI()
{
_idInput.gameObject.SetActive(false);
_passwordInput.gameObject.SetActive(false);
_loginButton.gameObject.SetActive(false);
_signupButton.gameObject.SetActive(false);
_loginText.gameObject.SetActive(true);
}
private BackendGameData _backendGameData = new BackendGameData();
private void UserDataUpdate()
{
_backendGameData.SaveUserData(10);
}
}
여기까지 따라오셨으면 다 됬습니다!
이제 씬을 실행하신 후 로그인을 진행하게 되면 UserData가 서버에 저장됬다는 로그를 확인할 수 있습니다.
또한 게임 정보 -> UserData를 클릭하시면 저장되어있는 데이터를 확인해 볼 수 있습니다!
이번 글은 여기까지 입니다.
다음 글에선 저장된 UserData를 유니티에 받아오는 법에 대해서 알아보겠습니다.
읽어주셔서 감사합니다!
'뒤끝' 카테고리의 다른 글
[Unity]뒤끝 4 (서버 데이터 불러오기) (0) | 2024.02.29 |
---|---|
[Unity] 뒤끝 2 (로그인) (0) | 2024.01.16 |
[Unity] 뒤끝 1 (연동) (0) | 2024.01.14 |