Search Unity

[Utility library - iOS] GKNativeExtensions - The missing iOS cloud save feature!

Discussion in 'Assets and Asset Store' started by Dark-1-Games, Mar 13, 2019.

  1. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    We are excited to announce the release of GKNativeExtensions, a small native utility library made to support iOS Cloud save by using the apple-recommended GKSavedGame API.

    The utility library currently supports the following operations:
    • Fetching existing save games
    • Saving a game by name
    • Loading a game by name
    • Deleting a game by name
    • Resolve multiple conflicting saves
    • Registering a callback to be called when a conflict is detected
    • Registering a callback to be called when a save occurs on a different device (not properly tested)
    The project can be found on Github:
    https://github.com/dark-1-games/GKNativeExtensions

    It is licensed under the Mozilla Public License 2.0.

    This should technically work on OSX, but our focus has been iOS. Please let us know if you get it working on OSX.

    The library was made to be integrated in our upcoming cross-platform integration system, DarkCPS, for which we're currently looking for testers
    https://forum.unity.com/threads/darkcps-game-services-simplified-looking-for-testers.697136/

    Comments and feedback are welcome!
     

    Attached Files:

    Last edited: Oct 17, 2019
  2. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Conflict resolution and native callbacks (conflicts and new save) have been added, and version 0.2 is now available for download on github.
    Do note that the conflicting save games callback is only called if two devices save with the same name while disconnected, and only after restarting the game.
    It is possible to manually fetch games and detect the conflict by looking for multiple games with the same name but different deviceName.
     
    OlivierEsolu likes this.
  3. CoCoNutti

    CoCoNutti

    Joined:
    Nov 30, 2009
    Posts:
    513
    Thank you will check this out
     
  4. Zimbres

    Zimbres

    Joined:
    Nov 17, 2014
    Posts:
    180
    Hi there!

    It's great to count with some help on handling data with iCloud. I... i'm not much into c coding or osx, and I'm not sure if I got the whole thing.
    For example, importing the v0.2 unity package gives some errors, like this one:
    Assets/GKNativeExtensions/GKNativeExtensions.cs(83,13): error CS0019: Operator `+=' cannot be applied to operands of type `System.IntPtr' and `int'

    I'm not exactly used to... never touched a IntPtr to be honest. Any clue on why I'm having this problem? Something with Unity3d version? I'm using 2018.2.21f1.
     
  5. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hey!
    Could you try replacing the contents of GKNativeExtensions.cs with this?
    https://raw.githubusercontent.com/d...ns/master/UnityBindings/GKNativeExtensions.cs

    The release version worked on .NET 4.5+. Since then, a small change by JBurkeKF added support for older versions, but we didn't make a new release.
     
    Zimbres likes this.
  6. Zimbres

    Zimbres

    Joined:
    Nov 17, 2014
    Posts:
    180
    Wow, thanks for the quick reply! I've replaced the code, all fine now! Will be testing the solution on various situations.
     
  7. Headup_Dev

    Headup_Dev

    Joined:
    Sep 18, 2013
    Posts:
    23
    Hi Dark-1-Games,

    have you tried this on tvos too?

    thx for the puplic repo btw :)
     
  8. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hey there,
    Tvos has never been tested, since we don't have a device to test on but i assume the iOS version would work either OOB or with a couple of small tweaks.
    Please let me know if you manage to get it working, PRs are welcome.
     
    Zimbres and Headup_Dev like this.
  9. Zimbres

    Zimbres

    Joined:
    Nov 17, 2014
    Posts:
    180
    Dark-1, maybe it's a little too much to ask but... would you have a snippet on how to implement your solution? It's not only lazyness of mine, but it would help a lot on the ios building/xcode/iDevice upload routine.
     
  10. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Sure, we really didn't do any documenting on it, so here goes:

    • GKInit(Action<SavedGameDataGameCenter[]> conflictCallback, Action<SavedGameDataGameCenter> modifiedCallback)
      This will initialize the library and give you conflicts, if any exist, and call back the modifiedCallback once a saved game has been modified remotely.

    • GKFetchSavedGames(Action<SavedGameDataGameCenter[]> callback)
      Gets the available save games

    • GKResolveConflicts(SavedGameDataGameCenter[] sgData, byte[] saveArray, Action<bool> callback)
      Resolves the conflicts with the given new data. sgData is a list of conflicted save games, saveArray is the new data to save in bytes, callback executes once the command is finished.

    • GKDeleteGame(string savedGame, Action<bool> callback)
      Deletes a saved game by it's name.

    • GKSaveGame(byte[] data, string savedGameName, Action<SavedGameDataGameCenter> callback)
      Saves given data with a saved game name. The callback gets executed once done, with the resulting game data, or null if failed.

    • GKLoadGame(SavedGameDataGameCenter savedGame, Action<byte[]> savedDataCallback)
      Loads a saved game, and gives you back the loaded data in bytes.

    Note: If conflicts occur, GameKit doesn't ALWAYS tell you through the GKInit. It is necessary to find conflicts yourself by comparing names of SavedGameDataGameCenter returned by GKFetchSavedGames.

    Hope you find this short doc useful, i'll try to add it somewhere in the repo.
     
    Last edited: Jul 4, 2019
  11. Zimbres

    Zimbres

    Joined:
    Nov 17, 2014
    Posts:
    180
    Absolutely! Hope to report something useful as the time goes.
     
  12. Zimbres

    Zimbres

    Joined:
    Nov 17, 2014
    Posts:
    180
    Nothing really useful... I feel like I should update myself more on the field of delegates. I'm trying to initialize the plugin, but I'm not getting a sign of life. Is the following code correct?

    Code (CSharp):
    1. private void InitializeGKNE() {
    2.         Debug.Log("testando as coisa,");
    3.         GKNativeExtensions.GKInit(ConflictHappened, InitSuccessfull);
    4.     }
    5.     void ConflictHappened(SavedGameDataGameCenter[] ps) {
    6.         Debug.Log("params" + ps);
    7.         base.Initialize();
    8.     }
    9.     void InitSuccessfull(SavedGameDataGameCenter savedGameMeta) {
    10.         Debug.Log("initSuccessfull" + savedGameMeta);
    11. GKNativeExtensions.GKFetchSavedGames(FetchSavedGames);
     
  13. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hey, sorry, it seems i got something mixed up, you don't need to wait for GKInit callbacks to begin working with the library.

    Code (CSharp):
    1.  
    2. Social.localUser.Authenticate((bool success) =>
    3. {
    4.     GKNativeExtensions.GKInit((conflicts) =>
    5.     {
    6.         Debug.Log("Conflicts received by C#");
    7.  
    8.     }, (modified) =>
    9.     {
    10.         Debug.Log("Modified game received by C#");
    11.     });
    12.  
    13.     if (!success)
    14.     {
    15.         callback(null);
    16.         return;
    17.     }
    18. });
    19.  
    20. // ... After GKInit is called...
    21.  
    22. GKNativeExtensions.GKFetchSavedGames((savedGameData) =>
    23. {
    24.     Debug.Log("Got saved games!");
    25. });
    26.  
    Also, may i interest you in another higher-level library we're working on, essentially covering the same stuff?
    https://forum.unity.com/threads/darkcps-game-services-simplified-looking-for-testers.697136/
     
    IgorAherne and Headup_Dev like this.
  14. Headup_Dev

    Headup_Dev

    Joined:
    Sep 18, 2013
    Posts:
    23
    Hey there,

    it looks like the class GKSavedGame from GameKit does not support tvOS at all. Other classes from GameKit work but GKSavedGame is excluded. I tried to build the library from XCode but could not get past this error. I am not a pro though so let me know if you have an idea how to fix this.
     
  15. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    I guess you're right, i can't see tvos listed here:
    https://developer.apple.com/documentation/gamekit/gksavedgame
    But it's available in GKLocalPlayer:
    https://developer.apple.com/documentation/gamekit/gklocalplayer

    Guess no tvOS :(
     
    Headup_Dev likes this.
  16. dotsquid

    dotsquid

    Joined:
    Aug 11, 2016
    Posts:
    224
    @Dark-1-Games, hi! Thanks for the library.
    Can you please explain the procedure of initialization and loading of games stored in the cloud?
    As far as I understand, I should call GKInit and then call GKFetchSavedGames to get all the saved games and use one of them in GKLoadGame. If there are any conflicts I should get an array of conflicting games through the callback passed to GKInit. However, if there are no conflicts the callback won't be invoked, right?
    So on which stage should I wait for any conflicts to resolve them before loading actual data and use them in the game? Or do I misunderstand the concept behind GKResolveConflicts?
     
  17. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hi,
    It's not super clear from apple's documentation when the conflicts callback is called exactly, but from prior testing i believe it's called if the game is running on one device and then another device saves the data at the same time (with a delay of a minute or more). However, if you're just starting the app and there's already a conflict, the callback won't be called. You need to also manually check for conflicting save game names when you get the loaded games back and call GKResolveConflicts with the resolution.

    To save yourself the headache of managing most of cloud features on iOS, Android and Steam, i would like to invite you to try out our unity asset, DarkCPS
     
  18. dotsquid

    dotsquid

    Joined:
    Aug 11, 2016
    Posts:
    224
    Thank you for a quick reply.
    Now it's a little bit clearer for me =)
    Thanks for your invitation, but the problem is the we've already released on Steam and now we are in a process of porting to iOS. So we already have an integration with Steam and switching to a 3rd-party plugin would take unnecessary time.
    Good luck with DarkCPS!
     
  19. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Sure, good luck with the project!
     
  20. denkacn

    denkacn

    Joined:
    May 13, 2011
    Posts:
    9
    Hi @Dark-1-Games
    An error occurred while building in Xcode
    Bitcode bundle could not be generated because '/Libraries/GKNativeExtensions/Plugins/IOS/libGKNativeExtensions.a(GKNativeExtensions.o)' was built without full bitcode. All object files and libraries for bitcode must be generated from Xcode Archive or Install build for architecture armv7
     
  21. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hi,

    You can build from source with whichever options you want, just open up the GKNativeExtensions Xcode project and you're good to build.
     
  22. denkacn

    denkacn

    Joined:
    May 13, 2011
    Posts:
    9
    Ok, I try it, Thanks))
     
  23. Jeeeenunity

    Jeeeenunity

    Joined:
    Sep 16, 2017
    Posts:
    2
    Hi, thanks for the good lib.

    Unfortunately I am faced with the problem that iCloud synchronization doesn’t work at the first launch after installing of the app. Although I see that there are some save files on iCloud, GKNativeExtensions tells me that there are 0 save games for me. And after the second launch of the app synchronization works as it should.

    The second problem is that from time to time I receive an error «You don’t have permission to save the file “com~apple~CloudDocs_0456C65C-D519-481E-AB31-C94777627A03_15up.bundle” in the folder “com.apple.ubiquity”. This error blocks me from saving games to iCloud. It worth to mention that I have iCloud capability enabled, correct entitlements, and iCloud container for the app.

    Please tell me if there are some ways to solve these issues.

    Thanks.
     
  24. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    The first one is a known issue that's been reported to apple, and it seems like it's fixed in the latest beta of iOS. Meanwhile, a workaround is to wait for some time (like 5-10s) and poll for changes, only on the first launch.

    I've never encountered the second issue, but i think the issue might be that user is not successfully authenticated on gamecenter before the games are loaded.
     
    dotsquid likes this.
  25. Jeeeenunity

    Jeeeenunity

    Joined:
    Sep 16, 2017
    Posts:
    2
    Thanks! I'll give your workaround a try.
     
  26. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    I'm still experiencing the first synchronization problem, and I'm using iOS 13.3.1.
    Did you find any solution for this ?
    Thanks,
    pistoleta
     
  27. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Unfortunately the only workaround we have is to wait a bit longer on the first launch of the game or solve it through UI (no save game found, try again?).

    The issue is known by apple, but we couldn't say when it's getting released.
     
  28. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    Yes, thats what I'm doing right now... where do you check if these kind of bugs are known or not by apple? is there some kind of issue tracker or GitHub Issue like page? because I struggle every time I have to find apple info stuff related... and their community forums are not very responsive generally, at least I haven't get any answer...
    Thanks a lot
     
  29. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    I submitted a report using feedback assistant a while ago, this is the status:
     
    Last edited: Mar 18, 2020
  30. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    About the workaround of waiting x seconds on first run, it's quite messy, I've been able to test on different devices and the seconds it needs its quite variable depending on the device and of course the internet connection.

    Knowing there is data on iCloud to sync:
    -iPhone XR with 4g : 4.5 sec
    -iPhone XR with 3g: 7.1 sec

    -iPhone 6 with 4g/wifi: >13 seconds

    Ill try to test with more devices.
     
  31. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    Thats from your apple developer account I guess ?
     
  32. SergioCanos

    SergioCanos

    Joined:
    Jul 4, 2018
    Posts:
    2
    Hi, is there any reason why calls to GKResolveConflicts can only be made one at a time? For what I understand from Apple documentation (here), multiple calls can be made at the same time and those are solved asynchronously.

    Great library, btw, it´s helping us a lot!
     
  33. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hey, glad you like the library.
    It's definitely possible to do multiple calls, but the main issue is the complexity that arises if there are multiple calls at the same time, and the C# code should figure out which callback function to call. We decided to play it simple and safe and allow just one call at a time.
    PR requests are welcome, so if you decide to change this, hopefully we can integrate it back to the library.

    Cheers
     
  34. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    We are using this GameKit API:
    https://unionassets.com/ios-native-pro/saving-a-game-662

    But we found an important problem, there is no way to know if a savedGame has been correctly syncronized on iCloud. Am I right? We are using this in order to save the state of a game, this game can be accessed from different devices if you login from the same account (we have time tokens so sessions dont overlap)

    This works nice on good network conditions.

    But the problem comes when a device A with a weak connection, saves game, this game gets locally saved somewhere in the phone ( I would like to know where) but it won't synchronize until gets good connection. So, in the meanwhile device B can enter the game and mess up with the progress of device A.

    Is there any way of knowing if the changes device A made have been synchronized? I know there was a token on KVS but I dont see anything similar for this.

    Do any of the assets you guys use have a solution for this scenario?

    Thanks a lot in advance.
     
  35. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    I'm not sure what GameKit does internally, but i assume it just writes to an internal icloud storage location and syncs the game when it gets the chance.
    Overlapping saves are possible, but i'd just let the conflict callback take care of it. The user will most likely notice the save is wrong, and should be presented with conflict resolution options, when the conflict is detected. There's not much else you can do anyways.
     
  36. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    No no, the problem is not the conflict, actually by not letting 2 devices run at the same time you are almost guaranteed to not have conflicts, anyway I have a conflict resolution system.

    The problem is, a device with poor connection not being able to synchronize the saved game... so device B when gets the chance to enter the game (5 mins later) does not have the last version of it.

    Thanks for your answer!
     
  37. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    I understood completely, but there's simply no way to guarantee that a save from device A is synchronized on device B. Perhaps the game is running on device A which is in airplane mode, has zero connection, and the player makes a lot of progress. The player, when playing on the device B (5 minutes later) will notice he's playing on a wrong save, and should get a prompt to resolve when device A is back online and the conflict can be detected. That's the only way to go about this in my opinion.

    Cheers
     
  38. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    Understood, I got it, thanks a lot!!:)
     
  39. SergioCanos

    SergioCanos

    Joined:
    Jul 4, 2018
    Posts:
    2
    Hi again, we are trying to make teh plugin work, but so far we are not getting any callback called from it. What settings should the library in Unity have? Should we configure something on xCode besides iCloud capabilities to make it function as expected?
     
  40. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hi Sergio, sorry for the delayed response, completely missed this.
    Besides iCloud, i believe the user needs to be logged in to game center before using any of the GKNativeExtensions methods
     
  41. Quawetim-Inf

    Quawetim-Inf

    Joined:
    Mar 10, 2020
    Posts:
    10
    Hello there. @Dark-1-Games, does your plugin works with iOS simulator?
     
  42. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Not sure, we've always tested this directly on device, but if the official library works itself, this would too.
     
  43. Quawetim-Inf

    Quawetim-Inf

    Joined:
    Mar 10, 2020
    Posts:
    10
    Thanks for fast answer. I have one more question. How can i find out why GKSaveGame returns null? Init is ok, auth too. Everything looks fine, but callback always null. I can see in source files that you use NSLog, but i don't see any errors in logs. :/
     
  44. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Make sure the iCloud container is set up properly for your app. Not sure how to help you, you could try adding extra logs.
     
  45. Quawetim-Inf

    Quawetim-Inf

    Joined:
    Mar 10, 2020
    Posts:
    10
    Found error from the device: "The requested operation could not be completed because this application is not recognized by Game Center."

    Looks like wrong Store Connect settings.
     
  46. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    @Dark-1-Games Thank you for the lib!

    do you know why in Xcode I am getting the following:


    Trying to save under name com.WhalerideLimited.SpiderPlanet/myGameFile
    The file "myGameFile.bundle" doesn't exist.


    I verified that ICloud capabilities are toggled in Xcode, but do I need to create the file before calling GKSaveGame() ?

    Thank you!
     
    Last edited: Jun 27, 2020
  47. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Hi Igor,
    I've never seen that error before, but there's a few things i'd check - The user must be signed in to Game Center before attempting save/load, there should be an icloud container linked to the app in app store, and also, GKInit must be called before doing anything.
     
    IgorAherne likes this.
  48. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    @Dark-1-Games Thank you! Still getting this message in xCode :/
    I prevent user from entering game unless logged-in to GameCenter. Here is how users login:

    Code (CSharp):
    1.     public static void SignIn(Action successCallback, Action errorCallback){
    2.  
    3.         if(Social.localUser.authenticated){//already logged in.
    4.             Debug.Log("SighnIn success: already authenticated.");
    5.             successCallback?.Invoke();
    6.             return;
    7.         }
    8.  
    9.         try {
    10.             Social.localUser.Authenticate( (bool success, string message) => {
    11.                 if(success){  Debug.Log("SignIn: success"); successCallback?.Invoke(); }
    12.                 else{ Debug.Log("SignIn: error: " + message);  errorCallback?.Invoke(); }
    13.             });
    14.         }
    15.  
    16.         catch (Exception e){
    17.             Debug.Log("SignIn exception: " + e);
    18.             errorCallback?.Invoke();
    19.         }
    20.     }

    Hm, just noticed that there were 'Debug.Log' placeholders inside callbacks for Init.
    Should use something else instead of them? This is how I was calling it:
    Code (CSharp):
    1. public static void Init(Action successCallback, Action errorCallback){
    2.      System.Action afterLogin =  () =>{
    3.             GKNativeExtensions.GKInit( (conflicts) => {  Debug.Log("Conflicts received by C#"); },
    4.                                         (modified) => {  Debug.Log("Modified game received by C#"); });
    5.             successCallback?.Invoke();
    6.     };
    7.  
    8.     SignIn( afterLogin, errorCallback);
    9. }
    10.  

    This is what my Save function looks like:

    Code (CSharp):
    1.     static void iOS_CloudSave_File( byte[] toSave,  string fileNameNoExtention,
    2.                                     Action onSave_success,  Action<PlayServiceError> onError ){
    3.         //will be called on main thread:
    4.         System.Action<SavedGameDataGameCenter> call_mainThread =  (SavedGameDataGameCenter dat) => {
    5.             if(dat ==null){
    6.                 onError?.Invoke(PlayServiceError.InternalError);
    7.                 return;
    8.             }
    9.             onSave_success?.Invoke();
    10.         };
    11.  
    12.         //will be called asap:
    13.         System.Action<SavedGameDataGameCenter> callback =  (SavedGameDataGameCenter dat)=>{
    14.             CoroutineInvoker.instance.synchronizeInvoke.Invoke( call_mainThread, new[]{dat} );
    15.         };
    16.  
    17.         fileNameNoExtention = "/iCloud.com.WhalerideLimited.SpiderPlanet/" + fileNameNoExtention;
    18.         GKNativeExtensions.GKSaveGame( toSave, fileNameNoExtention, callback);
    19.     }


    The container is also specified:

    screen.JPG
     
    Last edited: Jun 27, 2020
  49. Dark-1-Games

    Dark-1-Games

    Joined:
    Mar 26, 2014
    Posts:
    101
    Looks fine, but could you try without specifying a directory name? I don't think GameKit supports directories, and it sounds related to the error you're getting.
    Just try deleting this line:
    fileNameNoExtention = "/iCloud.com.WhalerideLimited.SpiderPlanet/" + fileNameNoExtention;
     
    IgorAherne likes this.
  50. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    @Dark-1-Games Sorry for late reply man
    Without the full path, I receive this error:
    Code (CSharp):
    1. 020-07-03 03:21:59.164470+0100 SpiderPlanet[739:323302] Trying to save under name myGameFile
    2. 2020-07-03 03:21:59.210367+0100 SpiderPlanet[739:323302] You don’t have permission to save the file “iCloud~com~WhalerideLimited~SpiderPlanet_1DC53A1B-ED9C-435F-888B-79B065A2846B_3k.bundlein the folder “com.apple.ubiquity.
    3. SaveName: myGameFile
    4. 2020-07-03 03:22:02.903674+0100 SpiderPlanet[739:323302] Trying to save under name myGameFile
    I remember that I already used to do it this way earlier, but had this error.

    Looks like it's trying to save into a root folder, hence why I've tried using the full path approach.
    Maybe the lib is trying to save some meta file in the root directory, and I don't have permission?
    Testing from xCode by the way, in a version that's not submitted to apple (maybe it would work on released version?)
     
    Last edited: Jul 3, 2020