‹ Google Play Games Services Doc Home
Google Play Games Services 集成指南
For the Lua version of cocos2d-x v3.x - (all other versions)
安装
用如下命令来集成 SDKBOX IAP 插件,请确保您可以正常执行的 SDKBOX 安装器.
$ sdkbox import gpg
安装后
Android
修改 AndroidManifest.xml
在你的 AndroidManifest.xml
中添加如下 meta-data.
<meta-data android:name="com.google.android.gms.games.APP_ID"
android:value="@string/google_app_id" />
修改 string.xml
添加如下文字到 proj.android/res/values/string.xml
中
<string name="google_app_id">777734739048</string>
请一定要把 google_app_id
对应的值修改为你自己的 App Id.
iOS
修改 proj.ios_mac/ios/AppController.mm
在 AppController.mm
中添加如下代码:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
return [[GIDSignIn sharedInstance] handleURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
}
设置自己的 Google Play Signin Listener (可选)
设置 GPG(Google Play Game Services) SignIn 的回调
这不是必选的,SDKBox GPG
已经在内部设置了对应的 Delegate, 当你在外面自己手动设置 GIDSignInUIDelegate 时,内部的就会被忽略
在外面自己手动设置 GIDSignInUIDelegate
, 如下步骤:
修改 proj.ios_mac/ios/RootViewController.h
让 RootviewController
实现 GIDSignInUIDelegate
:
#import <GoogleSignIn/GoogleSignIn.h>
// 在 RootViewController 类的定义中加上:
@interface RootViewController : UIViewController<GIDSignInUIDelegate>
设置 Google SignIn Listener
修改 proj.ios_mac/ios/AppController.mm
添加如下代码:
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
在 return 前添加如下代码:
// _viewController could also be named
// viewController, depending of the project type.
[GIDSignIn sharedInstance].uiDelegate = _viewController;
添加 URL types
添加如下 URL types 到你的工程中, 路径为 your project > Info > URL Types
-
URL 1:
- Identifier:
com.google.ReverseClientId
- Url schemes:
com.googleusercontent.apps.777734739048-cdkbeieil19d6pfkavddrri5o19gk4ni
(use this as sample, or put your very own application’s url scheme)
- Identifier:
-
URL 2:
- Identifier:
com.google.BundleId
- URL schemes:
com.sdkbox.gpg
(use this as sample or put your own application’s bundle id)
- Identifier:
更多资料
在官方文档中查看更多的相关信息
重点注意事项
如果您升级到了 Xcode7, 则需要以下额外步骤来确保插件工作正常:
禁用应用程序安全传输策略
添加以下项到 plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
添加后的文件内容看起来就像这样:
禁止 Bitcode 支持
您必须禁止 Bitcode 的支持,否则将会编译失败。
游戏全屏配置
如果您的游戏不同时支持横竖屏,则必须在 Xcode 中选中 Requires full screen
,否则将不会通过 Apple 的审核。
canOpenURL 白名单
取决于您使用哪些插件。需要在 info.plist
的 LSApplicationQueriesSchemes
下添加名单。
关于 Creator 工程
Creator 导出工程会修改工程的配置, 这可能会影响 SDKBox 对工程的配置修改. 所以可以有以下做法(任选其一):
- 保存导出工程中的修改, 对于 SDKBox 来说可能有这些文件(mk, gradle, gradle.properties, AppDelegate.cpp, ...), 导出后再恢复对应该文件
- 在每一次 Creator 导出工程后, 重新 import 插件.
推荐第一种, 但是第一种的麻烦点在于, 对于第一次(或很久没接触的人)可能会有露掉某个文件 第二种, 很方便, 它的麻烦点在于, 如果你对工程有自己的修改, SDKBox 的重新 import 不能帮你恢复.
用法
前期准备
你必须在Google Play Developer console 创建一个自己的 app , 同时请确保所有的服务都是 enable 状态, 对应的配置也是配置正确的.
提示: Google Play Game Services 默认使用 release keystore, 如果你想用 debug 模式来测试, 看下这个方法
初始化
目前,配置过程在Lua中完成,并在创建 Google Play Game 服务对象时传递进去。 这个对象是由你管理的,在Lua中可以用 `gpg
访问。
要创建一个 Google Play Game 服务实例的话,你需要传递一个表中,表中 包含了 ClientID
项, 它的值是你在 Google 开发者中心创建的应用的ID。
local config = {ClientID="..."}
gpg:CreateGameServices(config)
请注意,使用的 ClientID
是Google 开发者中心的较新的版本。 还有一个是在plist设置的较旧的版本。 确保您有正确的版本,否则初始化将失败。
配置结构包含的所有项值如下
{
LogLevel = 1 or 2,
EnableSnapshots = true or false,
ClientID = forwards client id
}
回调
大多数Google Play服务方法都需要用回调参数来返回结果。 这主要是因为方法是异步执行的。
支持两种类型的回调。 类方法回调和函数回调(其中包括lambda函数)
类方法例子
ExampleClass:CallbackMethod(result)
-- use result here
end
local someClass = ExampleClass:new()
gpg:MethodWithCallback({someClass, ExampleClass.CallbackMethod})
请注意,在类方法示例中,您必须传递类的实例以及方法。
Lambda 函数例子
gpg:MethodWithCallback(function(result)
-- use result here
end)
Authorization
在您可以对Google Play服务执行任何操作之前,您必须进行验证。 如果您之前已通过身份验证,则sdkbox将尝试自动登录。 你仍然会得到与你自动登录时相同的事件。
要开始认证过程,请调用以下代码,并传入类方法或lambda方法(参阅 回调)以接收响应。
gpg:StartAuthorizationUI(function(result)
if result.authStatus == gpg.AuthStatus.VALID then
-- successfully authenticated
end
end)
Quests API
准备
请务必查看Google Play服务 Quests 文档,以便更好地了解如何使用自己的门户网站设置任务,以及参考上的API。
在您的游戏可以访问事件和任务之前,您必须先在Google Play开发者中心中定义它们
提交 event
您可以将事件发送到 Events 服务,以便让它知道发生了什么事。 这个方法没有结果,因此不需要回调。
gpg.Events:Increment("<event id>")
获取 events
要取当前 events 的数量,其以下其中一个方法
gpg.Events:Fetch("<event id>", function(result)
-- use result.count here
end)
-- 或
gpg.Events:FetchAll(function(results)
for k,v in pairs(results.data) do
-- k is the event id
-- use v.count here
end
end)
回调结果的完整成员列表可以在本文档后面的的回调结果描述部分找到。
显示 quests
Google Play服务提供了一个用于选择任务的UI,或者您可以根据回调中的 quests 数据显示您自己的UI。
您可以显示所有可用的任务,或只显示一个任务UI。
gpg.Quests:ShowUI("<quest id>", function(result)
-- use result.quest here
end)
-- 或
gpg.Quests:ShowAllUI(function(result)
-- use result.quest here
end)
处理 quest 接受
如果你的游戏使用内置的任务UI,那么回调结果将有一个有效的Quest对象,你可以使用 quest.valid()
验证,如果你使用自己的UI,那么你可以调用accept,如下:
gpg.Quests:Accept("<quest id>", function(result)
-- use result.quest here
end)
处理 quest 完成
当玩家接受一个 quest(任务) 后,你发送事件到 quest 服务,通知它任务的进度。
一个所有的任务标准已经满足,你可以在内置的用户界面或自己的界面声明完成奖励。
您通过调用声明方法声明任务里程碑。
gpg.Quests:ClaimMilestone("<milestone id>", function(result)
if result.status == gpg.ClaimMilestoneResponse.VALID
-- use result.milestone.completionRewardData here
end
end)
Player statistics(玩家状态)
有关玩家状态的信息以及如何使用玩家状态信息的完整说明,请参阅Google Play服务中的文档
获取当前登录玩家的状态信息
你可以获取当前登录玩家的信息,如下
gpg.Stats:FetchForPlayer(function(result)
if result.status == gpg.FetchForPlayerResponse.VALID then
-- use PlayerStats here
end
end)
Achievements (成就)
相关的完整文档,参见这里
状态
成就可以隐藏,提示和解锁。
成就可以指定为标准或增量。 通常,增量成就可以让玩家逐渐进步,在更长的时间内获得成就
显示
gpg.Achievements:ShowAllUI(function(result)
-- handle the result here
end)
获取所有成就
gpg.Achievements:FetchAll(nil, function(result)
log:d(log:to_str(result))
end)
获取成就
gpg.Achievements:Fetch('CgkI6KjppNEWEAIQBQ', nil, function(result)
log:d(log:to_str(result))
end)
增量成就
gpg.Achievements:Increment('CgkI6KjppNEWEAIQBQ')
解锁成就
gpg.Achievements:Unlock('CgkI6KjppNEWEAIQBQ')
提示成就
gpg.Achievements:Reveal('CgkI6KjppNEWEAIQBQ')
Leaderboards (排行榜)
更多文档参见这里
显示
gpg.Leaderboards:ShowUI("achievement id")
显示所有排行榜
gpg.Leaderboards:ShowAllUI()
提交分数
gpg.Leaderboards:SubmitScore("achievement id", score, "meta data", function(result)
end)
获取所有分数的概括
gpg.Leaderboards:FetchAllScoreSummaries("achievement id", data source, function(result)
end)
获取所有
gpg.Leaderboards:FetchAll(datasource, function(result)
end)
获取分数页面
gpg.Leaderboards:FetchScorePage("achievement id", datasource, time, timespan, collection, maxitmes, function(result)
end)
获取下一页分数页
gpg.Leaderboards:FetchNextScorePage(datasource, max items, function(result)
end)
Realtime Multiplayer (实时多人)
开始之前,请先了解Google Play Game 实时多人游戏的概念这里。
要使用实时多人游戏,您必须在Google Play开发者中心启用。
开始实时多人游戏,有三种方式与其他玩家连接,开始实时多人游戏。
快速游戏
让玩家与随机用户对战 (通过 auto-matching).
gpg.Realtime:CreateRealTimeRoom(
{
type = "quick_match", -- select automatching
quick_match_params =
{
maximumAutomatchingPlayers = 1,
minimumAutomatchingPlayers = 1
}
},
listener,
function(result)
if (gpg:IsSuccess(result.result)) then
-- use result.room here
end
end
)
邀请玩家
可以选择邀请特定玩家
gpg.Realtime:CreateRealTimeRoom(
{
type = "ui", -- select invite players UI
ui_params =
{
maximumPlayers = 1,
minimumPlayers = 1
}
},
listener,
function(result)
if (gpg:IsSuccess(result.result)) then
-- use result.room here
end
end
)
接受邀请
在选择特定玩家的情况下,您将收到邀请,可以接受或拒绝。
-- you can fetch all pending invitations like this
-- this will call you back with a result and array of invitations
gpg.Realtime:FetchInvitations(function(result)
if (gpg:IsSuccess(result.result)) then
-- do something with result.invitations
end
end)
-- or you can use the UI to accept or decline an invitation
gpg.Realtime:ShowRoomInboxUI(function(result)
if (gpg:IsSuccess(result.result)) then
-- do something with result.invitation
end
end)
监听
房间创建方法,包括接受邀请,监听对象。 这是为了当房间状态有变化时,可以通过监听被告知。
这也是你将通过 onDataReceived
回调方法从其他玩家接收数据消息。
listener =
{
-- called when something about the room changes
onRoomStatusChanged = function(room)
end,
-- called when something about the connection changes
onConnectedSetChanged = function(room)
end,
-- called when you get connected
onP2PConnected = function(room, participant)
end,
-- called when you get disconnected
onP2PDisconnected = function(room, participant)
end,
-- called if the status of one of the room participants changes
onParticipantStatusChanged = function(room, participant)
end,
-- called whenever someone sends you a message
onDataReceived = function(room, from_participant, data, is_reliable)
end
}
给其他玩家发消息
有两种类型的消息可以发送,可靠和不可靠。 可靠的消息是保证的,如果丢失,将自动重新发送。 这有一些开销,所以如果你不需要可靠性,那么你可以发送一个不可靠的消息,这更高效,但以可靠性为代价。
gpg.Realtime:SendReliableMessage(room_id, participant_id, message, function(result)
if (gpg:IsSuccess(result.result)) then
-- message send was successful
end
end)
发送不可靠的消息的参数json编码的字符串。 没有回调,因为是不可靠消息,没有返回状态。
gpg.Realtime:SendUnreliableMessage(json.encode({
data = message,
room_id = room_id
participant_ids = {}
}))
离开房间
gpg.Realtime:LeaveRoom(room_id, function(result)
end)
接受 / 拒绝和忽略邀请
要接受邀请,你必须在你加入房间时设置监听,然后通过监听收取对应事件.
gpg.Realtime:AcceptInvitation(invitation_id, listener, function(result)
end)
拒绝和忽略邀请不需要回调参数,他们就是告诉服务器,你对这个邀请不感兴趣. 忽略邀请用在游戏结束了,但是这个邀请还在你的收件箱中.
gpg.Realtime:DeclineInvitation(invitation_id)
gpg.Realtime:DismissInvitation(invitation_id)
Turn Based Multiplayer (回合制多人)
在开始之前,请务必先查看Google的文档这里,并查看基于回合的多人游戏概念这里
要开始一个基于回合的多人游戏,有两种方式这样做。 您可以使用UI来选择玩家(Google的或您自己的玩家),也可以开始快速匹配,为您选择玩家。
快速比赛
local minimumPlayers = 1
local maximumPlayers = 2
local allowAutoMatching = false
gpg.Turnbased:ShowPlayerSelectUI(minimumPlayers, maximumPlayers, allowAutoMatching, function(result)
params = {
type = "quick_match",
minimumAutomatchingPlayers = result.minimumAutomatchingPlayers,
maximumAutomatchingPlayers = result.maximumAutomatchingPlayers,
playerIds = result.playerIds
}
gpg.Turnbased:CreateTurnBasedMatch(params, function(result)
if gpg:IsSuccess(result.result) then
-- use result.match to start playing
end
end)
end)
选择玩家界面
params = {
type = "ui",
minimumAutomatchingPlayers = 1,
maximumAutomatchingPlayers = 2
}
gpg.Turnbased:CreateTurnBasedMatch(params, function(result)
if gpg:IsSuccess(result.result) then
-- use result.match to start playing
end
end)
处理比赛事件
对于基于回合的多人游戏,需要处理两个事件。 您可以注册两个回调(参阅 回调)以处理这些事件。
gpg.Turnbased:addMatchEventCallback(
gpg.DefaultCallbacks.TURN_BASED_MATCH_EVENT,
function(event)
gpg.Turnbased:ShowMatchInboxUI(function(result)
if gpg:IsSuccess(result.result) then
-- start using result.match here
end
end)
end
)
-- 或
gpg.Turnbased:addMatchEventCallback(
gpg.DefaultCallbacks.MULTIPLAYER_INVITATION_EVENT,
{instance, method}
)
function class:method()
gpg.Turnbased:ShowMatchInboxUI(function(result)
if gpg:IsSuccess(result.result) then
local match = result.match
if match.matchStatus == gpg.MatchStatus.MY_TURN then
-- do something with match, take a turn
elseif match.matchStatus == gpg.MatchStatus.THEIR_TURN then
-- update for their turn
elseif match.matchStatus == gpg.MatchStatus.COMPLETED then
-- complete match, dismiss
else match.matchStatus == gpg.MatchStatus.EXPIRED then
-- dismiss
end
end
end)
end
进行回合
要进行回合,您必须使用回合数据更新比赛数据,并将其传递给下一个参赛者。 如果您希望自动匹配下一个参与者,则可以使用ID“AUTOMATCHING_PARTICIPANT”。
local results = match.participantResults
if winnig then
results = gpg.Turnbased:createParticipantResult(match_id, match.pendingParticipant.id, my_rank, win_token)
elseif losing then
results = gpg.Turnbased:createParticipantResult(match.id, match.pendingParticipant.id, my_rank, lose_token)
end
local nextParticipant = "AUTOMATCHING_PARTICIPANT"
if match.suggestedNextParticipant.valid and
match.suggestedNextParticipant.id ~= "" then
nextParticipant = match.suggestedNextParticipant.id
end
gpg.Turnbased:TakeMyTurn(match_id, match.pendingParticipant.id, nextParticipant, data, function(result)
end)
创建参与者结果
有时您需要将参与者结果传递给方法。 在 C++ 中,可以直接使用一个结构体,但在Lua,你需要使用一个id。 这个id用于查找结构体并为帮你传递。 您可以重复使用现有的参与者ID或仅创建自己的ID。 该方法还将返回 Lua 对象。
local results = gpg.Turnbased:CreateParticipantResult(match_id, participant_id, placement, match_result)
-- use results, or call method and pass participant_id
完成比赛
gpg.Turnbased:FinishMatchDuringMyTurn(match_id, participant_results_id, data, function(result)
if gpg:IsSuccess(result.result) then
-- success
end
end)
离开比赛
您可以随时离开比赛,但您需要调用正确的方法,以下任一方法。
gpg.Turnbased:LeaveMatchDuringMyTurn(match_id, next_participant_id, function(result)
if gpg:IsSuccess(result.result) then
-- success
end
end)
-- 或
gpg.Turnbased:LeaveMatchDuringTheirTurn(match_id, function(result)
if gpg:IsSuccess(result.result) then
-- success
end
end)
取消比赛
gpg.Turnbased:CancelMatch(match_id, function(result)
if gpg:IsSuccess(result.result) then
-- success
end
end)
忽略比赛
gpg.Turnbased:CancelMatch(match_id)
开始比赛
gpg.Turnbased:Rematch(match_id, function(result)
if gpg:IsSuccess(result.result) then
-- success
end
end)
获取之前的比赛
gpg.Turnbased:FetchMatch(match_id, function(result)
if gpg:IsSuccess(result.result) then
-- success
end
end)
获取所有比赛
gpg.Turnbased:FetchMatches(function(result)
if gpg:IsSuccess(result.result) then
-- use result.matches here
end
end)
NearbyConnections (附近联接)
更多文档,参见Nearby Connections
初始化
初始化 Nearby Connection, 如果不支持当前平台,会返回 false
local support = gpg.NearbyConnections:Init("{\"LogLevel\":1}",
function(result)
if result.InitializationStatus then
print('GPG Nearby init success')
else
print('GPG Nearby init failed')
end
end)
if not support then
print('GPG Nearby is not support ios')
end
取本地端的id
联接成功后,获取本地端的id
local endpoint = gpg.NearbyConnections:GetLocalEndpointId();
print('Local Endpoint Id:' .. endpoint)
获取本地的设备id
local deviceid = gpg.NearbyConnections:GetLocalDeviceId();
print('Local device id:' .. deviceid)
服务
开始服务,在附近广播, 让附近的手机可以找到本机
gpg.NearbyConnections:StartAdvertising(
"\"name\":\"\",\"duration\":0,\"app_identifiers\":{\"identifier\":\"com.sdkbox.gpg\"},",
function(result)
-- start advertising result
if (1 == result.start_advertising_result.status) then
print("GPG start advertising result:" .. result.client_id
.. " status:" .. result.start_advertising_result.status
.. " local_endpoint_name:" .. result.start_advertising_result.local_endpoint_name)
else
print('start advertising failed:' .. result.start_advertising_result.status)
end
end,
function(result)
--request connect callback
local remote_endpoint_id = result.request.remote_endpoint_id
local payload = result.request.payload
log:d('GPG receive connect request:' .. remote_endpoint_id)
-- auto accept or query user
-- 1. accept connect request
-- invoke AcceptConnectionRequest
-- gpg.NearbyConnections:AcceptConnectionRequest(remote_endpoint_id, payload, function (result) end)
-- 2. reject connect request
-- invoke RejectConnectionRequest
end)
停止服务
gpg.NearbyConnections:StopAdvertising()
接受联接请求
gpg.NearbyConnections:AcceptConnectionRequest(
remote_endpoint_id,
payload,
function (result)
print('event:' .. result.event)
if 'OnMessageReceived' == result.event then
print('OnMessageReceived client_id:' .. tostring(result.client_id)
.. ' remote_endpoint_id:' .. tostring(result.remote_endpoint_id)
.. ' payload:' .. tostring(result.payload)
.. ' is_reliable:' .. tostring(result.is_reliable))
elseif 'OnDisconnected' == result.event then
print('OnDisconnected client_id:' .. tostring(result.client_id)
.. ' remote_endpoint_id:' .. tostring(result.remote_endpoint_id))
else
print('Unknown event:' .. result.event);
end
end)
拒绝联接请求
gpg.NearbyConnections:RejectConnectionRequest(remote_endpoint_id)
开始搜索附近
搜索本地附近打开服务的设备, duration 参数单位是 milliseconds
gpg.NearbyConnections:StartDiscovery(server_id, duration,
function (result)
if 'OnEndpointFound' == result.event then
print('found client_id:' .. tostring(result.client_id)
.. ' endpoint_id:' .. tostring(result.endpoint_details.endpoint_id)
.. ' device_id:' .. tostring(result.endpoint_details.device_id)
.. ' name:' .. tostring(result.endpoint_details.name)
.. ' service_id:' .. tostring(result.endpoint_details.server_id))
elseif 'OnEndpointLost' == result.event then
print('endpoint lost')
else
print('unknown event')
end
end)
结束搜索
gpg.NearbyConnections:StopDiscovery()
发送联接请求
gpg.NearbyConnections:SendConnectionRequest(
name, remote_endpoint_id, payload,
function(result)
-- connect response callback
if (1 == result.response.status) then
print('Connect advertising success');
local remote_endpoint_id = result.response.remote_endpoint_id;
else
print('Connect advertising failed');
end
end,
function(result)
if 'OnMessageReceived' == result.event then
print('OnMessageReceived client_id:' .. tostring(result.client_id)
.. ' remote_endpoint_id:' .. tostring(result.remote_endpoint_id)
.. ' payload:' .. tostring(result.payload)
.. ' is_reliable:' .. tostring(result.is_reliable))
elseif 'OnDisconnected' == result.event then
print('OnDisconnected client_id:' .. tostring(result.client_id)
.. ' remote_endpoint_id:' .. tostring(result.remote_endpoint_id))
else
print('Unknown event:' .. result.event);
end
end)
发送可靠消息
gpg.NearbyConnections:SendReliableMessage(remote_endpoint_id1, message)
gpg.NearbyConnections:SendReliableMessage([remote_endpoint_id1, remote_endpoint_id2], message);
发送不可靠消息
gpg.NearbyConnections:SendUnreliableMessage(remote_endpoint_id1, message)
gpg.NearbyConnections:SendUnreliableMessage([remote_endpoint_id1, remote_endpoint_id2], message);
断开
gpg.NearbyConnections:Disconnect(remote_endpoint_id)
停止
gpg.NearbyConnections:Stop()
API Reference
Callback result descriptions
Result Codes
{
VALID = 1,
VALID_BUT_STALE = 2,
VALID_WITH_CONFLICT = 3,
FLUSHED = 4,
ERROR_LICENSE_CHECK_FAILED = -1,
ERROR_INTERNAL = -2,
ERROR_NOT_AUTHORIZED = -3,
ERROR_VERSION_UPDATE_REQUIRED = -4,
ERROR_TIMEOUT = -5,
ERROR_CANCELED = -6,
ERROR_MATCH_ALREADY_REMATCHED = -7,
ERROR_INACTIVE_MATCH = -8,
ERROR_INVALID_RESULTS = -9,
ERROR_INVALID_MATCH = -10,
ERROR_MATCH_OUT_OF_DATE = -11,
ERROR_UI_BUSY = -12,
ERROR_QUEST_NO_LONGER_AVAILABLE = -13,
ERROR_QUEST_NOT_STARTED = -14,
ERROR_MILESTONE_ALREADY_CLAIMED = -15,
ERROR_MILESTONE_CLAIM_FAILED = -16,
ERROR_REAL_TIME_ROOM_NOT_JOINED = -17,
ERROR_LEFT_ROOM = -18
}
Events
Event description
{
"valid" : 1 or 0
"id" : event id,
"name" : event name,
"description" : event description text,
"visibility" : whether or not event can be seen,
"count" : current count of events of this type,
"imageUrl" : URL to the event image
}
Events:FetchAll
{
"status" : 1 or 0 indication success or failure,
"data" : {
event_id : Event,
...
}
}
Events:Fetch
{
"result" : 1 or 0,
"event" : Event
}
Quests
Quest description
{
"valid" : 1 or 0,
"id" : quest id,
"name" : name of quest,
"description" : text description of quest,
"iconUrl" : URL to quest icon,
"bannerUrl" : URL to quest banner image,
"currentMilestone" : QuestMileStone,
"questState" : quest state, accepted or not,
"startTime" : time the quest starts,
"expirationTime" : time the quest expires,
"acceptedTime" : time that the quest was accepted
}
QuestMilestone description
{
"valid" : 1 or 0,
"id" : milestone id,
"questId" : quest id for this milestone,
"eventId" : event id,
"state" : milestone state,
"currentCount" : current count,
"targetCount" : count to complete milestone,
"completionRewardData" : string containing reward data from console
}
Quests:Fetch
{
"result" : 1 or 0,
"quest" : Quest
}
Quests:FetchList
{
"status" : 1 or 0,
"data : 1 based array of Quest
}
Quests:Accept
{
"status" : 1 or 0,
"quest" : Quest
}
Quests:ClaimMilestone
{
"status" : 1 or 0,
"milestone" : QuestMilestone
"quest" : Quest
}
Quests:ShowUI
{
"status" : 1 or 0,
"result" : 1 or 0,
"quest" : Quest
}
Quests:ShowAllUI
{
"status" : 1 or 0,
"result" : 1 or 0,
"quest" : Quest
}
PlayerStats
PlayerStats description
{
"valid" : 1 or 0,
"hasAverageSessionLength" : 1 if valid,
"averageSessionLength" : length of session in ?,
"hasChurnProbability" : 1 if valid,
"churnProbability" : 0 - 1 likelyhood of retention,
"hasDaysSinceLastPlayed" : 1 if valid,
"daysSinceLastPlayed" : count of days,
"hasNumberOfPurchases" : 1 if valid,
"numberOfPurchases" : total number of purchases,
"hasNumberOfSessions" : 1 if valid,
"numberOfSessions" : number of sessions played,
"hasSessionPercentile" : 1 if valid,
"sessionPercentile" : what percentile they are in,
"hasSpendPercentile" : 1 if valid,
"spendPercentile" : what spend percentile they are in
}
Achievements
Achievement description
{
"currentSteps" : Current steps completed of the achievement,
"description" : Description of the achievement.
"id" : String Id,
"lastModifiedTime" : Time last modified,
"name" : Name of the achievement,
"revealedIconUrl" : URL for the revealed icon,
"state" : State, hidden, revealed, unlocked,
"totalSteps" : Total number of steps,
"type" : Incremental or standard,
"unlockedIconUrl" : URL for the unlocked icon,
"valid" : 1 or 0 if valid,
"xp" : Amount of XP awarded
}
Achievements:ShowAllUI
{
"result" : result code
}
Achievements:FetchAll
"result" : result code,
"achievement_array" : array of Achievements
Achievements:Fetch
"result" : result code,
"achievement" : Achievement
Multiplayer
Player
{
"valid",
"id",
"name",
"avatarUrlHiRes",
"avatarUrlIconRes",
"hasLevelInfo",
"currentLevel",
"nextLevel",
"currentXP",
"lastLevelUpTime",
"title",
}
PlayerLevel
{
"valid",
"levelNumber",
"minimumXP",
"maximumXP"
}
Participant
{
"valid",
"displayName",
"avatarUrl",
"id",
"hasPlayer",
"player",
"participantStatus",
"hasMatchResult",
"matchResult",
"matchRank",
"icConnectedToRoom"
}
Room
{
"valid",
"id",
"variant",
"automatchWaitEstimate",
"creatingParticipant",
"creationTime",
"description",
"participants",
"remainingAutomatchingSlots",
"status"
}
MultiplayerInvitation
{
"valid",
"id",
"variant",
"automatchingSlotsAvailable",
"creationTime",
"invitingParticipant",
"type",
"participants"
}
ParticipantResults
{
"valid",
"hasResultsForParticipant",
"placeForParticipant",
"matchResultForParticipant"
}
TurnBasedMatch
{
"valid",
"id",
"creationTime",
"lastUpdateTime",
"creatingParticipant",
"lastUpdatingParticipant",
"pendingParticipant",
"matchStatus",
"automatchingSlotsAvailable",
"variant",
"description",
"number",
"version"
}
NearbyConnections
NearbyConnections:Init
{
"InitializationStatus" : initialization result
}
NearbyConnections:StartAdvertising
{
"client_id" : client id,
"start_advertising_result" :
{
"status" : start advertising result,
"local_endpoint_name" : local endpoint name
}
},
{
"client_id" : client id,
"request" :
{
"remote_endpoint_id" : remote endpoint id,
"remote_device_id" : remote device id
"remote_endpoint_name": remote endpoint name
"payload" : payload message
}
}
NearbyConnections:AcceptConnectionRequest
{
"event" : "OnMessageReceived" or "OnDisconnected",
"client_id" : client id,
"remote_endpoint_id" : remote endpoint id,
"payload" : payload message, valid when event is "OnMessageReceived",
"is_reliable" : if message is reliable, valid when event is "OnMessageReceived"
}
NearbyConnections:StartDiscovery
{
"event" : "OnEndpointFound" or "",
"client_id" : client id,
"remote_endpoint_id", remote endpoint id, valid when event is "OnEndpointLost"
"endpoint_details" : endpoint info, valid when event is "OnEndpointFound"
{
"endpoint_id" : endpoint id,
"device_id" : device id,
"name" : name,
"service_id" : service id
}
}
NearbyConnections:SendConnectionRequest
second callback function result is same with NearbyConnections:AcceptConnectionRequest
{
"client_id" : client id,
"response" : connection response
{
"remote_endpoint_id" : remote endpoint id,
"status" : status code;
"payload" : payload message;
}
},
{
"event" : "OnMessageReceived" or "OnDisconnected",
"client_id" : client id,
"remote_endpoint_id" : remote endpoint id,
"payload" : payload message, valid when event is "OnMessageReceived",
"is_reliable" : if message is reliable, valid when event is "OnMessageReceived"
}
手动集成
如果用 SDKBox Installer 安装失败了, 可以用手动的形式来集成. 如果用 SDKBox Installer 安装成功了,那么不用再手动来集成了.
这些步骤放在文档的最后,因为基本上你很少时候会有需要到他们. 当你要手动集成时,请在完成后,再看一个手动集成以上的这些步骤.
iOS手动集成步骤
把 GooglePlay
包中 plugins/ios 目录下的如下 frameworks 拖到你的 Xcode 工程中, 请确保 Copy items if needed
是选中状态:
sdkbox.framework
PluginGPG.framework
GoogleAppUtilities.framework
GoogleAuthUtilities.framework
GoogleNetworkingUtilities.framework
GoogleOpenSource.framework
GooglePlus.bundle
GooglePlus.framework
GoogleSignIn.bundle
GoogleSignIn.framework
GoogleSymbolUtilities.framework
GoogleUtilities.framework
gpg.bundle
gpg.framework
以上 framwork 是依赖于如下 framework 的,如果你的工程中还没有如下 framework , 添加上:
AddressBook.framework
AssetsLibrary.framework
CoreData.framework
CoreLocation.framework
CoreMotion.framework
CoreTelephony.framework
CoreText.framework
Foundation.framework
MediaPlayer.framework
QuartzCore.framework
SafariServices
Security.framework
StoreKit
Security.framework
SystemConfiguration.framework
libc++.dylib
libz.dylib
在工程中添加如下 linker flag ,工程路径为 Target -> Build Settings -> Linking -> Other Linker Flags:
-ObjC
代码修改
修改 proj.ios_mac/ios/AppController.mm
在 AppController.mm
中添加如下代码:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
return [[GIDSignIn sharedInstance] handleURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
}
设置自己的 Google Play Signin Listener (可选)
设置 GPG(Google Play Game Services) SignIn 的回调
这不是必选的,SDKBox GPG
已经在内部设置了对应的 Delegate, 当你在外面自己手动设置 GIDSignInUIDelegate 时,内部的就会被忽略
在外面自己手动设置 GIDSignInUIDelegate
, 如下步骤:
修改 proj.ios_mac/ios/RootViewController.h
让 RootviewController
实现 GIDSignInUIDelegate
:
#import <GoogleSignIn/GoogleSignIn.h>
// 在 RootViewController 类的定义中加上:
@interface RootViewController : UIViewController<GIDSignInUIDelegate>
设置 Google SignIn Listener
修改 proj.ios_mac/ios/AppController.mm
添加如下代码:
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
在 return 前添加如下代码:
// _viewController could also be named
// viewController, depending of the project type.
[GIDSignIn sharedInstance].uiDelegate = _viewController;
添加 URL types
添加如下 URL types 到你的工程中, 路径位置 your project > Info > URL Types
-
URL 1:
- Identifier:
com.google.ReverseClientId
- Url schemes:
com.googleusercontent.apps.777734739048-cdkbeieil19d6pfkavddrri5o19gk4ni
(这里要使用你自己的 application URL scheme)
- Identifier:
-
URL 2:
- Identifier:
com.google.BundleId
- URL schemes:
com.sdkbox.gpg
(这里要使用你自己的 application 包名)
- Identifier:
更多信息
在官方文档上有更多信息
把 plugin/luabindings
文件夹中所有的头文件和源文件都拷贝到你的工程的 Classes
文件夹中.
把刚刚拷贝的文件拖动到 Xcode 中或使用 File -> Add files to... 来添加.
Android 手动集成
SDKBox 支持三种 Android 工程, command-line, eclipse 和 Android Studio.
- command-line 和 eclipse 类型的项目,
<project_root>
指代proj.android
. - Android Studio 类型的项目,
<project_root>
指代proj.android-studio
.
拷贝文件
把安装包中的 plugin/android/libs
目录下的如下 jar 文件拷贝到你的工程中的
-
如果你使用 cocos2d-x 源码,拷贝 jar 文件到:
Android command-line:
cocos2d/cocos/platform/android/java/libs
Android Studio:
cocos2d/cocos/platform/android/libcocos2dx/libs
-
如果你使用 cocos2d-js 或者 lua ,拷贝 jar 文件到:
Android command-line:
frameworks/cocos2d-x/cocos/platform/android/java/libs
Android Studio:
frameworks/cocos2d-x/cocos/platform/android/libcocos2dx/libs
-
如果你使用 cocos2d-x 预编译包,拷贝 jar 文件到:
Android command-line:
proj.android/libs
拷贝 jni 库
从 plugin/android/jni/
拷贝并覆盖 <your_project_root>/jni/
目录.
修改 AndroidManifest.xml
添加如下 meta-data :
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data android:name="com.google.android.gms.games.APP_ID"
android:value="@string/google_app_id" />
请确保添加如下 <string name="google_app_id">777734739048</string>
到 res/values/string.xml
中.
一定要用你自己的 google app id 来代替.
修改 Android.mk
将 gpg
拷贝到 proj.andrid/jni
目录下
添加如下内容到你的 android.mk
文件中
添加头文件目录
LOCAL_C_INCLUDES += ./gpg/include/
添加静态库
添加静态库到 LOCAL_WHOLE_STATIC_LIBRARIES:
LOCAL_WHOLE_STATIC_LIBRARIES += gpg-1
LOCAL_WHOLE_STATIC_LIBRARIES += PluginGPG
LOCAL_WHOLE_STATIC_LIBRARIES += sdkbox
在 import-module 语句之前添加如下调用:
$(call import-add-path,$(LOCAL_PATH))
在 import-module 语句之后添加如下语句:
$(call import-module, ./gpg)
$(call import-module, ./sdkbox)
$(call import-module, ./plugingpg)
注意: 如果你使用的是预编译版本, 请一定要确保以上语句是在 $(call import-module,./prebuilt-mk)
之前.
修改 Application.mk
(只适用于 Cocos2d-x v3.0 to v3.2)
查看 <project_root>/jni/Application.mk
中的 APP_STL 是否定义正确. 如果 Application.mk
包含了 APP_STL := c++_static
语句, 那么请改为:
APP_STL := gnustl_static
把 plugin/luabindings
文件夹中所有的头文件和源文件都拷贝到你的工程的 Classes
文件夹中.
把你刚刚拷贝的 .cpp
文件添加到 Android.mk
文件的的 LOCAL_SRC_FILES 项.比如
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/HelloWorldScene.cpp \
../../Classes/NewSourceFile.cpp
修改 AppActivity.java
插件版本 >= 2.4.0.3
- 找到 AppActivity.java 文件
find . -name "AppActivity.java"
- 把
extends Cocos2dxActivity
替换为extends com.sdkbox.plugin.SDKBoxActivity
以下是 AppActivity.java 不同版本的引擎所在的目录:
cpp
- proj.android/src/org/cocos2dx/cpp/AppActivity.java
- proj.android-studio/app/src/org/cocos2dx/cpp/AppActivity.java
- proj.android/app/src/org/cocos2dx/cpp/AppActivity.java ( from cocos2d-x 3.17)
lua
- frameworks/runtime-src/proj.android/src/org/cocos2dx/lua/AppActivity.java
- frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/lua/AppActivity.java
- frameworks/runtime-src/proj.android/app/src/org/cocos2dx/lua/AppActivity.java (from cocos2d-x 3.17)
js
- frameworks/runtime-src/proj.android/src/org/cocos2dx/javascript/AppActivity.java
- frameworks/runtime-src/proj.android/app/src/org/cocos2dx/javascript/AppActivity.java ( from cocos2d-x 3.17)
插件版本 < 2.4.0.3
-
如果您使用 cocos2d-x 源代码,假设您在
proj.android
目录下,那么您可以在如下位置找到Cocos2dxActivity.java
文件:../../cocos2d-x/cocos/platform/android/java/src/org/cocos2dx/ lib/Cocos2dxActivity.java
-
如果您使用 cocos2dx-x 预编译库, 假设您在
proj.android
目录下,那么您可以在如下位置找到Cocos2dxActivity.java
文件:./src/org/cocos2dx/lib/Cocos2dxActivity.java
Note: 当你使用 cocos2d-x 源代码时,不同的版本中 Cocos2dxActivity.java
文件的位置也不同。一个确定该文件位置的方法是查看 proj.android/project.properties
。比如:
android.library.reference.1=../../cocos2d-x/cocos/platform/android/java
在这个例子中, Cocos2dxActivity.java
文件应该在如下位置:
../../cocos2d-x/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java
- 修改
Cocos2dxActivity.java
文件,导入如下包:
import android.content.Intent;
import com.sdkbox.plugin.SDKBox;
- 然后,修改
Cocos2dxActivity
类的onCreate(final Bundle savedInstanceState)
函数,添加一个调用语句SDKBox.init(this);
。添加的位置非常重要,必须在调用onLoadNativeLibraries();
之后。如下:
onLoadNativeLibraries();
SDKBox.init(this);
-
最后, 我需要提供合适的 overrides 方法的代码。这里有一些约定如下。
-
如果这个被列出的方法没有在
SDKBox
中定义,那么__定义它__。 -
如果这个被列出的方法已经被定义在
SDKBox
中,那么请调用这个在SDKBox
中的__同名方法__。
-
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(!SDKBox.onActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
protected void onStart() {
super.onStart();
SDKBox.onStart();
}
@Override
protected void onStop() {
super.onStop();
SDKBox.onStop();
}
@Override
protected void onResume() {
super.onResume();
SDKBox.onResume();
}
@Override
protected void onPause() {
super.onPause();
SDKBox.onPause();
}
@Override
public void onBackPressed() {
if(!SDKBox.onBackPressed()) {
super.onBackPressed();
}
}
修改 project.properties
添加 Google Play Services Android 库引用, 其路径因你的配置而有所不同. 并且 Google Play Services 不是默认下载的, 您需要打开 sdk installer , 选择 extras->google play services 下载并安装。例子:
android.library.reference.1=
../android/sdk.latest/extras/google/google_play_services/libproject/
google-play-services_lib
注意: 如果已经存在 android.library.reference.1
, 您可以递增数字, 例如 android.library.reference.2
, 等等.
Proguard (可选)
- 编辑
project.properties
,写入一个Proguard
配置. 例如:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
- 编辑你指定的这个文件,添加如下内容:
# cocos2d-x
-keep public class org.cocos2dx.** { *; }
-dontwarn org.cocos2dx.**
-keep public class com.chukong.** { *; }
-dontwarn com.chukong.**
# google play service
-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.**
-keep class * extends java.util.ListResourceBundle {
protected Object[][] getContents();
}
-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
public static final *** NULL;
}
-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
@com.google.android.gms.common.annotation.KeepName *;
}
-keepnames class * implements android.os.Parcelable {
public static final ** CREATOR;
}
#sdkbox
-keep class com.sdkbox.** { *; }
-dontwarn com.sdkbox.**
注意: Proguard 只在 Release 编译时有效 (比如 cocos run -m release
), debug 不会触发 Proguard.