UDN
Search public documentation:
GameCenterKR
English Translation
日本語訳
中国翻译
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
日本語訳
中国翻译
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
UE3 홈 > 모바일 홈 > 언리얼 엔진 3: 모바일 개요 > 언리얼 엔진 3: 애플 iOS 개요 > 게임 센터 (Game Center)
UE3 홈 > 네트워킹과 리플리케이션 > 게임 센터 (Game Center)
UE3 홈 > 네트워킹과 리플리케이션 > 게임 센터 (Game Center)
게임 센터 (Game Center)
문서 변경내역: Josh Adams 작성. 홍성진 번역.
개요
Game Center 는 애플의 온라인 게이밍 네트워크입니다. 게이머가 iOS 디바이스를 사용하여 연결, 다른 사람과 비교/협동/경쟁할 수 있도록 해 주는 것입니다. 언리얼 엔진 3를 사용하여 iOS 디바이스용으로 개발된 게임은 Game Center 기능을 지원하여, 게임에 소셜 및 커뮤니티 주도적인 느낌을 부여해 줄 수 있습니다. 주: 이하 GC 는 Garbage Collection 이 아닌, Game Center 를 일컫습니다.
주: UnrealScript 에서 OnlineSubsystemGameCenter 를 직접 리퍼런스하지 않도록 하는 것이 중요한데, PC 에서 UDK 를 실행시키려 할 때 문제가 생기기 때문입니다. 항상 OnlineSubsystem 리퍼런스를 사용하면 플랫폼에 따라 언리얼 엔진 3 가 자동으로 올바른 종류의 OnlineSubsystem 을 사용할 것입니다.
환경설정 셋업
언리얼 엔진 3 가 iOS 에서 GC 를 지원하는 방식은 OnlineSubsystemGameCenter 인터페이스를 통해서입니다. 그러나 시동 도중 자동으로 Game Center 로그인/환영 창을 띄우기 때문에, GC 는 환경설정 파일로 켜고 끌 수 있습니다. 개발자가 GC 를 테스트할 필요가 없는 경우 참으로 설정하고, 아니면 거짓으로 설정하면 됩니다.
[OnlineSubsystemGameCenter.OnlineSubsystemGameCenter] bDisableGameCenter=false
[OnlineSubsystemGameCenter.OnlineSubsystemGameCenter] bDisableGameCenter=false UniqueAchievementPrefix=com.epicgames.exploreue3.achievement_ UniqueCategoryPrefix=com.epicgames.exploreue3.leaderboard_ EpicUniqueAchievementPrefix=com.epicgames.exploreue3.achievement_ EpicUniqueCategoryPrefix=com.epicgames.exploreue3.leaderboard_
OnlineSubsystemGameCenter 컴파일하기
iOS 게임에 게임 센터를 사용하기 전에 OnlineSubsystemGameCenter 를 컴파일해야 할 수 있습니다. OnlineSubsystemGameCenter 를 컴파일하려면 DefaultEngine.ini 내 EditPackages 배열에 추가해 줘야 합니다.
[UnrealEd.EditorEngine] +EditPackages=UTGame +EditPackages=UTGameContent +EditPackages=OnlineSubsystemGameCenter
업적 (Achievement)
업적 작동방식은 다른 플랫폼과 꽤 비슷합니다만, GC에서는 업적을 처음 풀 때 화면위 메시지가 뜨지 않는다는 점이 다릅니다. 게임 코드가 업적 풀기를 시도할 때, 사용자에 대해 이미 풀려있지 않은가 먼저 검사를 해야 합니다. 그 일반적인 작업방식은 이와 같습니다:
- 게임 시동 (한 번만 해 주면 되지만, 더 해도 괜찮습니다.)
- 업적 데이터 읽기 완료시 통지받기 위해 OnlineSub.PlayerInterface.AddReadAchievementsCompleteDelegate() 호출
- 업적 읽기 시작을 위해 OnlineSub.PlayerInterface.ReadAchievements() 호출
- 델리게이트에서 업적이 읽히는 것을 눈여겨 보다가, 그 스테이트 질의
- 플레이어가 게임을 플레이하다가, 업적 조건을 충족
- 모든 업적 스테이트를 구하기 위해 OnlineSub.PlayerInterface.GetAchievements() 호출
- ID가 일치하는 업적에 대해 반환 배열 살펴보기
- 업적 ID를 업적 배열 속으로의 인덱스로 사용하지 마십시오! 한 가지 이유로, 업적 ID는 1부터 시작됩니다. (언리얼스크립트를 비롯한 여러 언어의 배열 시작 인덱스는 0 입니다.)
- bWasAchievedOnline 이 거짓이면
- UI 표시, 사운드 재생 등
- 플레이어가 업적을 풀었음을 GC에게 알리기 위해 OnlineSub.PlayerInterface.UnlockAchievement() 호출
업적 기술적인 세부사항
GC 코드가 시동될 때 즉시 업적 내려받기를 시작하므로, 게임 코드가 실행될 때 쯤에는 이미 전부 받아져 있을 것입니다. 그래도 확실히 하려면, 업적 내려받기가 끝났는지를 확인하는 델리게이트와 함께 OnlineSub.PlayerInterface.ReadAchievements() 를 호출하십시오. 그러면 OnlineSub.PlayerInterface.GetAchievements() 가 정말로 믿을 수 있는 결과를 반환할 것입니다. 내부적으로 업적에 대해 벌어지는 작업은 훨씬 복잡한데, 그 이유는 사용자가 오프라인일 때 업적을 풀면 GC는 서버에게 알리지 않기 때문입니다. 그래서 업적 스테이트를 로컬 iOS 플래시 디스크에 저장하여 유지합니다. 사용자가 나중에 GC에 연결할 때마다, 엔진은 원격지(GC 서버)와 로컬의 업적 스테이트 차이를 검사한 다음 둘을 병합시키고, UI 표시 없이 원격지 측의 업적을 풀어 업데이트합니다. 주: 업적 중 ID 가 -1 로 설정된 것이 있고, 그 업적을 제대로 다운받지 못했다면, UniqueAchievementPrefix 및/또는 EpicUniqueAchievementPrefix 가 제대로 설정되지 않은 것입니다.업적 예제
이 예제에서는 업적 처리기 액터 클래스를 만들어 업적 관련 호출을 전송할 수 있도록 합니다. 물론 커스텀 GameInfo 클래스 등 다른 클래스로 바꿔도 됩니다. 업적 처리기는 (여러 업적을 연속해서 달성한 경우) 대기중인 업적 목록을 보관한 다음, 대기중인 업적 전부 풀릴 때까지 반복하는 비동기 함수를 호출합니다. 업적을 풀기 위해서는, 업적 ID 를 파라미터로 붙여 YourAchievementHandler::UnlockAchievement() 를 호출합니다. 이 업적 ID 는 업적 ID 끝의 번호와 일치해야 합니다. 예를 들어 com.epicgames.exploreue3.achievement_01 라는 ID 를 가진 업적은 업적 ID 가 1 이어야 합니다. 기억하실 것은, 업적 ID 는 보통 0 이 아닌 1 부터 시작된다는 점입니다. 첫 검사는 업적 ID 가 대기중인 업적 배열에 있는지 확인하는 것입니다. 이를 통해 업적이 여러번 풀리지 않도록 합니다. 업척 처리기가 현재 아무것도 처리하지 않고 있다면, 대기중인 업척 처리를 시작시키고 ProcessingAchievements 플랙을 참으로 바꿉니다. 비동기 업적 처리 루프를 시작시키기 위해서입니다. 여기서 서버의 업적 읽기가 완료되었을 때 호출되는 델리게이트가 할당되며, 업적을 읽기 위한 비동기 호출을 합니다.class YourAchievementHandler extends Actor; // 대기중인 업적 var array<int> PendingAchievements; // 현재 업적을 처리중이면 참 var bool ProcessingAchievements; /** * 플레이어의 업적을 풉니다. * * @param AchievementId 풀 업적 * @param LocalUserNum 로컬 유저 인덱스 */ function UnlockAchievement(int AchievementId) { local OnlineSubsystem OnlineSubsystem; local int PlayerControllerId; // 이 업적은 이미 대기중으로 처리되고 있으니 그냥 기다립니다. if (PendingAchievements.Find(AchievementId) != INDEX_NONE) { return; } // 대기 목록에 업적 ID 를 추가합니다. PendingAchievements.AddItem(AchievementId); // 지금 업적을 처리중이지 않으면 처리합니다. if (!ProcessingAchievements) { // GameCenter 에 접속하여 업적 델리게이트를 연결합니다. OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem != None && OnlineSubsystem.PlayerInterface != None) { // 로컬 플레이어 콘트롤러 ID 를 구합니다. PlayerControllerId = GetALocalPlayerControllerId(); // 업적 읽기 델리게이트를 할당합니다. OnlineSubsystem.PlayerInterface.AddReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); // 모든 업적을 읽습니다. OnlineSubsystem.PlayerInterface.ReadAchievements(PlayerControllerId); // 참으로 설정하여 다시 발동되지 않도록 합니다. ProcessingAchievements = true; } } }
/** * 로컬 플레이어 콘트롤러 ID 를 반환합니다. 같은 규칙이 Actor::GetALocalPlayerController() 에 적용됩니다. * * @return 로컬 플레이어 콘트롤러 ID 를 반환 */ function int GetALocalPlayerControllerId() { local PlayerController PlayerController; local LocalPlayer LocalPlayer; // 로컬 플레이어 콘트롤러를 구합니다. PlayerController = GetALocalPlayerController(); if (PlayerController == None) { return INDEX_NONE; } // 로컬 플레이어 정보를 구합니다. LocalPlayer = LocalPlayer(PlayerController.Player); if (LocalPlayer == None) { return INDEX_NONE; } return class'UIInteraction'.static.GetPlayerIndex(LocalPlayer.ControllerId); }
class YourAchievementHandler extends Actor; // 모든 내려받은 업적 배열 var array<AchievementDetails> DownloadedAchievements; /** * 비동기 업적 읽기 완료시 호출됩니다. * * @param TitleId 읽고 있던 것에 해당하는 타이틀 ID (0 은 현재 타이틀) */ function InternalOnReadAchievementsComplete(int TitleId) { local OnlineSubsystem OnlineSubsystem; local int AchievementIndex, PlayerControllerId; // 온라인 서브시스템이 있는지, 연결된 플레이어 인터페이스가 있는지 확인합니다. OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None) { return; } // 로컬 플레이어 콘트롤러 ID 를 구합니다. PlayerControllerId = GetALocalPlayerControllerId(); // 새로운 데이터를 복사할 것이기에 현재 다운받은 업적 배열을 비웁니다. DownloadedAchievements.Remove(0, DownloadedAchievements.Length); // 다운받은 업적 배열에 업적을 읽어들입니다. OnlineSubsystem.PlayerInterface.GetAchievements(PlayerControllerId, DownloadedAchievements, TitleId); // 모든 업적을 구합니다. if (DownloadedAchievements.Length > 0 && PendingAchievements.Length > 0) { // 업적 인덱스를 구합니다. AchievementIndex = DownloadedAchievements.Find('Id', PendingAchievements[0]); // 업적을 풉니다. if (AchievementIndex != INDEX_NONE && !DownloadedAchievements[AchievementIndex].bWasAchievedOnline) { // 업적 풀기 완료 델리게이트를 할당합니다. OnlineSubsystem.PlayerInterface.AddUnlockAchievementCompleteDelegate(PlayerControllerId, InternalOnUnlockAchievementComplete); // 풀기 프로세스를 시작합니다. OnlineSubsystem.PlayerInterface.UnlockAchievement(PlayerControllerId, PendingAchievements[0]); } } // 가비지 콜렉션이 일어나도록 델리게이트 리퍼런스를 제거합니다. OnlineSubsystem.PlayerInterface.ClearReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); }
class YourAchievementHandler extends Actor; /** * 업적 풀기가 완료되면 호출됩니다. * * @param bWasSuccessful 비동기 동작이 에러없이 끝나면 참, 에러가 있었으면 거짓 */ function InternalOnUnlockAchievementComplete(bool bWasSuccessful) { local OnlineSubsystem OnlineSubsystem; local PlayerController PlayerController; local int AchievementIndex, PlayerControllerId; // 로컬 플레이어 콘트롤러 ID 를 구합니다. PlayerControllerId = GetALocalPlayerControllerId(); if (bWasSuccessful && PendingAchievements.Length > 0) { // 로컬 플레이어 콘트롤러를 구합니다. PlayerController = GetALocalPlayerController(); if (PlayerController != None) { // 업적 인덱스를 구합니다. AchievementIndex = DownloadedAchievements.Find('Id', PendingAchievements[0]); // 플레이어의 유저 인터페이스에 업적을 표시합니다. } } // 성공 여부와 관계없이 처리한 업적을 뽑아냅니다. PendingAchievements.Remove(0, 1); // 온라인 서브시스템이 있는지, 연결된 플레이어 인터페이스가 있는지 확인합니다. OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None) { return; } // 대기중인 업적이 남아있으면 다음 것을 처리합니다. if (PendingAchievements.Length > 0) { // 게임센터에 접속하여 업적 델리게이트를 연결합니다. // 업적 읽기 델리게이트를 할당합니다. OnlineSubsystem.PlayerInterface.AddReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); // 모든 업적을 읽습니다. OnlineSubsystem.PlayerInterface.ReadAchievements(PlayerControllerId); } else // 아니면 끝난 것이니 정리합니다. { // 델리게이트 바인드를 지웁니다. OnlineSubsystem.PlayerInterface.ClearUnlockAchievementCompleteDelegate(PlayerControllerId, InternalOnUnlockAchievementComplete); // 더이상 업적을 처리하지 않는 상태로 플랙 설정합니다. ProcessingAchievements = false; } }
class YourAchievementHandler extends Actor; /** * 액터 소멸시 호출됩니다. */ event Destroyed() { local OnlineSubsystem OnlineSubsystem; local int PlayerControllerId; Super.Destroyed(); // 온라인 서브시스템이 있는지, 연결된 플레이어 인터페이스가 있는지 확인합니다. OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None) { return; } // 아직 업적을 처리중이라면, 가비지 콜렉션이 일어날 수 있도록 할당된 델리게이트를 지워야 합니다. if (ProcessingAchievements) { // 로컬 플레이어 콘트롤러 ID 를 구합니다. PlayerControllerId = GetALocalPlayerControllerId(); // 가비지 콜렉션이 일어날 수 있도록 델리게이트 리퍼런스를 지웁니다. OnlineSubsystem.PlayerInterface.ClearReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); // 델리게이트 바인드를 지웁니다. OnlineSubsystem.PlayerInterface.ClearUnlockAchievementCompleteDelegate(PlayerControllerId, InternalOnUnlockAchievementComplete); } }
순위표 (Leaderboard)
GC의 또다른 주요 비-멀티플레이어 기능은 순위표(leaderboard) 입니다. 마찬가지로 다른 플랫폼과 비슷한 방식으로 작동합니다만, GC 순위표에는 이해해 둬야 할 제약이 몇 가지 있습니다. GC는 실제적으로 여러 "범주"에 하나의 "순위표"만 지원합니다. 하나의 순위표에 대해 표의 한 열로 범주를 취급합니다. 그러나 모든 범주가 (그 UI로) 동일한 포맷과 라벨로 표시되므로, 모든 범주는 시간이 될 수도, 정수형 점수가 될 수도, 정수형 킬 수가 될 수도 있습니다. 게다가 그들 전부 오름차순 아니면 내림차순 입니다. 이는 점수를 구성하는 방법에 영향을 끼치게 됩니다. 점수 보고는, OnlineStatsWrite 의 서브클래스를 만들어, defaultproperties 블록 내에 (1 시작 ID를 가지고) 그 속성을 구성한 뒤, 거기서 오브젝트의 인스턴스를 만들고, 값을 설정한 다음, OnlineSub.StatsInterface.WriteOnlineStats() 로 보고합니다.
class YourOnlineStatsWrite extends OnlineStatsWrite; const PROPERTY_KILLS = 1; const PROPERTY_LEVEL = 2; const PROPERTY_GOLD = 3; defaultproperties { Properties=((PropertyId=PROPERTY_KILLS,Data=(Type=SDT_Int32,Value1=0)),(PropertyId=PROPERTY_LEVEL,Data=(Type=SDT_Int32,Value1=0)),(PropertyId=PROPERTY_GOLD,Data=(Type=SDT_Int32,Value1=0))) }
- 플레이어 킬 수 저장된 값을 읽습니다. (환경설정 값이나 BasicSaveObject/BasicLoadObject 를 사용할 수도 있습니다.)
- 값을 알맞게 증가시킵니다.
- 점수를 게임 센터에 씁니다.
local OnlineSubsystem OnlineSubsystem; local OnlineSuppliedUIInterface OnlineSuppliedUIInterface; local array<UniqueNetId> PlayerIds; local UniqueNetId PlayerId; local YourOnlineStatsRead YourOnlineStatsRead; local PlayerController PlayerController; local LocalPlayer LocalPlayer; local byte LocalUserNum; // 온라인 서브 시스템을 구합니다. OnlineSubSystem = class'GameEngine'.static.GetOnlineSubsystem(); // 온라인 서브 시스템에 접근할 수 있는지 확인합니다. if (OnlineSubSystem != None) { // 로컬 플레이어 콘트롤러에서 PlayerIds 배열을 만듭니다. PlayerController = GetALocalPlayerController(); if (PlayerController != None) { // 로컬 플레이어를 구합니다. LocalPlayer = LocalPlayer(PlayerController.Player); if (LocalPlayer != None) { // 로컬 사용자 번호를 구합니다. LocalUserNum = class'UIInteraction'.static.GetPlayerIndex(LocalPlayer.ControllerId); // 로컬 사용자 번호에서 고유 플레이어 id 를 구합니다. OnlineSubSystem.PlayerInterface.GetUniquePlayerId(LocalUserNum, PlayerId); // 고유 플레이어 id 를 PlayerIds 배열에 추가합니다. PlayerIds.AddItem(PlayerId); // 온라인 제공 UI 인터페이스를 구합니다. OnlineSuppliedUIInterface = OnlineSuppliedUIInterface(OnlineSubSystem.GetNamedInterface('SuppliedUI')); if (OnlineSuppliedUIInterface != None) { // 온라인 통계 읽기 클래스 인스턴싱 입니다. YourOnlineStatsRead = new () class'YourOnlineStatsRead'; if (YourOnlineStatsRead!= None) { // 온라인 통계 UI 를 표시합니다. OnlineSuppliedUIInterface.ShowOnlineStatsUI(PlayerIds, YourOnlineStatsRead); } } } } }
class YourOnlineStatsRead extends OnlineStatsRead; const PROPERTY_KILLS = 1; const PROPERTY_LEVEL = 2; const PROPERTY_GOLD = 3; defaultproperties { ColumnIds=(PROPERTY_KILLS,PROPERTY_LEVEL,PROPERTY_GOLD) }
순위표 기술적인 세부사항
업적과 비슷하게 사용자가 오프라인일 때 점수가 보고되는 경우, GC가 자동으로 재전송하지 않습니다. 그래서 디스크에 보고된 최고 및 최저 점수를 저장하기 위해 내부적으로 많은 작업을 하고서, 다음 번 사용자가 접속할 때 서버에 보고해 줍니다. 순위표가 오름차순인지 내림차순인지를 알지 못하기 때문에 최고와 최저를 같이 제출합니다. 통계를 읽을 때 모든 점수는 QWORD / Int64 (64 비트 정수형, 범위는 - 9,223,372,036,854,775,808 에서 9,223,372,036,854,775,807) 로 보고되는데, GC의 순위표 값 저장 방식이 그렇기 때문입니다.멀티플레이어
멀티플레이어는 현재 로비-기반-상대검색(matchmaking) 방식으로만, 최대 4 명까지 지원됩니다. GC가 GKMatch 오브젝트를 통해 네트워크 트래픽을 처리하기 때문에, GC 상대검색을 통해 발견된 플레이어만 게임 네트워킹의 통신 대상이 될 수 있습니다. 네 개의 iOS 디바이스 중 하나가 서버 역할을 하게 됩니다.
상대검색
Matchmaking(상대검색)은 GC의 내장 인터페이스를 사용해서 이루어집니다:OnlineSuppliedUIInterface(OnlineSub.GetNamedInterface('SuppliedUI')).ShowMatchmakingUI();
초대
GC는 상대검색 UI에서 플레이어를 초대하는 기능을 지원합니다. 초대된 플레이어가 다른 게임을 플레이하던 중인 경우 초대 메시지를 보게 될 것이며, 초대를 수락하면 게임을 실행시키고, 게임이 부팅될 때 플레이어에게 게임에 참가중이라 알리는 GC 상대검색 UI가 밀려올라오게 됩니다. 그 플레이어가 이미 같은 게임에 있는 경우, PlayerController 내의 코드 (OnGameInviteAccepted() 참고)가 현재 온라인 게임을 전부 없애고, 해당 플레이어가 게임에 참가중임을 알리는 GC 상대검색 UI를 불러옵니다. 초대된 플레이어가 실제로 서버가 될 수도 있음에 유념하십시오.음성 채팅
GC에는 매우 기본적이고 사용하기 쉬운 음성 채팅 시스템이 있습니다. 멀티플레이어 게임에 들어간 후, 플레이어끼리 대화를 가능하게 하려면 OnlineSub.VoiceInterface.StartNetworkedVoice() 를 호출하면 됩니다. 음소거나 대화 상대자를 알아보는 기능 역시도 (MuteRemoteTalker(), IsRemotePlayerTalking() 등의) 표준 함수를 사용해 작동됩니다.가비지 콜렉션
온라인 서브시스템 델리게이트에 바인딩할 때마다, 레벨의 가비지 콜렉션 예약시 항상 지우도록 하는 것이 중요합니다. 그렇게 하지 않으면 가비지 콜렉션이 실패하여 메모리 일부가 절대 해제되지 않기 때문입니다! 이에 대해 자세한 내용은 키즈멧 온라인 서브시스템 UDK 젬을 참고하시기 바랍니다.
관련 토픽
- 키즈멧 온라인 서브시스템 - Game Center 와의 인터페이스 역할을 하는 키즈멧 노드 추가법을 선보이는 UDK 젬입니다.
내려받기
- YourAchievementHandler.uc - 업적 예제에 대한 소스 코드 내려받기.