유니티, 장고 서버 REST API 로 연결하기 - 서버 시간 동기화

정다소·2023년 11월 24일
0

Django 서버를 열어 REST API를 생성하고 이를 유니티에서 연결하기 위해 필요한 프레임 워크를 정리 해보겠습니다.

서버 시간을 동기화하는 코드를 예제를 작성해보면서 익히실 수 있습니다.

필요한 절차는 아래와 같습니다.

  1. 서버 시간을 받아 올 수 있는 GET API 생성
  2. 유니티로 REST API 호출

1. 서버 시간을 받아 올 수 있는 GET API 생성

django project veiws.py에 서버 시간을 return 하는 GET API를 생성합니다.

from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view(['GET'])
def servertime(request):
	
    //serverTime에 서버 시간 할당
    data = {"serverTime": datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") }
     
    return Response(data)

urls.py에 api path를 등록합니다.

from django.urls import path
from . import views

urlpatterns = [
    path('servertime/', views.servertime,name="get_servertime")
]

서버를 열어서 GET API가 정상적으로 작동하는 지 테스트 합니다.

$python manage.py runserver

http://127.0.0.1:8000/servertime/ 에 접속하면 정상적으로 API가 작성된것을 확인 할 수 있습니다.

2. 유니티로 REST API 호출

유니티에서 HttpServerBase.cs 파일을 생성하고 아래 코드를 작성합니다.
코드는 UnityWebRequest와 Newtonsoft의 Json 라이브러리를 활용하여 REST API를 코루틴으로 호출을 하는 함수를 포함합니다.

코드 참조 링크 : https://trialdeveloper.tistory.com/65

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using System.Text;

// ServerManager의 베이스가 될 클래스
public class HttpServerBase : MonoBehaviour
{    
    public enum SendType { GET, POST, PUT, DELETE }

    // 최종적으로 보내는 곳
    protected virtual IEnumerator SendRequestCor(string url, SendType sendType, JObject jobj, Action<Result> onSucceed, Action<Result> onFailed, Action<Result> onNetworkFailed)
    {
        // 네트워크 연결상태를 확인한다.
        yield return StartCoroutine(CheckNetwork());

        using (var req = new UnityWebRequest(url, sendType.ToString()))
        {
            // 보낸 데이터를 확인하기 위한 로그
            Debug.LogFormat("url: {0} \n" +
                "보낸데이터: {1}", 
                url, 
                JsonConvert.SerializeObject(jobj, Formatting.Indented));

            // body 입력
            byte[] bodyRaw = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(jobj));
            req.uploadHandler = new UploadHandlerRaw(bodyRaw);
            req.downloadHandler = new DownloadHandlerBuffer();
            // header 입력
            req.SetRequestHeader("Content-Type", "application/json");

            // 전송
            yield return req.SendWebRequest();

            // 성공인지 실패인지 확인
            var result = ResultCheck(req);
            // 네트워크 에러라면
            if (result.IsNetworkError)
            {
                onNetworkFailed?.Invoke(result);

                // TODO: 네트워크 재시도 팝업 호출.

                yield return new WaitForSeconds(1f);
                Debug.LogError("재시도");
                yield return StartCoroutine(SendRequestCor(url, sendType, jobj, onSucceed, onFailed, onNetworkFailed));
            }
            else
            {
                // 통신성공 이라면
                if (result.IsSuccess)
                {
                    onSucceed?.Invoke(result);
                }
                // 서버측 실패라면(인풋이 잘못됐덨가 서버에서 연산오류가 났던가)
                else
                {
                    onFailed?.Invoke(result);
                }
            }
        }
    }

    protected virtual IEnumerator CheckNetwork()
    {
        if(Application.internetReachability == NetworkReachability.NotReachable)
        {
            // TODO: 네트워크 오류 팝업 호출
            Debug.LogError("네트워크 연결 안됨");
            
            yield return new WaitUntil(() => Application.internetReachability != NetworkReachability.NotReachable);

            Debug.Log("네트워크 재연결됨");
        }
    }

    protected virtual Result ResultCheck(UnityWebRequest req)
    {
        Result res;
        switch (req.result)
        {
            case UnityWebRequest.Result.InProgress:
                res = new Result(req.downloadHandler.text, false, true, "InProgress");
                return res;
            case UnityWebRequest.Result.Success:
                Debug.Log("Result"+req.downloadHandler.text);
                JObject jobj = JObject.Parse(req.downloadHandler.text);

                
                // 서버측에서 "code"데이터가 0이 아니면 전부 실패 케이스로 쓰기로 했다.
                bool isSuccess = int.Parse(jobj["meta"]["code"].ToString()) == 0 ? true : false;

               
                // 성공
                if (isSuccess)
                {                    
                    res = new Result(req.downloadHandler.text, true, false, string.Empty);
                    return res;
                }
                // 실패
                else
                {
                    Debug.LogErrorFormat("요청 실패: {0}", jobj["message"].ToString());
                    res = new Result(req.downloadHandler.text, false, false, 
                        string.Format("Code: {0} - {1}", jobj["code"].ToString(), jobj["message"].ToString()));
                    return res;
                }
            case UnityWebRequest.Result.ConnectionError:
            case UnityWebRequest.Result.ProtocolError:
            case UnityWebRequest.Result.DataProcessingError:
                // 통신에러
                Debug.LogError(req.error);
                Debug.Log(req.downloadHandler.text);
                res = new Result(req.downloadHandler.text, false, true, req.error);
                return res;
            default:
                Debug.LogError("디폴트 케이스에 걸림");
                Debug.LogError(req.error);
                Debug.Log(req.downloadHandler.text);
                res = new Result(req.downloadHandler.text, false, true, "Unknown");
                return res;
        }
    }

    // 통신 결과를 담기위한 클래스
    public class Result
    {
        // 서버로부터 받은 리턴값을 담을 변수
        private string json;
        // 성공인지 여부
        private bool isSuccess;
        // 네트워크 에러인지 서버측 에러인지 여부
        private bool isNetworkError;
        // 에러 내용
        private string error;

        public string Json => json;
        public bool IsSuccess => isSuccess;
        public bool IsNetworkError => isNetworkError;
        public string Error => error;

        public Result(string json, bool isSuccess, bool isNetworkError, string error)
        {
            this.json = json;
            this.isSuccess = isSuccess;
            this.isNetworkError = isNetworkError;
            this.error = error;
        }
    }
}

