Cocos2d-x C++ Client Guide #

The official C++ client handles all communication in real-time with the server. It implements all features in the server and is written with C++11.

The Cocos2d-x C++ client is open source on GitHub. Please report issues and contribute code to help us improve it.

Download #

The client SDK can be downloaded from the GitHub releases. You can download “nakama-cocos2d-x-sdk_$version.zip”.

For upgrades you can see changes and enhancements in the CHANGELOG before you update to newer versions.

Setup #

When you’ve downloaded the Nakama Cocos2d archive and extracted it to NAKAMA_COCOS2D_SDK folder, you should include it in your project.

We don’t recommend to copy Nakama Cocos2d SDK to your project because it’s quite big in size (~600 Mb).

Copy NakamaCocos2d folder #

Copy NakamaCocos2d folder from NAKAMA_COCOS2D_SDK to your Classes folder.

Setup for Mac and iOS projects #

  1. Add NAKAMA_COCOS2D_SDK/include in Build Settings > Header Search Paths
  2. Add libs folder in Build Settings > Library Search Paths:
    • NAKAMA_COCOS2D_SDK/libs/ios - for iOS
    • NAKAMA_COCOS2D_SDK/libs/mac - for Mac
  3. Add all .a files located in libs folder to General > Frameworks, Libraries, and Embedded Content
  4. Add NAKAMA_COCOS2D_SDK/NakamaCocos2d folder to your XCode project as group (not a reference).

Setup for Android projects #

If you use CMake then see Setup for CMake projects section.

If you use ndk-build then add following to your Android.mk file:

1
2
3
4
5
6
7
8
# add this to your module
LOCAL_STATIC_LIBRARIES += nakama-cpp

# add this after $(call import-add-path, $(LOCAL_PATH)/../../../cocos2d)
$(call import-add-path, NAKAMA_COCOS2D_SDK)

# add this after $(call import-module, cocos)
$(call import-module, nakama-cpp-android)

Android uses a permissions system which determines which platform services the application will request to use and ask permission for from the user. The client uses the network to communicate with the server so you must add the “INTERNET” permission.

1
<uses-permission android:name="android.permission.INTERNET"/>

Setup for CMake projects #

Open for edit your CMakeLists.txt file and find following existing code:

1
2
3
4
5
# mark app complie info and libs info
set(all_code_files
        ${GAME_HEADER}
        ${GAME_SOURCE}
    )

add next code before:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Cocos2d Nakama sources
list(APPEND GAME_SOURCE
        Classes/NakamaCocos2d/NCocosWebSocket.cpp
        Classes/NakamaCocos2d/NCocosHTTP.cpp
        Classes/NakamaCocos2d/NCocosHelper.cpp
    )
# Cocos2d Nakama headers
list(APPEND GAME_HEADER
        Classes/NakamaCocos2d/NCocosWebSocket.h
        Classes/NakamaCocos2d/NCocosHTTP.h
        Classes/NakamaCocos2d/NCocosLogSink.h
        Classes/NakamaCocos2d/NCocosHelper.h
    )

At bottom of your CMakeLists.txt file add following:

1
2
add_subdirectory(NAKAMA_COCOS2D_SDK ${CMAKE_CURRENT_BINARY_DIR}/nakama-cpp)
target_link_libraries(${APP_NAME} ext_nakama-cpp)

Setup for Visual Studio projects #

In Project Settings add following:

  1. Add NAKAMA_COCOS2D_SDK/include to C/C++ > General > Additional Include Directories
  2. Add folder to Linker > General > Additional Library Directories:
    • NAKAMA_COCOS2D_SDK/libs/win32/v140 - for VS 2015 x86
    • NAKAMA_COCOS2D_SDK/libs/win64/v140 - for VS 2015 x64
    • NAKAMA_COCOS2D_SDK/libs/win32/v141 - for VS 2017 x86
    • NAKAMA_COCOS2D_SDK/libs/win64/v141 - for VS 2017 x64
    • NAKAMA_COCOS2D_SDK/libs/win32/v142 - for VS 2019 x86
    • NAKAMA_COCOS2D_SDK/libs/win64/v142 - for VS 2019 x64
  3. Add all .lib files located in above folder to Linker > Input > Additional Dependencies
  4. Add sources from Classes/NakamaCocos2d to your Visual Studio project.

Usage #

Include nakama helper header.

1
#include "NakamaCocos2d/NCocosHelper.h"

Initialize logger with debug logging level.

1
2
3
using namespace Nakama;

NCocosHelper::init(NLogLevel::Debug);

The client object is used to execute all logic against the server.

1
2
3
4
5
6
7
8
9
// Default connection settings for a local Nakama server
NClientParameters parameters;
parameters.serverKey = "defaultkey";
parameters.host = "127.0.0.1";
parameters.port = DEFAULT_PORT;
NClientPtr client = NCocosHelper::createDefaultClient(parameters);

// Quickly setup a client for a local server.
NClientPtr client = NCocosHelper::createDefaultClient(NClientParameters());

Tick #

The tick method pumps requests queue and executes callbacks in your thread. You must call it periodically (recommended every 50ms) in your thread.

1
2
3
4
5
6
7
8
9
auto tickCallback = [this](float dt)
{
    client->tick();
    if (rtClient)
        rtClient->tick();
};

auto scheduler = Director::getInstance()->getScheduler();
scheduler->schedule(tickCallback, this, 0.05f /*sec*/, CC_REPEAT_FOREVER, 0, false, "nakama-tick");

Without this the default client and real-time client will not work, and you will not receive responses from the server.

Authenticate #

With a client object you can authenticate against the server. You can register or login a user with one of the authenticate options.

