뒤끝

[Unity] 뒤끝 3 (데이터 저장)

때류기 2024. 1. 18. 23:13

 

저번에는 뒤끝 서버와 연결하여 회원가입, 로그인 기능까지 구현해 봤습니다.

이번 글에선 로그인 후 게임내 데이터를 서버에 저장시키는 기능을 구현해 보도록 하겠습니다.

 

 

 

데이터 저장 구현

 

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