‹ 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.hRootviewController 实现 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

更多资料

官方文档中查看更多的相关信息

重点注意事项

如果您升级到了 Xcode7, 则需要以下额外步骤来确保插件工作正常:

禁用应用程序安全传输策略

添加以下项到 plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

添加后的文件内容看起来就像这样:

禁止 Bitcode 支持

您必须禁止 Bitcode 的支持,否则将会编译失败。

游戏全屏配置

如果您的游戏不同时支持横竖屏,则必须在 Xcode 中选中 Requires full screen,否则将不会通过 Apple 的审核。

canOpenURL 白名单

取决于您使用哪些插件。需要在 info.plistLSApplicationQueriesSchemes 下添加名单。

关于 Creator 工程

Creator 导出工程会修改工程的配置, 这可能会影响 SDKBox 对工程的配置修改. 所以可以有以下做法(任选其一):

  1. 保存导出工程中的修改, 对于 SDKBox 来说可能有这些文件(mk, gradle, gradle.properties, AppDelegate.cpp, ...), 导出后再恢复对应该文件
  2. 在每一次 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.hRootviewController 实现 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

更多信息

官方文档上有更多信息

plugin/luabindings 文件夹中所有的头文件和源文件都拷贝到你的工程的 Classes 文件夹中.

把刚刚拷贝的文件拖动到 Xcode 中或使用 File -> Add files to... 来添加.

Android 手动集成

SDKBox 支持三种 Android 工程, command-line, eclipseAndroid Studio.

拷贝文件

把安装包中的 plugin/android/libs 目录下的如下 jar 文件拷贝到你的工程中的 /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

  1. 找到 AppActivity.java 文件
find . -name "AppActivity.java"
  1. 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

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
import android.content.Intent;
import com.sdkbox.plugin.SDKBox;
onLoadNativeLibraries();
SDKBox.init(this);
    @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 (可选)

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.