AltimitSystems / mv-android-client

RPG Maker MV unofficial Android client
http://www.hbgames.org/forums/viewtopic.php?f=48&t=79391
Apache License 2.0
115 stars 36 forks source link

Goolge Play Services #4

Closed JTM-rootstorm closed 6 years ago

JTM-rootstorm commented 6 years ago

Google Play Services

Attempt to bring services such as achievements, events, leaderboards, and eventually cloud saving to the mv-android platform.

On the note of cloud saves, as you'll notice anything relating to that aside from a line or two in the build.gradle, I've determinied that implementating and testing that service alone was going to take a bit and was beginning to draw my attention more than plugging any possible holes within the other services' code. So, I've a feature freeze in place until this pull is accepted and merged; I think cloud saves can wait in the light of something like achievements being implemented.

This also implements AbstractExtesion changes as requested in #2

ids.xml

This is a very simple document, yet arguably one of the most important. If the app_id for the game is not placed in this

<string name="app_id">YOUR_APP_ID</string>

then nothing will connect, thus rendering the entire library inoperable. To be honest, this string can exist in literally any other XML be it something like strings.xml or values.xml or whatever, it just needs to exist

app build.grade

Just added a line that needs to be uncommented to activate the library

def EXTENSIONS = [
        [ module: 'libandroidapi', extension: 'systems.altimit.libandroidapi.AndroidAPI' ],
        //[ module: 'libgoogleplay', extension: 'systems.altimit.libgoogleplay.GooglePlay' ],
]

The duplicate changes from 3ecc1f3 can be hopefully ignored, assuming the build.grade in the request will overwrite the one in master

libgoogleplay build.gradle

Currently holds one important def that will be referenced later in this request

    def ALLOW_AUTO_SIGNIN = true // toggle as per 1.1 from https://developers.google.com/games/services/checklist#1_sign-in
                                 // as you CANNOT have auto sign-in if children under 13 are an
                                 // intended audience

should be self-explanatory

GooglePlay

This class handles the initialization of the service handlers as well as handling the connection and its results from the Play Services servers. This class has only 3 JsInterfaces, one method, as well as a gradle flag worth noting.

private void startSilentSignIn()

If the ALLOW_AUTO_SIGNIN def in the build.gradle is set to TRUE then when onStart or onResume is called, the library will attempt to connect to Play Services. It will not do this, however, if the boolean manualSignOut has been set to TRUE as well, which leads to

@JavascriptInterface
public void signOut()

Handles signing out, of course. This method should be called from a button press or otherwise be player-initiated. This will set manualSignOut to TRUE since, as per Google app guidlines, we should not keep attempting to sign the user in if they have manually signed out on the app.

@JavascriptInterface
public void startInteractiveSignIn()

Should the silent sign-in fail for whatever reason (GPlay settings, etc) then the library will attempt an interactive sign-in, displaying the UI for the player to agree to the usual Google Play permissions. This can also be called by the dev through player interaction with a sign in button which is a MUST if the app is to be marketed towards those 13 or younger or if that group is included in the marketing scope. This has been stated next to the ALLOW_AUTO_SIGNIN def in the build.gradle. After connecting via interactive sign-in again, the manualSignOut flag is set to FALSE.

@JavascriptInterface
public boolean isSignedIn()

Simply return whether or not the player has signed in to Google Play. This is can be used to determine whether or not to present the player with sign in/out buttons or Play Services-specific options in menus.

AchievementsHandler

This handler serves as the dev's connection between their game and the achievements part of Play Services. It can also handle caching achievements for handling unlocks off-line if the player has explicitly signed out of Google Play; it wont change much if the user is still signed in as then it'll just deal with the local GPlay stuff.

For future reference, a shell class has been implemented to make handling achievements both natively and on the JavaScript side easier. The class is as follows

    private class AchievementShell {
        String id;
        String name;
        String desc;
        Uri    revealedImageUri;
        Uri    unlockedImageUri;
        int    state;
        int    type;
        int    currentSteps = 0;
        int    stepsToUnlock = 0;

        AchievementShell(Achievement achievement) {
            // init stuff
        }

        boolean isUnlocked() {
            return (type == Achievement.STATE_UNLOCKED);
        }

        boolean isRevealed() {
            return (type == Achievement.STATE_REVEALED);
        }

        boolean isIncremental() {
            return (type == Achievement.TYPE_INCREMENTAL);
        }
    }

