I can create games with invites, I can invite and accept invitations, the thing mostly works fine. The problem appears when I make a game for 3 or more people:
user A creates game with 3 people or 2 people at least
user A chooses to invite someone and then adds another person to auto-pick for the room
User A invites user B
User B accepts
They both wait until the "Start now" button appears
User B presses the start now button and OnRoomConnected() is called 3 times for some reason and the game doesn't start (the room was also never left as far as I can tell, because this user can't receive invitations anymore)
Nothing changes from the perspective of user A, he still waits for the game to start or search for another auto pick opponent
I made sure that the problem is not from my code. I created a separate simple project, that I used only for testing purposes and it does exactly the same thing. So I was starting to think maybe it's not a problem from my side and I didn't see similar problems on the internet. So I decided to ask here. What should I do? What could be the problem?
That's basically it. Even if I restrict the number of players to 3 or 4 (min and max number of players are equal, 3 or 4), it still lets me start the game prematurely and I have the same problem with OnRoomConnected() being called multiple times and the game doesn't start.
Thanks in advance. If you have a link or something that would help me solve this problem, it would be greatly appreciated.
Here's the basic code I used for logging in the game and creating a room.
public class GPGM : MonoBehaviour, RealTimeMultiplayerListener{
public static GPGM instance;
public static int target;
private void Awake()
{
target = 60;
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = target;
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
}
else if (instance != this)
{
Destroy(gameObject);
}
}
// Use this for initialization
void Start () {
Login();
}
// Update is called once per frame
void Update () {
}
public void Login()
{
StartCoroutine(checkInternetConnection((isConnected) =>
{
LoginGPG();
}));
}
IEnumerator checkInternetConnection(Action<bool> action)
{
WWW www;
www = new WWW("http://google.com");
yield return www;
if (!String.IsNullOrEmpty(www.error))
{
Debug.Log("DebugM | no internet connection");
action(false);
}
else
{
Debug.Log("DebugM | There IS connection");
action(true);
}
}
public void LoginGPG()
{
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder().WithInvitationDelegate(OnInvitationReceived).Build();
PlayGamesPlatform.InitializeInstance(config);
PlayGamesPlatform.DebugLogEnabled = true;
PlayGamesPlatform.Activate();
Debug.Log("DebugM | LoginGPG");
Auth();
}
public void Auth()
{
Debug.Log("DebugM | Auth");
try
{
//doesn't work sometimes for some reason. It gives null data if success is false
//reason for false success is unknown
Social.localUser.Authenticate((bool succes) =>
{
if (succes)
{
Debug.Log("DebugM | Logged in");
}else
{
Debug.Log("DebugM | authentication failed");
}
});
}
catch (Exception e)
{
Debug.Log("DebugM | Auth() has failed with error: " + e.Message);
}
}
public void OnInvitationReceived(Invitation invitation, bool shouldAutoAccept)
{
StartCoroutine(InvitationCo(invitation, shouldAutoAccept));
}
Invitation mIncomingInvitation;
IEnumerator InvitationCo(Invitation invitation, bool shouldAutoAccept)
{
yield return new WaitUntil(() => SceneManager.GetActiveScene().name == "Lobby");
Debug.Log("DebugM | Invitation has been received!!!");
//StartCoroutine(LM.LoadingAnim());
if (shouldAutoAccept)
{
Debug.Log("DebugM | Should auto accept: TRUE");
PlayGamesPlatform.Instance.RealTime.AcceptInvitation(invitation.InvitationId, instance);
}
else
{
// The user has not yet indicated that they want to accept this invitation.
// We should *not* automatically accept it. Rather we store it and
// display an in-game popup:
Debug.Log("DebugM | Should auto accept: FALSE");
Lobby LM = FindObjectOfType<Lobby>();
LM.invPanel.SetActive(true);
mIncomingInvitation = invitation;
}
}
public void AcceptGoogleInv(GameObject panel)
{
if (mIncomingInvitation != null)
{
// show the popup
//string who = (mIncomingInvitation.Inviter != null &&
// mIncomingInvitation.Inviter.DisplayName != null) ?
// mIncomingInvitation.Inviter.DisplayName : "Someone";
Debug.Log("DebugM | Invitation has been accepted");
PlayGamesPlatform.Instance.RealTime.AcceptInvitation(mIncomingInvitation.InvitationId, instance);
panel.SetActive(false);
}
}
public void CreateQuickRoom()
{
PlayGamesPlatform.Instance.RealTime.CreateWithInvitationScreen(1, 3, 1, instance );
}
public void OnRoomSetupProgress(float percent)
{
Debug.Log("OnRoomSetupProgress()");
PlayGamesPlatform.Instance.RealTime.ShowWaitingRoomUI();
}
public void OnRoomConnected(bool success)
{
SceneManager.LoadScene("Game");
Debug.Log("DebugM | Room conected");
}
public void OnLeftRoom()
{
throw new NotImplementedException();
}
public void OnParticipantLeft(Participant participant)
{
throw new NotImplementedException();
}
public void OnPeersConnected(string[] participantIds)
{
throw new NotImplementedException();
}
public void OnPeersDisconnected(string[] participantIds)
{
throw new NotImplementedException();
}
public void OnRealTimeMessageReceived(bool isReliable, string senderId, byte[] data)
{
throw new NotImplementedException();
}}
Edit (pictures):
This is when I wait for the last auto pick slot to fill (it works like this only when people were invited to the game)
The game goes to lobby for the person who pressed start and the others still wait for the last autopick even if 1 player practically left the room
You can do it like:
public void OnRoomConnected (bool success)
{
if (success)
{
//Start the game here
SceneManager.LoadScene("Game");
Debug.Log("DebugM | Room conected");
}
else
{
//Do somthing else.
}
}
or the best way to do it by checking connected participans count.
public void OnPeersConnected (string[] participantIds)
{
List<Participant> playerscount = PlayGamesPlatform.Instance.RealTime.GetConnectedParticipants();
if (playerscount != null && playerscount.Count > 1)//this condition should be decided by you.
{
//Start the game here
SceneManager.LoadScene("Game");
}
}
Related
I'm developing an Android App using Fernando Ceja's clean architecture. One of my Interactors or Use Cases is in charge of getting the User's feed data. In order to get the data, first I have to retrieve the User's Teams from a database table and then I have to get the Feed list from the server-side.
This is how I get the Teams from the database layer:
mTeamCache.getAllTeams().subscribe(new DefaultSubscriber<List<SimpleTeam>>() {
#Override
public void onNext(List<SimpleTeam> simpleTeams) {
super.onNext(simpleTeams);
mTeams = simpleTeams;
}
});
TeamCache is basically just another Interactor that takes care of getting all the teams that I have in the database.
Here's how I get the Feed data from the server-side:
mFeedRepository.getFeed(0, 50).subscribe(new ServerSubscriber<List<ApiFeedResponse>>() {
#Override
protected void onServerSideError(Throwable errorResponse) {
callback.onFeedFetchFailed(...);
}
#Override
protected void onSuccess(List<ApiFeedResponse> responseBody) {
//Do stuff with mTeams
callback.onFeedFetched(...);
}
});
My GetFeedInteractor class has a method called execute, where I pass through the Callback that I'm later using in the UI to handle the response. The issue with all this is that currently I'm chaining the responses like this:
#Override
public void execute(final Callback callback, String userSipId) {
mTeamCache.getAllTeams().subscribe(new DefaultSubscriber<List<SimpleTeam>>() {
#Override
public void onNext(List<SimpleTeam> simpleTeams) {
super.onNext(simpleTeams);
mTeams = simpleTeams;
getFeedFromRepository(callback);
}
});
}
public void getFeedFromRepository(final Callback callback) {
mFeedRepository.getFeedRx(0, 50).subscribe(new ServerSubscriber<List<ApiFeedResponse>>() {
#Override
protected void onServerSideError(Throwable errorResponse) {
callback.onFeedFetchFailed("failed");
}
#Override
protected void onSuccess(List<ApiFeedResponse> responseBody) {
//Do stuff with mTeams
List<BaseFeedItem> responseList = new ArrayList();
for (ApiFeedResponse apiFeedResponse : responseBody) {
responseList.add(FeedDataMapper.transform(apiFeedResponse));
}
callback.onFeedFetched(responseList);
}
});
}
As you can see, once that I get the Team collection from the Cache Interactor I call the method that gets the feed from the very same Subscriber. I don't like this. I want to be able to do something nicer, like using Observable.concat(getTeamsFromCache(), getFeedFromRepository()); chain a call to another rx.Observable inside a Subscriber is not something nice to do. I guess that my question is, how can I chain two rx.Observables that are using different Subscribers?
Update:
ServerSubscriber is a subscriber that I implemted to subscribe to Retrofit services. It simply checks the error codes and some stuff. Here is:
https://gist.github.com/4gus71n/65dc94de4ca01fb221a079b68c0570b5
Default subscriber is an empty default subscriber. Here is:
https://gist.github.com/4gus71n/df501928fc5d24c2c6ed7740a6520330
TeamCache#getAllTeams() returns rx.Observable>
FeedRepository#getFeed(int page, int offset) returns rx.Observable>
Update 2:
This is how the Interactor to get the User's feed looks like now:
#Override
public void execute(final Callback callback, int offset, int pageSize) {
User user = mGetLoggedUser.get();
String userSipid = mUserSipid.get();
mFeedRepository.getFeed(offset, pageSize) //Get items from the server-side
.onErrorResumeNext(mFeedCache.getFeed(userSipid)) //If something goes wrong take it from cache
.mergeWith(mPendingPostCache.getAllPendingPostsAsFeedItems(user)) //Merge the response with the pending posts
.subscribe(new DefaultSubscriber<List<BaseFeedItem>>() {
#Override
public void onNext(List<BaseFeedItem> baseFeedItems) {
callback.onFeedFetched(baseFeedItems);
}
#Override
public void onError(Throwable e) {
if (e instanceof ServerSideException) {
//Handle the http error
} else if (e instanceof DBException) {
//Handle the database cache error
} else {
//Handle generic error
}
}
});
}
I think you're missing the point of RxJava and reactive approach, you should not have different subscribers with OO hierarchy, and callbacks.
You should construct separated Observables that should emit the specific data it's handle, without the Subscriber, then you can chain you're Observable as needed, and at the end, you have the subscriber that react to the final result expected from the chained Observable stream.
something like this (using lambdas to have more thin code):
TeamCache mTeamCache = new TeamCache();
FeedRepository mFeedRepository = new FeedRepository();
Observable.zip(teamsObservable, feedObservable, Pair::new)
.subscribe(resultPair -> {
//Do stuff with mTeams
List<BaseFeedItem> responseList = new ArrayList();
for (ApiFeedResponse apiFeedResponse : resultPair.second) {
responseList.add(FeedDataMapper.transform(apiFeedResponse));
}
}, throwable -> {
//handle errors
}
);
I've use zip and not concat as it's seems you have 2 independent calls here that you want to wait for both to finish ('zip' them together) and then act upon, but ofcourse, as you have separated Observables stream, you can chain them together differently according to your needs.
as for your ServerSubscriber with all the response validation logic, it should be rxify too, so you can compose it along your server Observable stream.
something like this (some logic emitted to simplify, and as I'm not familiar with it...)
Observable<List<SimpleTeam>> teamsObservable = mTeamCache.getAllTeams();
Observable<List<ApiFeedResponse>> feedObservable = mFeedRepository.getFeed(0, 50)
.flatMap(apiFeedsResponse -> {
if (apiFeedsResponse.code() != 200) {
if (apiFeedsResponse.code() == 304) {
List<ApiFeedResponse> body = apiFeedsResponse.body();
return Observable.just(body);
//onNotModified(o.body());
} else {
return Observable.error(new ServerSideErrorException(apiFeedsResponse));
}
} else {
//onServerSideResponse(o.body());
return Observable.just(apiFeedsResponse.body());
}
});
I am new to Rxjava. It is so complex......
I need to handle a complex scenario, I want to know how to make it using Rxjava.
It's a logic about app start:
app start and splash page open
make http reqesut check_update to check if app need update.
if app has new version and must update, pop up a notice dialog with only one update button; if app has new version, but not must update, then pop up a dialog with 2 button(update and ignore); if no new version, go next.
no need to update, so we need to jump to home page or login page, according to current auth_token state.
read auth_token from sharedPreference. then call http request check_auth to check if it is valid or not. if valid, jump to home page; if not, jump to login page.
In this process, 2 http requests and lots of condition checks are involved. http request error also need to be handled, Can this process be written in one Rxjava?
I think I can do it in this way, but I am not sure if it is right.
mRetrofitService.checkUpdate(new CheckUpdateRequest("android", "0.0.1")) // check update request
.compose(RxUtil.applyScheduler()) // scheduler
.flatMap(RxUtil.applyFunction()) // handle the response.
.flatMap((Function<CheckUpdateResponse, ObservableSource<?>>) checkUpdateResponse -> {
int updateMode;
if (checkUpdateResponse.isHas_new_version()) {
if (checkUpdateResponse.isForce_update()) {
updateMode = FORCE_UPDATE; // 1
} else {
updateMode = CHOOSE_UPDATE; // 2
}
} else {
updateMode = NO_UPDATE; // 0
}
return Observable.just(updateMode);
})
.flatMap(o -> {
if (Integer.parseInt(o.toString()) == NO_UPDATE) {
return mRetrofitService.checkAuth();
}
return null; //what should I return for pop up dialog?
})
.flatMap(RxUtil.applyFunction())
.subscribe(new Observer<CheckAuthResponse>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CheckAuthResponse checkAuthResponse) {
// valid
gotoHome();
}
#Override
public void onError(Throwable e) {
// should I handle pop here?
// should I handle auth invalid here?
// should I handle checkUpdate request error here?
// should I handle checkAuth here?
// how to distinguish these errors?
}
#Override
public void onComplete() {
}
});
Am I right? And would you please answer my questions(comments in the code)?
According to the documentation onError "Notifies the Observer that the Observable has experienced an error condition.". So this callback is not relevant to handle request errors.
I suggest you to use the Response class of retrofit to handle your differents cases.
For example you define your service like that :
#method(url)
Call<CheckAuthResponse> checkUpdate(...);
and in your rx workflow :
mRetrofitService.checkUpdate(new CheckUpdateRequest("android", "0.0.1")) // check update request
.compose(RxUtil.applyScheduler()) // scheduler
.flatMap(RxUtil.applyFunction()) // handle the response.
.map((Function<Response<CheckUpdateResponse>, CheckUpdateResponse>) retrofitResponse -> {
if ( retrofitResponce.code() == authInvalidCode ) {
//do something
} else if (...) {
}
return retrofitResponse.body()
})
.flatMap((Function<CheckUpdateResponse, ObservableSource<?>>) checkUpdateResponse -> {
int updateMode;
if (checkUpdateResponse.isHas_new_version()) {
if (checkUpdateResponse.isForce_update()) {
updateMode = FORCE_UPDATE; // 1
} else {
updateMode = CHOOSE_UPDATE; // 2
}
} else {
updateMode = NO_UPDATE; // 0
}
return Observable.just(updateMode);
})
.flatMap(o -> {
if (Integer.parseInt(o.toString()) == NO_UPDATE) {
return mRetrofitService.checkAuth();
}
return null; //what should I return for pop up dialog?
})
.flatMap(RxUtil.applyFunction())
.subscribe(new Observer<CheckAuthResponse>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CheckAuthResponse checkAuthResponse) {
// valid
gotoHome();
}
#Override
public void onError(Throwable e) {
// should I handle pop here?
// should I handle auth invalid here?
// should I handle checkUpdate request error here?
// should I handle checkAuth here?
// how to distinguish these errors?
}
#Override
public void onComplete() {
}
});
Hope this helps;
Sorry for my english.
I created a "play button" which starts the automatch UI and it begins to search for players.Once when the UI shows up, when I press back in the automatch UI back button ,it returns back to "play button"....But when I press it again it does nothing.
It works when I press quit in the automatch UI and then press play again
What should I be doing in code (I'm using Google play games Unity plugin) when someone presses the back button in the ui ?
I'm working with Google play games services unity plugin
I am not sure of what your code looks like, but this is my minimal example of creating the real-time game. I think your problem was whenever you "go back" to the play button, you need to call LeaveRoom() to reset the multi-player room state.
This is based on the SmokeTest sample in the GPGS Unity plug in project (https://github.com/playgameservices/play-games-plugin-for-unity).
All the code (including this sample are covered under Apache 2 license.
using UnityEngine;
using GooglePlayGames.BasicApi.Multiplayer;
using GooglePlayGames;
using GooglePlayGames.BasicApi;
class SampleRTMP : MonoBehaviour, RealTimeMultiplayerListener
{
bool canStartPlaying = true;
bool playingGame = false;
void Start()
{
PlayGamesClientConfiguration config =
new PlayGamesClientConfiguration.Builder()
// registers a callback to handle game invitations
// received while the game is not running.
.WithInvitationDelegate(OnInvitation)
.Build();
PlayGamesPlatform.InitializeInstance(config);
// recommended for debugging:
PlayGamesPlatform.DebugLogEnabled = true;
// Activate the Google Play Games platform
PlayGamesPlatform.Activate();
// Auto login
Social.localUser.Authenticate((bool success) => {
// handle success or failure
});
}
void OnGUI()
{
if (playingGame) {
// don't show the buttons during game play.
return;
}
if (canStartPlaying) {
if (GUI.Button(new Rect(10, Screen.height / 4f, Screen.height / 8f, Screen.width / 3f), "Play")) {
canStartPlaying = false;
ulong bitmask = 0L; // no special bitmask for players
uint minOpponents = 1; // so the smallest is a 2 player game
uint maxOpponents = 1; // so the largest is a 2 player game
PlayGamesPlatform.Instance.RealTime.CreateQuickGame(minOpponents,
maxOpponents, 0, bitmask, this);
}
} else {
if (GUI.Button(new Rect(10, Screen.height / 4f, Screen.height / 12f, 100), "Leave Room")) {
PlayGamesPlatform.Instance.RealTime.LeaveRoom();
}
if (GUI.Button(new Rect(350, Screen.height / 4f, Screen.height / 12f, 100), "Show Waiting Room")) {
PlayGamesPlatform.Instance.RealTime.ShowWaitingRoomUI();
}
}
}
public void OnInvitation(Invitation invitation, bool shouldAutoAccept)
{
// handle the invitation
if (shouldAutoAccept) {
PlayGamesPlatform.Instance.RealTime.AcceptInvitation(invitation.InvitationId, this);
}
else {
// do some other stuff.
}
}
// Callbacks for Room setup
public void OnRoomSetupProgress(float percent)
{
// called once initially, then each time a player joins or leaves.
// or if the waiting room activity returns.
Debug.Log("Setting up room (" + ((int)percent) + "%)");
}
public void OnRoomConnected(bool success)
{
Debug.Log("Connected to room: " + success);
if (success) {
// Start playing the game!
playingGame = success;
} else {
// Handle error here, then leave the room.
PlayGamesPlatform.Instance.RealTime.LeaveRoom();
}
}
public void OnLeftRoom()
{
Debug.Log("Left the room");
canStartPlaying = true;
}
public void OnParticipantLeft(Participant participant)
{
}
public void OnPeersConnected(string[] participantIds)
{
}
public void OnPeersDisconnected(string[] participantIds)
{
//Do stuff
}
public void OnRealTimeMessageReceived(bool isReliable, string senderId, byte[] data)
{
//Do stuff
}
}
I have some incremental achievements on my game and i want to check if one of them is already unlocked. How can i do that?
Originaly i used this method:
getGamesClient().incrementAchievement(achievement, increment);
Then i tried to use this:
mHelper.getGamesClient().incrementAchievementImmediate(new OnAchievementUpdatedListener()
{
#Override
public void onAchievementUpdated(int statusCode, String achievementId)
{
// TODO: Check if the achievement got unlocked
}
}, achievement, increment);
Is this the right way to do what i want? Is there a better way?
I only got two statusCode values on my tests.
Value: 5 = STATUS_NETWORK_ERROR_OPERATION_DEFERRED
Value: 0 = STATUS_OK
Can some one help me with this?
Thanks
Declare this inner class:
class AchievementClass implements ResultCallback<Achievements.LoadAchievementsResult> {
#Override
public void onResult(LoadAchievementsResult arg0) {
Achievement ach;
AchievementBuffer aBuffer = arg0.getAchievements();
Iterator<Achievement> aIterator = aBuffer.iterator();
while (aIterator.hasNext()) {
ach = aIterator.next();
if ("The Achievement Id you are checking".equals(ach.getAchievementId())) {
if (ach.getState() == Achievement.STATE_UNLOCKED) {
// it is unlocked
} else {
//it is not unlocked
}
aBuffer.close();
break;
}
}
}
}
And use it like so:
Games.Achievements.load(ApiClient, false).setResultCallback(new AchievementClass());
This will get achievement data for the currently signed in player, go through all the achievements, and when it reaches the achievement you're concerned with, it will check if it is unlocked.
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();
}