Django 서버를 열어 REST 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가 작성된것을 확인 할 수 있습니다.
유니티에서 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를 생성하고 이를 유니티에서 호출하는 프레임 워크를 직접 구현 해보았습니다.
감사합니다.