Which follows into the first method of note

@JavascriptInterface
public String getAllAchievementDataAsJSON()

The library caches all of the app's achievement data into a map for local use, as has been stated. The purpose of this method is to provide that data to plugin authors/devs to possibly implement into native UIs should they want to. The method takes all the values of the map as an array and, via GSON reflection, returns a JSON formatted String for parsing and use on the JavaScript end. This should really only ever be called once, as the other methods provide a way of updating specific achievements. This method returns null if the cache is empty

@JavascriptInterface
public String incrementAchievementStep(String achievementId, int amountToIncrement)

This method, if given the achievementId of an incremental achievement (and the method checks that it is), will attempt to increment its value by the given amount both on the GPlay side and locally. The returned String is a JSON-formatted string representing the AchievementShell object that represents the achievement, but can be null if the achievement is not found or the cache is empty

@JavascriptInterface
public String unlockAchievement(String achievementId)

Simply put, unlocks the achievement with the given ID. This should NOT be used with incremental achievements as this will unlock them without regard to the steps needed to be unlocked. The returned String is a JSON-formatted string representing the AchievementShell object that represents the achievement, but can be null if the achievement is not found or the cache is empty

@JavascriptInterface
public void showAchievementView()

Should the end-user not have a custom UI, the default GPlay UI can be called through this method.

EventHandler

As with AchievementHandler, this handler is specifically for Events/Quests. There are only 2 methods exposed to the end-user

@JavascriptInterface
public String incrementEvent(String eventId, long amountToIncrement)

@JavascriptInterface
public String getAllEventDataAsJSON()

and they function similarly to their achievement-related counterparts right down to returning JSON-formatted representations of the events (or null). The shell class for events is as follows

    private class EventShell {
        String id;
        String name;
        String desc;
        String formatedVal;
        Uri imageUri;
        long val;

        EventShell(Event event) {
            // init stuff
        }
    }

NOTE: there is no default Events view like with achievements and leaderboards. It will have to be implemented on its own with the data given by one of the two methods

Leaderboards

This is the simplest of the handlers, having only 2 methods, both of which are exposed to the end-user.

@JavascriptInterface
public void showLeaderboardView(String boardId)

Will bring up the a view for the leaderboard with the given id and

@JavascriptInterface
public void addScoreToLeaderboard(String boardId, int score)

will attempt to push the score to the board with the given id. It currently handles no more than that and does not sport a local cache like achievements and events. Similar functionality can be limited, but that depends on the demand for such

End

If there are any further questions, comments, or concerns I will try my best to answer them quickly. A lot of methods were left out in favor of presenting the ones that are exposed to the end-user to keep things rather short and to not clog things up.

Cloud Saves will be coming once I've a plan for implementation in mind as the Google docs don't quite go over everything needed in handling Snapshots, but figuring out how to pass that data to the MV engine is another problem in itself

felixjones commented 6 years ago

Had a quick look at what you've got, and I'm liking what I'm seeing. When I have time I'll do a pull and check it out in-depth. What I'll do is write my own Plugin and see what the experience is like and see how the performance is (testing that JavascriptInterface overhead).

JTM-rootstorm commented 6 years ago

Alright, cool.

Fun thing I just found out: JavascriptInterfaces don't work like I thought.. I'm able to call interfaces from the GooglePlay class that belong to the AchievementHandler.. like

__google_play_main.showAchievementView()

works just fine, yet '__google_play_achievements' (the handler's INTERFACE_NAME) is what owns those.. Of course, as soon as I seriously start to test that, I suddenly can't reproduce it but it's something to look for while whipping up a test plugin, I guess.

Your relevant interfaces are

__google_play_main
__google_play_achievements
__google_play_events
__google_play_leaderboards

Also found out a new result code that I hadn't gotten before..

JTM-rootstorm commented 6 years ago

Google's gone and screwed everything again with 12.0.0 so give me a bit for a fix

EDIT: disregard, it's a lint error as per https://developers.google.com/android/guides/releases

felixjones commented 6 years ago

I'm happy with this as a preliminary implementation, but the API you've created could do with some work. I think there's a good opportunity to map the Google Java API to Javascript quite nicely for developers to use.

JTM-rootstorm commented 6 years ago

Hrm; I know that, first and foremost, I should definitely be able to move the achievment/event caches into JS and work with it there to reduce the amount of calls to the Java code and some of that reflection overhead going back and forth between the code..

Second thing I believe I've got to do would be to take what the main app does for extensions (minus throwing things into their own library, hopefully) and use it for the Play APIs so it can be a bit more modular

Beyond that, having poked at it, have you any suggestions on where to look?

jtarim commented 5 years ago

I managed to get the Google Play Games library activated and even got my game signed in, but I'm not able to call up the achievement window or unlock any achievements. Im not sure if im properly calling the right code from in-game, need help!(Been going back and forth for months on Google's play services setup, on their site trying to figure it out with no luck)

