-
Notifications
You must be signed in to change notification settings - Fork 130
CloudBread를 사용하여 Flappy Bird 게임에 서버 구축 CloudBread Unity SDK 사용
by 홍윤석 (yshong93)
Updated : 2016 / 09 / 10
Flappy Bird 라는 게임은 매우 단순한 Running 게임 입니다.
원본 게임은 현재 앱 스토어에는 제공되지 않고 있지만 지속적으로 패러디 게임들이 개발되고 있습니다. 뿐만 아니라 개발 하기 매우 쉽기 때문에 처음 게임을 개발을 해보시는 분들에게 부담 없이 만들어 볼 수 있을 최적의 게임이라고 생각합니다.
실제 유니티를 사용하여 게임을 만드는 과정은 이 블로그를 참고하시면, Unity 3D를 사용하여 개발을 진행할 수 있습니다. 실제 데모는 이 곳에서 해 볼 수 있습니다.
아래 주소에서 제공해주는 필요한 소스코드를 다운받으면, 바로 게임에 CloudBread 를 붙여 볼 수 있습니다. https://github.com/dgkanatsios/FlappyBirdClone
아래 링크에서는 개발 하는 과정의 영상을 볼 수 있습니다. https://youtu.be/umWGSm0h8kE
CloudBread 를 사용하여 게임 만들기 는 다음과 같은 순서로 진행됩니다.
- Flappy Bird 게임 만들기
- Facebook 로그인 기능 구현
- Azure 에서 제공하는 Facebook 인증기능 사용하기
- CloudBread 의 회원가입 API 호출
- CloudBread 의 랭킹 API 호출
Asset - Scenes - mainGame 을 열면 위와 같이 게임을 실행 할 수 있습니다.
마우스 오른쪽 버튼 클릭 - Create - Scene 을 클릭하여 loginGame 씬 생성
Assets – Scrips 에 FacebookLoginScript.cs (C# Script) 생성하기
loginGame 씬에서 마우스 오른쪽 – UI – Button 클릭 해서 버튼 만들기
마우스 오른쪽 – Create Empty 클릭해서 FacebookLoginManager라는 Gameobject 만들기
아까 만든 FacebookLoginScript를 드래그 해서 FacebookLoginManager에 추가하기
아래 순서대로 새로 만든 버튼 클릭 – Inspector - On Click () - + 버튼 클릭 None (Object)에 FacebookLoginManager 드래그해서 놓기
FacebookLoginScipt.cs 에 아래와 같이 FacebookLoginBtnClick() 메소드 추가
using UnityEngine;
using System.Collections;
public class FacebookLoginScript : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public void FacebookLoginBtnClick()
{
}
}
No Function – FacebookLoginScript – FacebookLoginBtnClicked() 클릭
(캠프에서는 하나의 데모 CloudBread 서버만을 사용하기 때문에, Azure에 Facebook 을 등록하는 실습에 어려움이 있습니다. 현장 캠프 미션에서는 설정하는 방법을 생략하고, 미리 설정된 계정을 사용합니다.)
- Facebook에 앱 추가하기
- Azure Mobile App 에 페이스북 앱 추가하기
아래 사이트에서 자세하게 볼 수 있습니다. https://azure.microsoft.com/ko-kr/documentation/articles/mobile-services-how-to-register-facebook-authentication/
중요 Advanced 탭을 클릭하고, Valid OAuth redirect URIs에 아래URL 형식 입력한 다음 Save Changes를 클릭합니다. https://[mobile_service].azure-mobile.net/login/facebook
Facebook App ID : 207398879650397
- SDK 다운로드 하기 (https://developers.facebook.com/docs/unity)
- Unity 프로젝트에 SDK 추가하기 (위 사이트의 Getting Started 참고)
- loginGame 에서 버튼 클릭했을 때, Facebook Permission 받아오도록 구현 (위 사이트의 Example 참고해서 FacebookLoginScript 구현)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Facebook.Unity;
using UnityEngine.SceneManagement;
public class FacebookLoginScript : MonoBehaviour {
// Awake function from Unity's MonoBehavior
void Awake()
{
if (!FB.IsInitialized)
{
// Initialize the Facebook SDK
FB.Init(InitCallback, OnHideUnity);
}
else {
// Already initialized, signal an app activation App Event
FB.ActivateApp();
}
}
private void InitCallback()
{
if (FB.IsInitialized)
{
// Signal an app activation App Event
FB.ActivateApp();
// Continue with Facebook SDK
// ...
}
else {
Debug.Log("Failed to Initialize the Facebook SDK");
}
}
private void LoginwithPermissions()
{
var perms = new List<string>() { "public_profile", "email", "user_friends" };
FB.LogInWithReadPermissions(perms, AuthCallback);
}
private void AuthCallback(ILoginResult result)
{
if (FB.IsLoggedIn)
{
// AccessToken class will have session details
var aToken = Facebook.Unity.AccessToken.CurrentAccessToken;
// Print current access token's User ID
Debug.Log(aToken.UserId);
// Print current access token's granted permissions
foreach (string perm in aToken.Permissions)
{
Debug.Log(perm);
}
// 유저 이름(닉네임) 불러오기
FB.API("me?fields=name", HttpMethod.GET, NameCallBack);
// 인증 토큰 가져오기
// TODO : CloudBread 클래스 생성 후, Login API 호출
}
else {
Debug.Log("User cancelled login");
}
}
public void StartGame()
{
SceneManager.LoadScene("mainGame");
}
private void NameCallBack(IGraphResult result)
{
string userName = (string)result.ResultDictionary["name"];
print(userName + "님 안녕하세요^^");
PlayerPrefs.SetString("nickName", userName);
}
private void OnHideUnity(bool isGameShown)
{
if (!isGameShown)
{
// Pause the game - we will need to hide
Time.timeScale = 0;
}
else {
// Resume the game - we're getting focus again
Time.timeScale = 1;
}
}
public void FacebookLoginBtnClick()
{
if (!FB.IsLoggedIn)
LoginwithPermissions();
}
}
페이스북 SDK 를 사용하여 로그인 성공했을 때
지금까지 페이스북 SDK 를 사용하여 User Access Token, User ID, User Name 을 받는 것을 진행하였습니다. 앞으로 이어질 내용에서는 CloudBread-Unity-SDK를 사용하여 애저 인증 기능을 사용하는 방법과 CloudBread 서버를 사용하는 방법에 대해 자세히 다루겠습니다.
CloudBread 는 HTTP RESTful API 를 지원하기 때문에, 어떠한 플랫폼에서든 Http Request만으로 사용 할 수 있습니다. 하지만 저희가 공식적으로 진행하고 있는 Unity SDK 프로젝트를 사용하면 훨씬 쉽게 CloudBread와 Unity를 연동 할 수 있습니다. 저희는 이번 캠프에서는 Unity-SDK 를 사용하여 개발하는 방법에 대해 소개를 하고 있으며, 만약 Http Request 를 직접 구현하고 싶으시다면, 다음 문서를 참조해주세요.
- 레포지토리의 코드를 다운받습니다. Assets 안에 있는 CloudBread 폴더와 Editor Default Resources 폴더를 프로젝트에 추가합니다.
참조 : CloudBread-Unity-SDK 사용법 가이드
Assets/CloudBread/CB.Settings 파일을 설정을 다음과 같이 변경합니다.
여기서 말하는 Azure 인증 이란 Facebook 과 CloudBread 사이의 oAuth 2.0 인증 방식의 기능을 말합니다. Azure 에서 기본적으로 제공하는 기능이며, 이 기능을 사용하면 페이스북 로그인 구현을 매우 간단하게 구현 할 수 있습니다.
Facebook SDK 에서 받아온 User Token을 사용하여, 애저에서 발급하는 토큰을 발급받을 것입니다. 애저 인증 기능을 사용하기 위해서는 HTTP Request 할 때, Header 에 애저에서 발급받은 토큰을 항상 넣어야 한다는 것입니다.
다음은 HTTP Request 를 하기 위해 꼭 필요한 헤더 값 입니다.
"Accept" : "application/json"
"Content-Type" : "application/json"
"X-ZUMO-FEATURES" : "AJ"
"ZUMO-API-VERSION" : 2.0.0
"x-zumo-auth" : "<애저에서 발급받은 토큰>"
이 때, Azure 에서 발급받은 토큰을 x-zumo-auth
에 넣어 http 호출을 함으로써, 비정상적인 Http 호출을 막을 수 있습니다.
Azure 에 요청을 하여 x-zumo-auth
에 넣을 토큰을 받아오는 과정입니다. 다음과 같은 RESTful API 호출을 통해 Azure로 부터 토큰을 받아 올 수 있습니다.
Address :
https://cb2-auth-demo.azurewebsites.net/.auth/login/facebook
Request Data :
{
"access_token" : "페이스북에서 발급 받은 토큰"
}
Response Data :
{
"authenticationToken" : "<발급받은 Token>",
"user" : {
"userId" : "sid:<발급받은 sid>"
}
}
- authenticationToken 을 호출 할 때마다 헤더에
X-ZUMO-AUTH
값으로 넣어야만 서버로부터 응답을 받을 수 있다. - userId의 sid 를 게임에서 유일한 값 (
memberID
)로 사용
Asstes/CloudBread/Protocols 폴더에 CloudBread.POST.AzureAuth.cs(C# Scripts) 파일 생성 다음과 같이 스크립트 작성하기.
using UnityEngine;
using System;
namespace CloudBread
{
public partial class AzureAuth
{
//https://cb2-auth-demo.azurewebsites.net/.auth/login/facebook
// 호출 할 Address
const string _url = ".auth/login/facebook";
// API 호출 시 Request Data
[Serializable]
public struct Post
{
[SerializeField]
public string access_token;
}
// API 호출 시 Response Data
[Serializable]
public struct Receive
{
[SerializeField]
public string authenticationToken;
[SerializeField]
public User user;
}
[Serializable]
public struct User
{
[SerializeField]
public string userId;
}
static public void Request(Post postData_, System.Action<Receive> callback_, System.Action<string> errorCallback_ = null)
{
CloudBread.Request(CloudBread.MakeFullUrl(_url), JsonUtility.ToJson(postData_), callback_, errorCallback_);
}
}
}
FacebookLoginScipt.cs 스크립트에서 CloudBread 클래스의 Login 함수 호출하기 (// TODO 부분에 추가하기)
CloudBread cb = new CloudBread();
cb.Login(AzureAuthentication.AuthenticationProvider.Facebook, aToken.TokenString, Callback_login);
Login 콜백 함수 구현
private void Callback_login(string id, WWW www)
{
print(www.text);
string resultJson = www.text;
ㄴ
JsonReader jsonReader = new JsonReader();
AuthData resultData = jsonReader.Read<AuthData>(resultJson);
// Azure 인증을 위해 발급받은 Azure Token 을 헤더에 추가
AzureMobileAppRequestHelper.AuthToken = resultData.authenticationToken;
// 게임에서 사용 할 userId 를 PlayerPrefs 에 저장
// 원래 게임에서는 아래와 같이, Azure 에서 제공해 주는 userID 를 넣는 것이 맞지만,
// 데모 서버를 사용하는 분들은 임의의 아이디를 넣어서 다른 사람들과 충돌이 나지 않도록 합시다
PlayerPrefs.SetString("userId", resultData.user.userId);
}
애저에서 Token과 UserID를 json 형식 제공
{
"authenticationToken" : "<발급받은 Token>",
"user" : {
"userId" : "sid:<발급받은 sid>"
}
}
-
Facebook에서 UserID와 이름을 가져와서 CloudBread 서버에 등록하기 CBInsRegMember API 호출 (PostMan 참고)
-
유효한 사용자이면, 게임 화면으로 넘어가기
- CBInsRegMember API호출 시, 새로 가입 된 사용자 응답
{
"result": "2"
}
- CBInsRegMember API호출 시, 중복된 사용자(이미 가입된 사용자)
status : 500 Internal Server Error
CloudBread.cs 파일에 CloudBread 서버에 회원 등록을 하기 위해 CBInsRegMember API 를 호출하도록 구현
public void CBInsRegMember(Action<string, WWW> callback)
{
var ServerEndPoint = ServerAddress + "api/CBInsRegMember";
WWWHelper helper = WWWHelper.Instance;
helper.OnHttpRequest += OnHttpRequest;
MemberData memberData = new MemberData
{
MemberID_Members = (string)PlayerPrefs.GetString("userId"),
EmailAddress_Members = (string)PlayerPrefs.GetString("userId"),
Name1_Members = (string)PlayerPrefs.GetString("nickName")
};
JsonWriter jsonWriter = new JsonWriter();
string jsonBody = jsonWriter.Write(memberData);
helper.POST("CBInsRegMember", ServerEndPoint, jsonBody);
Callback = callback;
}
FacebookLoginManager.cs 의 Login 콜백 함수에서 바로 CBInsRegMember 호출
private void Callback_login(string id, WWW www)
{
print(www.text);
string resultJson = www.text;
JsonReader jsonReader = new JsonReader();
AuthData resultData = jsonReader.Read<AuthData>(resultJson);
// Azure 인증을 위해 발급받은 Azure Token 을 헤더에 추가
AzureMobileAppRequestHelper.AuthToken = resultData.authenticationToken;
// 게임에서 사용 할 userId 를 PlayerPrefs 에 저장
PlayerPrefs.SetString("userId", resultData.user.userId);
// CBInsRegMember 함수를 사용하여 CloudBread 에 memberId 등록하기
CloudBread cb = new CloudBread();
cb.CBInsRegMember(Callback_CBInsRegMember);
}
public void Callback_CBInsRegMember(string id, WWW www)
{
if (www.error != null) //새로 회원 가입
{
print("이미 가입된 회원");
StartGame();
}
else //이미 가입된 회원
{
print("새로 가입한 회원");
StartGame();
}
}
FacebookLoginManager.cs 스크립트 완성
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Facebook.Unity;
using Assets.Scripts.CloudBread;
using JsonFx.Json;
using UnityEngine.SceneManagement;
public class FacebookLoginScript : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
// Awake function from Unity's MonoBehavior
void Awake()
{
if (!FB.IsInitialized)
{
// Initialize the Facebook SDK
FB.Init(InitCallback, OnHideUnity);
}
else {
// Already initialized, signal an app activation App Event
FB.ActivateApp();
}
}
private void InitCallback()
{
if (FB.IsInitialized)
{
// Signal an app activation App Event
FB.ActivateApp();
// Continue with Facebook SDK
// ...
}
else {
Debug.Log("Failed to Initialize the Facebook SDK");
}
}
private void LoginwithPermissions()
{
var perms = new List<string>() { "public_profile", "email", "user_friends" };
FB.LogInWithReadPermissions(perms, AuthCallback);
}
private void AuthCallback(ILoginResult result)
{
if (FB.IsLoggedIn)
{
// AccessToken class will have session details
var aToken = Facebook.Unity.AccessToken.CurrentAccessToken;
// Print current access token's User ID
Debug.Log(aToken.UserId);
// Print current access token's granted permissions
foreach (string perm in aToken.Permissions)
{
Debug.Log(perm);
}
// 유저 이름(닉네임) 불러오기
FB.API("me?fields=name", HttpMethod.GET, NameCallBack);
// 인증 토큰 가져오기
CloudBread cb = new CloudBread();
cb.Login(AzureAuthentication.AuthenticationProvider.Facebook, aToken.TokenString, Callback_login);
}
else {
Debug.Log("User cancelled login");
}
}
private void Callback_login(string id, WWW www)
{
print(www.text);
string resultJson = www.text;
JsonReader jsonReader = new JsonReader();
AuthData resultData = jsonReader.Read<AuthData>(resultJson);
AzureMobileAppRequestHelper.AuthToken = resultData.authenticationToken;
PlayerPrefs.SetString("userId", resultData.user.userId);
CloudBread cb = new CloudBread();
cb.CBInsRegMember(Callback_CBInsRegMember);
}
public void Callback_CBInsRegMember(string id, WWW www)
{
if (www.error != null) //새로 회원 가입
{
print("이미 가입된 회원");
StartGame();
}
else //이미 가입된 회원
{
print("새로 가입한 회원");
StartGame();
}
}
public void StartGame()
{
SceneManager.LoadScene("mainGame");
}
private void NameCallBack(IGraphResult result)
{
string userName = (string)result.ResultDictionary["name"];
print(userName + "님 안녕하세요^^");
PlayerPrefs.SetString("nickName", userName);
}
private void OnHideUnity(bool isGameShown)
{
if (!isGameShown)
{
// Pause the game - we will need to hide
Time.timeScale = 0;
}
else {
// Resume the game - we're getting focus again
Time.timeScale = 1;
}
}
public void FacebookLoginBtnClick()
{
if (!FB.IsLoggedIn)
LoginwithPermissions();
}
}
-
ScoreManagerScript.cs 파일 열기
-
게임 끝났을 때, CBComUdtMmberGameInfoes 호출을 위해, GameState가 Dead 일 때 추가하기
-
CBComUdtMmberGameInfoes API 호출을 위해 MemberGameInfo 클래스 생성 (PostMan 참조)
-
CloudBread 클래스에 CBComUDTMemberGameInofes 호출 구현
public void CBComUdtMemberGameInfoes(Action<string, WWW> callback)
{
var ServerEndPoint = ServerAddress + "api/CBComUdtMemberGameInfoes";
WWWHelper helper = WWWHelper.Instance;
helper.OnHttpRequest += OnHttpRequest;
MemberGameInfo gameinfoData = new MemberGameInfo
{
MemberID = (string)PlayerPrefs.GetString("userId"),
Level = 2,
Points = PlayerPrefs.GetInt("bestScore")
};
JsonWriter jsonWriter = new JsonWriter();
string jsonBody = jsonWriter.Write(gameinfoData);
helper.POST("CBComUdtMemberGameInfoes", ServerEndPoint, jsonBody);
Callback = callback;
}
class MemberGameInfo
{
public string MemberID;
public int Level = 0;
public int Exps = 0;
public int Points = 0;
public string UserSTAT1;
public string UserSTAT2;
public string UserSTAT3;
public string UserSTAT4;
public string UserSTAT5;
public string UserSTAT6;
public string UserSTAT7;
public string UserSTAT8;
public string UserSTAT9;
public string UserSTAT10;
public string sCol1;
public string sCol2;
public string sCol3;
public string sCol4;
public string sCol5;
public string sCol6;
public string sCol7;
public string sCol8;
public string sCol9;
public string sCol10;
}
- ScoreManagerScript 에서 CBComDUTMemberGameInfoes 호출하기
기존에는 항상 게임 상태와 상관없이 항상 Update 되던 함수를 게임 플레이 중인 모드와 게임이 죽었을 때 모드로 분리
if (GameStateManager.GameState == GameState.Playing)
{
if (previousScore != Score) //save perf from non needed calculations
{
if (Score < 10)
{
//just draw units
Units.sprite = numberSprites[Score];
}
else if (Score >= 10 && Score < 100)
{
(Tens.gameObject as GameObject).SetActive(true);
Tens.sprite = numberSprites[Score / 10];
Units.sprite = numberSprites[Score % 10];
}
else if (Score >= 100)
{
(Hundreds.gameObject as GameObject).SetActive(true);
Hundreds.sprite = numberSprites[Score / 100];
int rest = Score % 100;
Tens.sprite = numberSprites[rest / 10];
Units.sprite = numberSprites[rest % 10];
}
}
}
else if (GameStateManager.GameState == GameState.Dead)
{
}
게임 상태가 죽었을 때(GmaeStateManage.Gamestate == Gamestate.Dead) CB CBComUdtMemberGameInfoes 호출하기
else if (GameStateManager.GameState == GameState.Dead)
{
if (bestScore >= Score)
{
setScorewithSpirte(BestScoreUnit, bestScore);
}
else
{
PlayerPrefs.SetInt("bestScore", Score);
setScorewithSpirte(BestScoreUnit, Score);
if (deadRefreshFlag)
{
deadRefreshFlag = false;
CloudBread cb = new CloudBread();
cb.CBComUdtMemberGameInfoes(Callback_Success);
}
}
}
public void Callback_Success(string id, WWW www)
{
print("[" + id + "] Success");
}
페이스북 로그인 서비스의 보안 정책 때문에, 아래와 같이 페이스북 앱 ID 와 가상 사용자 앱 엑세스 토큰을 제공.
Facebook Test ID
ID : [email protected]
PW : cloud bread
access Token :
EAAC8oNCMYl0BAEeBpbo8Eech8f19oHTvrx8O5Aau3ba1UqRHJ9DmsWFr0MTp2cfIoKMZAfxE0BEc3Mbew7ktSZCctVc2ZAqFZCAtfFG3KtSTWDySJVt0Qjro5ZAkEams2bng9Axbaqd6ERvijfnx57pD6lXToXhpwoNbhOsFatQZDZD