I'm trying to develop a simple Turn based multiplayer game in android using Play games services. I followed all the steps in the docs: https://developers.google.com/games/services/android/turnbasedMultiplayer#implementing_auto-matching The only difference is that I don't want my players to be able to invite friends I want it to be purely Automatching. The game is only 2 player and it can only start ONCE 2 players have been matched. My problem is it doesn't seem to be automatching players. I run the app on 2 devices and they never seem to find each other... They both connect to the Play Games Services fine but they both just create a new game. Here is my code after I connect the GoogleApiClient.
#Override
public void onConnected(Bundle bundle) {
pDialog.dismiss();
Toast.makeText(this, "Connected", Toast.LENGTH_LONG).show();
showDialog("Matching players");
Bundle autoMatchCriteria = RoomConfig.createAutoMatchCriteria(1,1,0);
TurnBasedMatchConfig tbmc=TurnBasedMatchConfig.builder().
setAutoMatchCriteria(autoMatchCriteria).build();
Games.TurnBasedMultiplayer.createMatch(mGoogleApiClient,
tbmc).setResultCallback(new MatchInitiatedCallback(this));
}
Here is my MatchInitiatedCallback
public class MatchInitiatedCallback implements
ResultCallback<TurnBasedMultiplayer.InitiateMatchResult> {
private Context context;
public MatchInitiatedCallback(Context c) {
context = c;
}
#Override
public void onResult(TurnBasedMultiplayer.InitiateMatchResult
initiateMatchResult) {
pDialog.dismiss();
if(!initiateMatchResult.getStatus().isSuccess()) {
Toast.makeText(context, "ERROR: " +
initiateMatchResult.getStatus().getStatusCode(), Toast.LENGTH_LONG).show();
return;
}
TurnBasedMatch match = initiateMatchResult.getMatch();
if(match.getData() != null) {
Toast.makeText(context, "Player2 " + match.getData().toString(),
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(context, "Player1", Toast.LENGTH_LONG).show();
initGame(match);
}
}
}
Both devices show the TOAST that says: "Player1" and call the initGame(match) method which is here:
public void initGame(TurnBasedMatch match) {
String initialise = "initialised";
Games.TurnBasedMultiplayer.takeTurn(mGoogleApiClient,
match.getMatchId(),initialise.getBytes(Charset.forName("UTF-16")),
match.getParticipantId(Games.Players.getCurrentPlayerId(mGoogleApiClient))).
setResultCallback(this);
}
#Override
public void onResult(TurnBasedMultiplayer.UpdateMatchResult
updateMatchResult) {
if(updateMatchResult.getMatch().getStatus() ==
TurnBasedMatch.MATCH_STATUS_AUTO_MATCHING) {
Toast.makeText(this, "Still automatching",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Not automatching", Toast.LENGTH_LONG).show();
}
}
And once again they both Display the TOAST: "Still automatching"
What am I doing wrong. Why don't the devices automatch. Did I skip a step somewhere. Please help.
Try to change your initGame method so it specifies null for the next participant (to let Play Game services find a player for auto-matching):
public void initGame(TurnBasedMatch match) {
String initialise = "initialised";
Games.TurnBasedMultiplayer.takeTurn(
mGoogleApiClient,
match.getMatchId(),
initialise.getBytes(Charset.forName("UTF-16")),
null
).setResultCallback(this);
}
Also make sure to use two different google+ accounts on the two devices.
I tried your code and both devices said "Player1" and "Not automatching"(!). After changing to null, first device said "Player1" and "Still automatching" and second device said "Player2 [B#41b08830"
I think your problem is due to the way invitations as well as auto matching is handled by Play Game services. When hooking up participants to a TurnBasedMultiplayer match, invitations are sent to participants one by one. When you call takeTurn in the initGame method, you must specify the next participant to receive an invitation. When that participant has accepted the invitation, he must call takeTurn and specify the next participant to receive an invitation and so on. If you specify null, it means the next invitation goes to an automatched player.
Related
I have it showing fine for one leader board, whatever board, I call it by itself. When I want to show all of the boards I make the call below. It shows the last board called only. I understand this, what I want to know is there a way to just call the leader boards without calling one at a time so the user can compare the scores to each other?
private void signToLeaderBoard() {
if (mGoogleApiClient.isConnected()) {
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(mGoogleApiClient,
context.getString(R.string.leaderboard_game_high_score)), 1);
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(mGoogleApiClient,
context.getString(R.string.leaderboard_total_high_score)),1);
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(mGoogleApiClient,
context.getString(R.string.leaderboard_grab_a_word_count)), 1);
} else {
// make dialog asking to sign in
}
}
Ok, I guess if I spent more time researching for an answer I'd saved myself time asking the question. I have made the changes to the code below. and it works great.
private static final int RC_UNUSED = 5001;
private void signToLeaderBoard() {
if (mGoogleApiClient.isConnected()) {
startActivityForResult(Games.Leaderboards.getAllLeaderboardsIntent(mGoogleApiClient),
RC_UNUSED);
// startActivityForResult(Games.Leaderboards.getLeaderboardIntent(mGoogleApiClient,
// context.getString(R.string.leaderboard_game_high_score)), 1);
} else {
// make dialog asking to sign in
}
}
I have a couple questions of how to implement google game services in my app.
First of all, after I checked that the user is signed in and the client is connected, I launch
Intent intent = Games.TurnBasedMultiplayer.getSelectOpponentsIntent(mGoogleApiClient, 1, 1, true);
startActivityForResult(intent, RC_SELECT_PLAYERS);
To show the user a dialog where they can select the opponent.
After I get the result from the activity I execute this code
if (request == RC_SELECT_PLAYERS) {
Log.e(LOG_TAG, "Got RC_SELECT_PLAYERS result intent");
if (response != Activity.RESULT_OK) {
// user canceled
return;
}
// Get the invitee list.
final ArrayList<String> invitees = data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);
// Get auto-match criteria.
Bundle autoMatchCriteria;
int minAutoMatchPlayers = data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
int maxAutoMatchPlayers = data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
if (minAutoMatchPlayers > 0) {
autoMatchCriteria = RoomConfig.createAutoMatchCriteria(minAutoMatchPlayers, maxAutoMatchPlayers, 0);
} else {
autoMatchCriteria = null;
}
TurnBasedMatchConfig turnBasedMatchConfig = TurnBasedMatchConfig.builder()
.addInvitedPlayers(invitees)
.setAutoMatchCriteria(autoMatchCriteria)
.build();
// Create and start the match.
Games.TurnBasedMultiplayer
.createMatch(mGoogleApiClient, turnBasedMatchConfig)
.setResultCallback(new MatchInitiatedCallback());
}
To initiate the match. The problem is, the opponent I challenged doesn't get any notification where he is asked to join the game. So when the user gets a notification to join a game, how can I catch this notification and show the user the appropriate content? Which intent result should I look for?
In my MatchInitiatedCallback I check which turn it is now:
public class MatchInitiatedCallback implements ResultCallback<TurnBasedMultiplayer.InitiateMatchResult> {
private static final String LOG_TAG = "MatchInitiatedCallback";
#Override
public void onResult(TurnBasedMultiplayer.InitiateMatchResult result) {
// Check if the status code is not success.
Status status = result.getStatus();
if (!status.isSuccess()) {
Log.e(LOG_TAG, "showError() " + status.toString());
//showError(status.getStatusCode());
return;
}
TurnBasedMatch match = result.getMatch();
// If this player is not the first player in this match, continue.
if (match.getData() != null) {
Log.e(LOG_TAG, "Not my turn");
//showTurnUI(match);
return;
}
// Otherwise, this is the first player. Initialize the game state.
//initGame(match);
Log.e(LOG_TAG, "initGame()");
// Let the player take the first turn
//showTurnUI(match);
Log.e(LOG_TAG, "showTurnGui()");
}
}
But this code gets executed even before my opponent selects to join.
Shouldn't he first be able to accept or decline the game?
Can someone please tell me if I'm doing this properly or if this is supposed to be like this? If not, how should it be implemented correctly?
EDIT: I took all this code from the documentation. It does show some code samples of how to implement things, but not of how the code all comes together and the order in which things should be executed, or how to handle notifications and such.
Yes, it is supposed to work like this. You must take a turn before the opponent actually receives the invitation. In fact, if you invite several opponents, each of them will have to accept the invitation and take a turn before the next opponent is invited, meaning it can take quite a while before you know whether you actually got a match going.
To avoid asking the users to take turns during the invitation phase, your program can take a dummy turn for each user to pass the invitation to the next user. When all users have joined you can start to take real turns.
I'm trying to implement Facebook sharing in my game using Unity 3D + Facebook Unity SDK. But when I tried testing to post to my wall, this error shows up: "We are Sorry, this post is no longer available. It may have been removed." Can anybody help me? Thanks in advance.
BTW, here's my code:
using UnityEngine;
using System.Collections;
public class FacebookController : MonoBehaviour {
public bool isUsingFacebook = true; //Are we using Facebook SDK? This variable is only
//Feed parameters.
private string link = "market://details?id=com.LaserCookie.Queue"; //The link that will show the user the game's google play store address
private string linkName = "Queue"; //The link name
private string linkCaption = "Wow this game is great! 10/10 must play!"; // The caption of the link
private string linkDescription = "I achieved the score of " + PlayerController.instance.score.ToString() + "! Try to beat me if you can!"; //The description of the link
private string picture = "http://www.drycreekequestriancenter.com/testImage.jpeg"; //This is the image / game icon for the link. For now, it's shamelessly got from a random source. Thank you, random citizen...
void Awake()
{
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
//Init FB
private void Init()
{
if (!FB.IsInitialized)
{
FB.Init(OnInitComplete, OnHideUnity);
}
}
//Callback that will be called when the initialization is completed
private void OnInitComplete()
{
Debug.Log("FB.Init completed: Is user logged in? " + FB.IsLoggedIn);
//Check if we are logged in.
//If not, we will log in.
//If we are, post status.
if (!FB.IsLoggedIn)
{
LoginWithPublish();
}
else
{
PostImpl();
}
}
//Callback that will be called when the game is shown / not
private void OnHideUnity(bool isGameShown)
{
Debug.Log("Is game showing? " + isGameShown);
}
//Post to Facebook. This is the only exposed method because we only use this to post to Facebook.
//The initialization and login will be called when needed.
//It will first detect whether or not we have been initialized. And will init if we haven't.
//Then it will check whether or not we have been logged in with publish. And will log in if not.
public void PostToFacebook()
{
//Are we using facebook SDK?
if (isUsingFacebook)
{
if (!FB.IsInitialized) //Check for initialization
{
Init();
}
else if (!FB.IsLoggedIn) //Check for login
{
LoginWithPublish();
}
else //Post if we are already initia;ized and logged in
{
PostImpl();
}
}
}
//The true implementation of the posting
private void PostImpl()
{
FB.Feed("",link, linkName,linkCaption,linkDescription,picture);
}
//Login to Facebook with publish
private void LoginWithPublish()
{
// It is generally good behavior to split asking for read and publish
// permissions rather than ask for them all at once.
//
// In your own game, consider postponing this call until the moment
// you actually need it.
FB.Login("publish_actions", LoginCallback);
}
//Login callback
void LoginCallback(FBResult result)
{
if (result.Error != null)
{
Debug.Log( "Error Response:\n" + result.Error );
//TODO: Retry login if we have error? Or do we display a pop up?
}
else if (!FB.IsLoggedIn)
{
Debug.Log( "Login cancelled by Player" );
//TODO: Do we display a pop up?
}
else
{
Debug.Log( "Login was successful!" );
PostImpl();
}
}
}
You need to add Key Hash for FB application.
Go to My Apps, select you application, open Setting tab, add platform for android, and add you key hash.
check this link out
Setting a Release Key Hash
I've fixed the issue. It turns out it's because I used my still in development google store address as the link. I thought it would be automatically recognized regardless of my app is live or not. Thank you anyway. :)
I want to implement a custom dialog box on onInvitationReceived(Invitation invitation) callback. It will have 2 options - 'accept' and 'reject'.
I successfully implemented the 'reject' action. Simplified code below -
#Override
public void onInvitationReceived(Invitation invitation) {
String invitationId = invitation.getInvitationId();
if (/*code for selecting 'reject' action*/) {
Games.TurnBasedMultiplayer.declineInvitation(mGoogleApiClient, invitationId);
}
}
But how do I implement 'accept' action? Specifically from just Invitation object. I will need TurnBasedMatch object to start the match on invited players end.
The following link from google developers lists only the way to show default view to 'accept' (or 'reject') game.
https://developers.google.com/games/services/android/turnbasedMultiplayer#handling_invitations
I managed to solve this on my own. Below is the simplified code to 'accept' the game invitation and get the match object -
PendingResult<TurnBasedMultiplayer.InitiateMatchResult> pendingResult =
Games.TurnBasedMultiplayer.acceptInvitation(mGoogleApiClient, invitationId);
pendingResult.setResultCallback(new ResultCallback<TurnBasedMultiplayer.InitiateMatchResult>() {
#Override
public void onResult(TurnBasedMultiplayer.InitiateMatchResult result) {
if (result.getStatus().getStatusCode() == GamesStatusCodes.STATUS_OK) {
TurnBasedMatch match = result.getMatch();
// do something with match ...
}
}
});
As in the title I've been able to connect to Google Game Services, exchange data between two devices and everything is running fine, except one thing: disconnection callbacks.
I tried to intercept both onPeersDisconnected and onP2PDisconnected without any success. The onP2PDisconnected method is being called in the device that get disconnected from Internet but not into device that is still online (so there is no way to tell the player that the other one got disconnected).
After the match is started it seems that the second device is never notified of the accidental disconnection. If the user close the game properly the onPeersLeft method is being called thought.
Is a ping between the two devices really necessary to overcome this "bug"? Am I doing something wrong?
Here is the code I use:
void startQuickGame() {
// quick-start a game with 1 randomly selected opponent
final int MIN_OPPONENTS = 1, MAX_OPPONENTS = 1;
Bundle autoMatchCriteria = RoomConfig.createAutoMatchCriteria(MIN_OPPONENTS,
MAX_OPPONENTS, 0);
RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this);
rtmConfigBuilder.setMessageReceivedListener(this);
rtmConfigBuilder.setRoomStatusUpdateListener(this);
rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
mListener.switchToScreen(R.id.screen_wait);
keepScreenOn();
resetGameVars();
getGamesClient().createRoom(rtmConfigBuilder.build());
}
And here the simple listeners:
#Override
public void onPeersDisconnected(Room room, List<String> peers) {
Log.d(TAG, "onPeersDisconnected");
updateRoom(room);
}
void updateRoom(Room room) {
Log.d(TAG, "UpdateRoom: "+room.getParticipants().size());
mParticipants = room.getParticipants();
}
#Override
public void onP2PDisconnected(String participantId) {
Log.d(TAG, "onP2PDisconnected");
}
public int getPartecipantsInRooom(){
if(mRoom != null)
return mRoom.getParticipants().size();
else
return -123456;
}
Note that calling getPartecipantsInRooom() after one of the two devices disconnects always return 2, and updateRoom never get called.
Just to be sure this might not work for you, for my applications I use this to let me know when another Participant has left the Room, and it is called immediately :
#Override
public void onPeerLeft(Room room, final List<String> participantIds) {
this.mRoomCurrent = room;
this.mRoomId = this.mRoomCurrent.getRoomId();
this.mParticipants = this.mRoomCurrent.getParticipants();
int connected = 0;
for (Participant p : room.getParticipants()) {
if(p.getStatus() == Participant.STATUS_JOINED) {
connected += 1;
}
}
final int fconnected = connected;
for (String s : listIgnoreTheseIDs) {
//checkint to see if we care anymore about this ID.. if out of game already.. nope
if(s.equals(participantIds.get(0))){
return;
}
}
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
mGHInterface.onPeerLeft(fconnected, participantIds.size());
}
});
}
No idea why there are two items, but like you, I realized the onPeersDisconnected() isn't that reliable, but onPeerLeft() normally gets back to the other devices in under 1 second.
onPeerDisconnected() handles disconnects. So if somebody is still in the application but the network connection is lost, this is called for him.
onPeerLeft() handles participants who leave a room. This is called when somebody explizit leaves the room in the application or the application is minimized, and the room is left on the androids onStop() or onDestroy() callback.
I'm making two player game. So I use this approach
#Override
public void onPeerLeft(Room room, List<String> peersWhoLeft) {
updateRoom(room);
Toast.makeText(MyLauncherActivity.this, "Other player left the game", Toast.LENGTH_LONG).show();
quitGame();
}