JTM-rootstorm commented 5 years ago

I managed to get the Google Play Games library activated and even got my game signed in, but I'm not able to call up the achievement window or unlock any achievements. Im not sure if im properly calling the right code from in-game, need help!(Been going back and forth for months on Google's play services setup, on their site trying to figure it out with no luck)

If the game is signed in, then calling __google_play_achievements.showAchievementView() from any script call in RPG Maker MV should be pulling the window up. Also give __google_play_main.showAchievementView() a try since it looks like that apparently worked at some point

Similarly, unlocking would be __google_play_achievements.unlockAchievement()

try copy/pasting some of those in a small test game and see if it plays nice. If none of that works, then I'll have to roll my sleeves up and dive in again to see what's up; little odd you get the whole Google Play Games sign-in dealio yet nothing else works.

Side note, but you should also be able to use the debugger in Android Studio to see if the required objects are being initialized (just drop a breakpoint around where it's supposed to happen and just step through the code)

jtarim commented 5 years ago

I used the first code, google_play_achievements.showAchievementView() and that worked(Thanks). Im still unable to unlock my test achievements with, google_play_achievements.unlockAchievement()

I tried both of these and didn't work, google_play_achievements.unlockAchievement(CgkIveCby5QCEAIQIw) google_play_achievements.unlockAchievement()

Idk if im inserting the id wrong.

JTM-rootstorm commented 5 years ago

I think you have to encase the ID in quotes, so calling

__google_play_achievements.unlockAchievement("CgkIveCby5QCEAIQIw")

might work better as, without the quotes, the JS engine might otherwise interpret CgkIveCby5QCEAIQIw as a variable/object (I know the Java side absolutely will). The “Achievements unlocked” pop-up should come up automatically when the unlockAchievement method is successfully called.

Don't know about the 'Welcome Back' thing; might be the silentSignIn doing stuff, but also could do with the fact that the signOut method isn't called when the app closes (dunno why, but I've witnessed stranger things trying to get this stuff working)

Inside RPGMaker MV, sign-in/out can be handled however; if via button then you would need to add, say, a menu option on the ESC menu/inventory screen/whatever that just calls

__google_play_main.signOut() or __google_play_main.startInteractiveSignIn()

when pressed. You should change which option (sign-in/out) gets shown in the menu with isSignedIn()

I guess I'll take a peek at what's currently deprecated and see what things it'll break and attempt to fix 'em

JTM-rootstorm commented 5 years ago

And if you can point out the deprecations, that'd help a bit; my Android Studio isn't picking anything up other than some 'unused method's/'this can be null'/'this can be private/package-private'/etc (which should be ignored anyways)

jtarim commented 5 years ago

Nevermind on the deprecations, and thanks for the help, I copy/pasted all the codes you provided and everything is working accordingly (OMG thank you) all the codes i was using before were way off.(can u perhaps post all the Gplay script commands to avoid confusion for the newbs like me)

jtarim commented 5 years ago

Will apk expansion be added eventually?

JTM-rootstorm commented 5 years ago

It was something I had planned a while ago before schoolwork/life swamped me.. Things permitting, I can probably have something working pushed to https://github.com/tehguy/mv-android-client/tree/apk-expansion by next week

With some of the new changes Google Play has implemented since then, though, it's only a rough estimate since I'm assuming I can use some old code from an experiment I had done with expansions and MV a couple years back

jtarim commented 5 years ago

That would be amazing whenever you do implement it, keeping below Google apk upload limit has been a pain in the A%#

jtarim commented 5 years ago

I've been playing around with my game and still haven't gotten any "Achievement unlocked" to popup, it is showing that I've unlocked it in the play games app and when i call up the achievement list.

And any update on apk expansion?