I have configured cocos2dx into Android Studio works perfectly fine (NDK 15 latest update). Integrated SDKBOX with the command $ sdkbox import gpg
ran on my root folder of cocos2dx project. Integration seemed working fine and sdkbox::PluginGPG::init();
inside my AppDelegate::applicationDidFinishLaunching()
didnt seem to fire any exceptions, build and ran my game successfully. It was only after I tried to implement gpg Real-Time Multiplayer in a class which I DIDNT actually use that I started getting compile time error: Error:error: ld returned 1 exit status
the full stack is as follows:
Information:Gradle tasks [:mProject:assembleDebug]
Error:error: ld returned 1 exit status
Information:BUILD FAILED
Information:Total time: 2 mins 9.195 secs
Information:1 error
Information:0 warnings
Information:See complete output in console
Simple enough right? But no matter what I tried I couldnt get rid of the build error. Tried rebuilding, checking the integrity of SDKBOX by following the manual Android Studio installation, no luck. Following are my new classes which cause compilation errors: (very similar to official C++ samples)
AMSMPNativeActivity.cpp
//
// Created by Ams on 9/1/2017.
//
//#include <platform/CCStdC.h>
//#include <base/CCConsole.h>
#include "AMSMPNativeActivity.h"
void AMSMPNativeActivity::InitGooglePlayGameServices() {
if (service_ != nullptr) {
return;
}
gpg::AndroidInitialization::android_main(app_);
// Game Services have not been initialized, create a new Game Services.
gpg::AndroidPlatformConfiguration platform_configuration;
platform_configuration.SetActivity(app_->activity->clazz);
gpg::GameServices::Builder builder;
service_ =
builder.SetOnAuthActionStarted([this](gpg::AuthOperation op) {
// This callback is invoked when auth
// action started
// While auth action is going on, disable
// auth and related UI
OnAuthActionStarted(op);
})
.SetOnAuthActionFinished([this](gpg::AuthOperation op,
gpg::AuthStatus status) {
// This callback is invoked when auth action finished
// Check status code and update UI to signed-in state
OnAuthActionFinished(op, status);
})
.SetOnMultiplayerInvitationEvent([this](
gpg::MultiplayerEvent event, std::string match_id,
gpg::MultiplayerInvitation invitation) {
// Invoked when invitation has been received
// It can be received from the Play Game app, a notification, or
// live while the app is running.
// (e.g. Though PlayGam app, while the app is running)
//CCLOG("MultiplayerInvitationEvent callback");
if (event ==
gpg::TurnBasedMultiplayerEvent::UPDATED_FROM_APP_LAUNCH) {
// In this case, an invitation has been accepted already
// in notification or in Play game app
gpg::RealTimeMultiplayerManager::RealTimeRoomResponse result =
service_->RealTimeMultiplayer().AcceptInvitationBlocking(
invitation, this);
if (gpg::IsSuccess(result.status)) {
room_ = result.room;
service_->RealTimeMultiplayer().ShowWaitingRoomUI(
room_, MIN_PLAYERS,
[this](gpg::RealTimeMultiplayerManager::
WaitingRoomUIResponse const &waitResult) {
EnableUI(true);
if (gpg::IsSuccess(waitResult.status)) {
PlayGame();
}
});
} else {
LeaveGame();
}
} else {
// Otherwise, show default inbox and let players to accept an
// invitation
ShowRoomInbox();
}
})
.Create(platform_configuration);
}
void AMSMPNativeActivity::OnAuthActionStarted(gpg::AuthOperation op) {
if(!initialized_resources_) {
return;
}
// do whatever when signing in
/*ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this, op]() {
EnableUI(false);
if (op == gpg::AuthOperation::SIGN_IN) {
status_text_->SetAttribute("Text", "Signing In...");
} else {
status_text_->SetAttribute("Text", "Signing Out...");
}
});*/
}
void AMSMPNativeActivity::OnAuthActionFinished(gpg::AuthOperation op,
gpg::AuthStatus status) {
if (gpg::IsSuccess(status)) {
service_->Players().FetchSelf([this](
gpg::PlayerManager::FetchSelfResponse const &response) {
if (gpg::IsSuccess(response.status)) {
self_id_ = response.data.Id();
}
});
}
if(!initialized_resources_) {
return;
}
}
void AMSMPNativeActivity::ShowRoomInbox() {
service_->RealTimeMultiplayer().ShowRoomInboxUI([this](
gpg::RealTimeMultiplayerManager::RoomInboxUIResponse const &response) {
if (gpg::IsSuccess(response.status)) {
gpg::RealTimeMultiplayerManager::RealTimeRoomResponse result =
service_->RealTimeMultiplayer().AcceptInvitationBlocking(
response.invitation, this);
if (gpg::IsSuccess(result.status)) {
room_ = result.room;
service_->RealTimeMultiplayer().ShowWaitingRoomUI(
room_, MIN_PLAYERS,
[this](
gpg::RealTimeMultiplayerManager::WaitingRoomUIResponse const &
waitResult) {
EnableUI(true);
if (gpg::IsSuccess(waitResult.status)) {
PlayGame();
}
});
} else
EnableUI(true); // Go back to original state for retry
} else {
//CCLOG("Invalid response status");
EnableUI(true); // Go back to original state for retry
}
});
EnableUI(false);
}
void AMSMPNativeActivity::QuickMatch() {
gpg::RealTimeRoomConfig config =
gpg::RealTimeRoomConfig::Builder()
.SetMinimumAutomatchingPlayers(MIN_PLAYERS)
.SetMaximumAutomatchingPlayers(MAX_PLAYERS)
.Create();
service_->RealTimeMultiplayer().CreateRealTimeRoom(
config, this,
[this](gpg::RealTimeMultiplayerManager::RealTimeRoomResponse const &
response) {
//CCLOG("created a room %d", response.status);
if (gpg::IsSuccess(response.status)) {
room_ = response.room;
service_->RealTimeMultiplayer().ShowWaitingRoomUI(
room_, MIN_PLAYERS,
[this](
gpg::RealTimeMultiplayerManager::WaitingRoomUIResponse const &
waitResult) {
EnableUI(true);
if (gpg::IsSuccess(waitResult.status)) {
PlayGame();
}
});
} else
EnableUI(true); // Go back to original state for retry
});
EnableUI(false);
}
void AMSMPNativeActivity::InviteFriend() {
service_->RealTimeMultiplayer().ShowPlayerSelectUI(
MIN_PLAYERS, MAX_PLAYERS, true,
[this](gpg::RealTimeMultiplayerManager::PlayerSelectUIResponse const &
response) {
//CCLOG("inviting friends %d", response.status);
if (gpg::IsSuccess(response.status)) {
// Create room
gpg::RealTimeRoomConfig config =
gpg::RealTimeRoomConfig::Builder()
.PopulateFromPlayerSelectUIResponse(response)
.Create();
auto roomResponse =
service_->RealTimeMultiplayer().CreateRealTimeRoomBlocking(config,
this);
if (gpg::IsSuccess(roomResponse.status)) {
room_ = roomResponse.room;
service_->RealTimeMultiplayer().ShowWaitingRoomUI(
room_, MIN_PLAYERS,
[this](gpg::RealTimeMultiplayerManager::
WaitingRoomUIResponse const &waitResult) {
EnableUI(true);
if (gpg::IsSuccess(waitResult.status)) {
PlayGame();
}
});
} else
EnableUI(true); // Go back to original state for retry
} else
EnableUI(true); // Go back to original state for retry
});
EnableUI(false);
}
void AMSMPNativeActivity::BroadcastScore(bool bFinal) {
std::vector<uint8_t> v;
if (!bFinal) {
v.push_back('U');
v.push_back(static_cast<uint8_t>(score_counter_));
service_->RealTimeMultiplayer().SendUnreliableMessageToOthers(room_, v);
} else {
v.push_back('F');
v.push_back(static_cast<uint8_t>(score_counter_));
const std::vector<gpg::MultiplayerParticipant> participants =
room_.Participants();
for (gpg::MultiplayerParticipant participant : participants) {
service_->RealTimeMultiplayer().SendReliableMessage(
room_, participant, v, [](gpg::MultiplayerStatus const &) {});
}
}
}
void AMSMPNativeActivity::OnDataReceived(gpg::RealTimeRoom const &room,
gpg::MultiplayerParticipant const &from_participant,
std::vector<uint8_t> data, bool is_reliable) {
if (data[0] == 'F' && is_reliable) {
// Got final score
players_score_[from_participant.Id()].score = data[1];
players_score_[from_participant.Id()].finished = true;
//CCLOG("Got final data from Dispname:%s ID:%s",
//from_participant.DisplayName().c_str(), from_participant.Id().c_str());
} else if (data[0] == 'U' && !is_reliable) {
// Got current score
uint8_t score = players_score_[from_participant.Id()].score;
players_score_[from_participant.Id()].score = std::max(score, data[1]);
//CCLOG("Got data from Dispname:%s ID:%s",
// from_participant.DisplayName().c_str(), from_participant.Id().c_str());
}
UpdateScore();
}
void AMSMPNativeActivity::OnRoomStatusChanged(gpg::RealTimeRoom const &room) {
room_ = room;
}
void AMSMPNativeActivity::OnParticipantStatusChanged(
gpg::RealTimeRoom const &room,
gpg::MultiplayerParticipant const &participant) {
if (participant.Status() != gpg::ParticipantStatus::JOINED) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (players_score_.find(participant.Id()) != players_score_.end()) {
players_score_[participant.Id()].finished = true;
}
}
UpdateScore();
}
}
void AMSMPNativeActivity::PlayGame() {
}
void AMSMPNativeActivity::UpdateScore() {
std::lock_guard<std::mutex> lock(mutex_);
int32_t SIZE = 64;
char str[SIZE];
snprintf(str, SIZE, "%03d", score_counter_);
std::string str_myscore(str);
snprintf(str, SIZE, "My score: %03d %s\n", score_counter_,
playing_ ? "" : "*");
std::string allstr(str);
std::vector<gpg::MultiplayerParticipant> participants = room_.Participants();
for (gpg::MultiplayerParticipant participant : participants) {
if (participant.HasPlayer() &&
participant.Player().Id().compare(self_id_) == 0)
continue; // Skip local player
int32_t score = 0;
bool finished = false;
if (players_score_.find(participant.Id()) != players_score_.end()) {
score = players_score_[participant.Id()].score;
finished = players_score_[participant.Id()].finished;
}
snprintf(str, SIZE, "%s: %03d %s\n", participant.DisplayName().c_str(),
score, finished ? "*" : "");
allstr += str;
}
}
void AMSMPNativeActivity::LeaveGame() {
std::lock_guard<std::mutex> lock(mutex_);
service_->RealTimeMultiplayer().LeaveRoom(
room_, [](gpg::ResponseStatus const &status) {});
playing_ = false;
}
void AMSMPNativeActivity::EnableUI(bool enable) {
}
extern "C" {
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityResult(
JNIEnv *env, jobject thiz, jobject activity, jint requestCode,
jint resultCode, jobject data) {
gpg::AndroidSupport::OnActivityResult(env, activity, requestCode, resultCode,
data);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityCreated(
JNIEnv *env, jobject thiz, jobject activity, jobject saved_instance_state) {
gpg::AndroidSupport::OnActivityCreated(env, activity, saved_instance_state);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityDestroyed(
JNIEnv *env, jobject thiz, jobject activity) {
gpg::AndroidSupport::OnActivityDestroyed(env, activity);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityPaused(
JNIEnv *env, jobject thiz, jobject activity) {
gpg::AndroidSupport::OnActivityPaused(env, activity);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityResumed(
JNIEnv *env, jobject thiz, jobject activity) {
gpg::AndroidSupport::OnActivityResumed(env, activity);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivitySaveInstanceState(
JNIEnv *env, jobject thiz, jobject activity, jobject out_state) {
gpg::AndroidSupport::OnActivitySaveInstanceState(env, activity, out_state);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityStarted(
JNIEnv *env, jobject thiz, jobject activity) {
gpg::AndroidSupport::OnActivityStarted(env, activity);
}
JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityStopped(
JNIEnv *env, jobject thiz, jobject activity) {
gpg::AndroidSupport::OnActivityStopped(env, activity);
}
}
AMSMPNativeActivity.h
//
// Created by Ams on 9/1/2017.
//
#ifndef PROJ_ANDROID_STUDIO_AMSMPNATIVEACTIVITY_H
#define PROJ_ANDROID_STUDIO_AMSMPNATIVEACTIVITY_H
/*
* Include files
*/
#include <jni.h>
#include <errno.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include <cpu-features.h>
#include <sstream>
#include <algorithm>
#include <thread>
#include <unordered_map>
#include <mutex>
// For GPGS
#include "gpg/gpg.h"
/*
* Preprocessors
*/
// Class name of helper function
#define HELPER_CLASS_NAME "com.sample.helper.NDKHelper"
// Class name of JUIhelper function
#define JUIHELPER_CLASS_NAME "com.sample.helper.JUIHelper"
// Share object name of helper function library
#define HELPER_CLASS_SONAME "ButtonClickerNativeActivity"
//
// Also set "com.google.android.gms.games.APP_ID" in AndrdoiManifest.xml
//
const int32_t MIN_PLAYERS = 1;
const int32_t MAX_PLAYERS = 3;
const double GAME_DURATION = 20.0;
enum NEXT_PARTICIPANT {
NEXT_PARTICIPANT_AUTOMATCH = -1,
NEXT_PARTICIPANT_NONE = -2,
};
struct PLAYER_STATUS {
int32_t score;
bool finished;
};
/*
* Engine class of the sample
*/
struct android_app;
class AMSMPNativeActivity : public gpg::IRealTimeEventListener {
public:
void InitGooglePlayGameServices();
void InviteFriend();
void ShowRoomInbox();
void InitializeGame();
void PlayGame();
void LeaveGame();
void QuickMatch();
void BroadcastScore(bool bFinal);
static void HandleCmd(struct android_app *app, int32_t cmd);
static int32_t HandleInput(android_app *app, AInputEvent *event);
void UpdatePosition(AInputEvent *event, int32_t iIndex, float &fX, float &fY);
AMSMPNativeActivity();
~AMSMPNativeActivity();
void SetState(android_app *state);
int InitDisplay(const int32_t cmd);
void LoadResources();
void UnloadResources();
void DrawFrame();
void TermDisplay(const int32_t cmd);
void TrimMemory();
bool IsReady();
virtual void OnRoomStatusChanged(gpg::RealTimeRoom const &room);
virtual void OnParticipantStatusChanged(
gpg::RealTimeRoom const &room,
gpg::MultiplayerParticipant const &participant);
virtual void OnDataReceived(
gpg::RealTimeRoom const &room,
gpg::MultiplayerParticipant const &from_participant,
std::vector<uint8_t> data, bool is_reliable);
virtual void OnConnectedSetChanged(gpg::RealTimeRoom const &room) {}
virtual void OnP2PConnected(gpg::RealTimeRoom const &room,
gpg::MultiplayerParticipant const &participant) {}
virtual void OnP2PDisconnected(
gpg::RealTimeRoom const &room,
gpg::MultiplayerParticipant const &participant) {}
private:
// Callbacks for GPG authentication.
void OnAuthActionStarted(gpg::AuthOperation op);
void OnAuthActionFinished(gpg::AuthOperation op, gpg::AuthStatus status);
void UpdateScore();
bool UpdateTime();
void EnableUI(bool enable);
void InitUI();
void UpdateFPS(float fFPS);
std::unique_ptr<gpg::GameServices> service_; // gpg service instance
std::unordered_map<std::string, PLAYER_STATUS> players_score_; // hashmap to
gpg::RealTimeRoom room_;
int32_t score_counter_; // Score counter of local player
bool playing_; // Am I playing a game?
std::string self_id_; // Local player's ID
double start_time_; // Game start time
mutable std::mutex mutex_;
bool initialized_resources_;
bool has_focus_;
android_app *app_;
};
#endif //PROJ_ANDROID_STUDIO_AMSMPNATIVEACTIVITY_H
I could also put Android.mk
or gradle files here but again, prior to adding the above C++ codes into my build path I did not get any compile time errors so there must be something wrong in the implementation of AMSMPNativeActivity.h
and AMSMPNativeActivity.cpp
then again, I could put them here too if they could potentially help solve the problem.