To authenticate you should follow our recommended pattern in your client code:

  1. Build an instance of the client:
1
NClientPtr client = NCocosHelper::createDefaultClient(NClientParameters());
  1. Authenticate a user. By default Nakama will try and create a user if it doesn’t exist:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
auto loginFailedCallback = [](const NError& error)
{
};

auto loginSucceededCallback = [](NSessionPtr session)
{
};

std::string deviceId = "unique device id";

client->authenticateDevice(
        deviceId,
        opt::nullopt,
        opt::nullopt,
        loginSucceededCallback,
        loginFailedCallback);

It is good practice to cache a device identifier when authenticating on Android because they can change with device OS updates.

In the code above we use authenticateDevice() but for other authentication options have a look at the code examples.

Sessions #

When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a NSession object.

1
2
3
4
5
6
CCLOG("%s", session->getAuthToken().c_str()); // raw JWT token
CCLOG("%s", session->getUserId().c_str());
CCLOG("%s", session->getUsername().c_str());
CCLOG("Session has expired: %s", session->isExpired() ? "yes" : "no");
CCLOG("Session expires at: %llu", session->getExpireTime());
CCLOG("Session created at: %llu", session->getCreateTime());

It is recommended to store the auth token from the session and check at startup if it has expired. If the token has expired you must reauthenticate. The expiry time of the token can be changed as a setting in the server.

A full example class with all code above is here.

Send requests #

When a user has been authenticated a session is used to connect with the server. You can then send messages for all the different features in the server.

This could be to add friends, join groups, or submit scores in leaderboards. You can also execute remote code on the server via RPC.

All requests are sent with a session object which authorizes the client.

1
2
3
4
5
6
7
8
auto successCallback = [](const NAccount& account)
{
    CCLOG("user id : %s", account.user.id.c_str());
    CCLOG("username: %s", account.user.username.c_str());
    CCLOG("wallet  : %s", account.wallet.c_str());
};

client->getAccount(session, successCallback, errorCallback);

Have a look at other sections of documentation for more code examples.

Real-time client #

The client can create one or more real-time clients. Each real-time client can have it’s own event listener registered for responses received from the server.

The socket is exposed on a different port on the server to the client. You’ll need to specify a different port here to ensure that connection is established successfully.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
bool createStatus = true; // if the server should show the user as online to others.
// define real-time client in your class as NRtClientPtr rtClient;
rtClient = NCocosHelper::createRtClient(client, DEFAULT_PORT);
// define listener in your class as NRtDefaultClientListener listener;
listener.setConnectCallback([]()
{
    CCLOG("Socket connected");
});
rtClient->setListener(&listener);
rtClient->connect(session, createStatus);

Don’t forget to call tick method. See Tick section for details.

You can use real-time client to send and receive chat messages, get notifications, and matchmake into a multiplayer match. You can also execute remote code on the server via RPC.

To join a chat channel and receive messages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
listener.setChannelMessageCallback([](const NChannelMessage& message)
{
    CCLOG("Received a message on channel %s", message.channel_id.c_str());
    CCLOG("Message content: %s", message.content.c_str());
});

std::string roomName = "Heroes";

auto successJoinCallback = [this](NChannelPtr channel)
{
    CCLOG("joined chat: %s", channel->id.c_str());

    // content must be JSON
    std::string content = "{\"message\":\"Hello world\"}";

    rtClient->writeChatMessage(channel->id, content);
};

rtClient->joinChat(
            roomName,
            NChannelType::ROOM,
            {},
            {},
            successJoinCallback,
            errorCallback);

There are more examples for chat channels here.

Handle events #

A real-time client has event handlers which are called on various messages received from the server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
listener.setStatusPresenceCallback([](const NStatusPresenceEvent& event)
{
    for (auto& presence : event.joins)
    {
        CCLOG("Joined User ID: %s Username: %s Status: %s", presence.user_id.c_str(), presence.username.c_str(), presence.status.c_str());
    }

    for (auto& presence : event.leaves)
    {
        CCLOG("Left User ID: %s Username: %s Status: %s", presence.user_id.c_str(), presence.username.c_str(), presence.status.c_str());
    }
});

Event handlers only need to be implemented for the features you want to use.

CallbacksDescription
onDisconnectReceived when the client is disconnected from the server.
onNotificationReceives live in-app notifications sent from the server.
onChannelMessageReceives real-time chat messages sent by other users.
onChannelPresenceReceives join and leave events within chat.
onMatchStateReceives real-time multiplayer match data.
onMatchPresenceReceives join and leave events within real-time multiplayer.
onMatchmakerMatchedReceived when the matchmaker has found a suitable match.
onStatusPresenceReceives status updates when subscribed to a user status feed.
onStreamPresenceReceives stream join and leave event.
onStreamStateReceives stream data sent by the server.

Logging #

Client logging is off by default.

To enable logs output to console with debug logging level:

1
NCocosHelper::init(NLogLevel::Debug);

Errors #

The server and the client can generate logs which are helpful to debug code.

To enable client logs see Logging section.

In every request in the client you can set error callback. It will be called when request fails. The callback has NError structure which contains details of the error:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
auto errorCallback = [](const NError& error)
{
    // convert error to readable string
    CCLOGERROR("%s", toString(error).c_str());

    // check error code
    if (error.code == ErrorCode::ConnectionError)
    {
        CCLOG("The server is currently unavailable. Check internet connection.");
    }
};

client->getAccount(session, successCallback, errorCallback);

The client writes all errors to logger so you don’t need to do this.

Full Cocos2d-x C++ example #

You can find the Cocos2d-x C++ example here.

Client reference #

You can find the C++ Client Reference here.