위 코드를 베이스로 ServerManger.cs 파일에서 URL과 parameter 작성합니다. 아래 코드는 Generalized 함수를 url과 필요한 파라미터를 넣어 호출하는 코루틴을 포함하고 이를 IEnumerator로 호출합니다.

 public IEnumerator GetServerTimeCoroutine()
 {
        yield return GetServerTime();
 }
    
    public Coroutine GetServerTime(
        Action<Result> onSucceed = null, Action<Result> onFailed = null, Action<Result> onNetworkFailed = null)
    {
        // 로그인 URL을 조합
        string url = "http://127.0.0.1:8000/servertime/";

        // Parameter가 될 json
        JObject jobj = new JObject();
        
        // 성공했을때 콜백
        // 새로운 유저 정보를 세팅함 로그인 요청을했고 성공했다면 항상 업데이트 되도록 할려고 이쪽에 정의함
        Action<Result> updateServerTimeInfoAction = (result) =>
        {
            // Newtonsoft.Json 패키지를 이용한 Json Parsing
            var resultData = JObject.Parse(result.Json)["serverTime"]; 
            
            Debug.Log("서버시간"+resultData);
            
            
        };

        onSucceed += updateServerTimeInfoAction;

        return StartCoroutine(SendRequestCor(url, SendType.GET, jobj, onSucceed, onFailed, onNetworkFailed));
    }

IEnumerator를 최종적으로 호출하면 장고 웹서버에서 서버시간이 반환 됩니다.

StartCoroutine( NPCServerManager.Instance.GetServerTimeCoroutine());

이상 장고 서버에서 API를 생성하고 이를 유니티에서 호출하는 프레임 워크를 직접 구현 해보았습니다.

감사합니다.

profile
슬기로운 코딩 생활

0개의 댓글