Friday, June 14, 2019

Unity Games Using CloudKit on MacOS (Part 1)

We are in the process of developing several games for various Apple products, and we're using CloudKit to keep game saves in sync across the various platforms (iOS, tvOS, macOS).  There's a lot of good documentation out there for iOS and tvOS, and Unity provides good tools for exporting the projects to Xcode and letting you build things in Apple's environment with all of their tools readily available.

This is not the case with macOS.

Unity still uses a precompiled binary executable (even with IL2CPP builds) and does not generate an Xcode project that will handle all of your codesigning and entitlement needs.  All of that has to happen manually.  (There do seem to be plans to change this.)  Our task is doubly complicated because the game needs to be released not only on the Mac App Store, but also as a standalone product and still have access to CloudKit features.

Part 1 of this process will detail getting signing identities, App IDs, provisioning profiles, etc. set up, as well as the process of getting your game built and signed correctly to run in a development environment.  Part 2 will (eventually) detail changes you need to make to make the game viable to upload to the App Store and distribute generally.

First off, a huge debt of gratitude to the folks who put together the Unity Apple Distribution Workflow document.  This guide is thorough and well-referenced.  Things with Apple keep changing, so the blog post you're reading now is accurate for the summer of 2019, but may become less so as Apple changes things in the future.

Also thank you to eppz! Appcrafting for their amazing work digging into the bits and pieces of Unity so they could point us at the tools we needed to finally solve CloudKit access.

Certificates

We are working on a development version for the moment, so you only need a development certificate.  You do specifically need a Mac Development certificate.  You can either create one yourself on developer.apple.com or have Xcode do it for you.

App ID

Our goal is to have iCloud support (through CloudKit) across all platforms: iOS, tvOS, and macOS.  You can only create App IDs that target either iOS/tvOS or macOS.  With iOS/tvOS being the flagship platforms, it probably makes more sense to create your primary App ID for them and have a secondary one for macOS.  But the secondary one will need to have a different name.

Both App IDs will need to have the iCloud/CloudKit capability set.  Again, you can do all the primary work on the iOS/tvOS App ID.  If you're also doing your Unity game on those platforms, make a build there, have it generate the Xcode project, and use Xcode to turn on the iCloud capability and set up CloudKit.  It'll handle creating your default container (which will be named for the iOS/tvOS App ID).

For the macOS App ID, you enable iCloud/CloudKit, but then for the container, point it at the existing container you made for iOS/tvOS.  This way you can share data across platforms.

Register Test Devices

You still need to register your Mac as a test device through Apple.  Xcode can do this for you if you make a temporary project and set your development team.  If you want to do it manually, your Mac's UDID is in Apple -> About This Mac -> System Report -> Hardware (first item selected) -> Hardware UDID.

Provisioning Profiles

You will need to make provisioning profiles that combine your signing certificate, App ID, and test devices.  For development, if you're using the Xcode-created signing certificate, you will need to make a temporary Xcode project, make a build, and then extract the provisioning profile from the build.  If you're using certificates you created on developer.apple.com, you can similarly create provisioning profiles there and download them.

Since we're doing a development build, create your provisioning profile with your development certificate, then make sure to set your Mac as one of the allowed devices before generating and downloading it.  Also make sure you've taken care of all the CloudKit setup before you generate the provisioning profile.

Entitlements

You need to create an entitlements file for use when signing your game.  The easiest way to do this is to create a temporary Xcode project, give it your credentials, and enable iCloud/CloudKit on it.  Make sure the iCloud container identifier is pointed at the correct one as described in the App ID setup previously.  Here's a sample:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-only</key>
    <true/>
    <key>com.apple.developer.aps-environment</key>
    <string>development</string>
    <key>com.apple.developer.icloud-container-identifiers</key>
    <array>
        <string>your container identifier</string>
    </array>
    <key>com.apple.developer.icloud-services</key>
    <array>
        <string>CloudKit</string>
    </array>
    <key>com.apple.application-identifier</key>
    <string>your team identifier.your game app ID</string>
    <key>com.apple.developer.team-identifier</key>
    <string>your team identifier</string>
</dict>
</plist>

iCloud

Sign into iCloud on your test Mac.  Make sure you have iCloud Drive enabled, or CloudKit will not work.

Unity Build

Make your Unity build as normal.  This post won't get into integrating CloudKit into your game (we've used Prime31's iCloud Plugin); we'll assume that is taken care of.

You will need to do this work on a Mac.  You can make Unity builds on Windows, but you will not be able to perform the codesigning steps there.

The build process will generate an application bundle (<your game name>.app) which is really a folder that contains all the important files.


  1. Delete all the .meta files in your plugins.  The Unity Apple Distribution Workflow details this out.  Codesigning will fail if you don't do this, because there are files in unexpected places (from Apple's perspective).  Plugins are located in <your game name>.app/Contents/Plugins.
  2. Modify your Info.plist file (<your game name>.app/Contents/Info.plist) per the Unity Apple Distribution Workflow.  For the purposes of making a development build, you just need to add the key "ITSAppUsesNonExemptEncryption" set to false.  We found that for us (unlike the Unity Apple Distribution Workflow recommendation), we did not need to modify each plugin's Info.plist.
  3. Copy over the correct provisioning profile to <your game name>app/Contents/embedded.provisionprofile.
  4. Modify the Unity executable to link the CloudKit framework.
    1. Following from the eppz! blog, you need to use the third party tool optool.
    2. Run the command optool install -c load -p "/System/Library/Frameworks/CloudKit.framework/Versions/A/CloudKit" -t "<your game name>.app/Contents/MacOS/<your game name>"
    3. This will modify the Unity binary to load the CloudKit framework at startup.  We found that without this - even though the CloudKit framework is linked in the Prime31 plugin - actual calls to CloudKit will fail with the error "connection to service names com.apple.cloudd was invalidated".
  5. Change file permissions for your game bundle: chmod -R a+xr "<your game name>.app"
  6. Codesign your build.  Follow the steps laid out in the Unity Apple Distribution Workflow, but with some modifications that have worked for us for making a development build.
    1. Sign all the .dylib libraries in <your game name>.app/Contents/Frameworks manually.  Do not include entitlements in the signing command.
      1. codesign --force --verify --sign "<your development signing id>" --preserve-metadata=identifier,entitlements,flags "<your game name>.app/Contents/Frameworks/<library name>.dylib
      2. Do this for all the libraries in Frameworks, both in the root folder and in sub-folders
    2. Sign all the plugins in your game.  Do not include entitlements in the signing command.
      1. codesign --force --verify --sign "<your development signing id>" --preserve-metadata=identifier,entitlements,flags "<your game name>.app/Contents/Plugins/<plugin name>.bundle"
    3. Sign the final game application bundle.  Despite what the Unity Apple Distribution Workflow says, we've discovered that you have to include entitlements even in the development signing.  Also, because we signed everything individually, do not include the "--deep" flag, because it will mess with the existing codesigns we've done.
      1. codesign --force --verify --sign "<your development signing id>" --entitlements "<path to your entitlements files>" "<your game name>.app"
At this point your game should be signed and ready to test in development mode on the Mac you registered.

Note that the game will run in sandbox mode.  It will still use whatever user you've signed into iCloud with on your Mac, but all of its files will be written to ~/Library/Containers/<your app ID>/Data/Library/.

Wednesday, April 24, 2019

Super Blood Hockey!

We haven't made a lot of posts the last three months, but have been keeping busy.  We've had some development work that we handled under contract, and meanwhile have been working with Digerati to port Super Blood Hockey to all three consoles!

This is officially announced as of a little while ago, check it out!  The Switch version will be out in the next few days (on the eShop here), and the Xbox and PS4 won't be far behind!  If you've got nostalgia for the old-school hockey games of the Genesis/SNES era, and want one of those amped up to 11, this is your jam!


EDIT: Now available, check it out!

Tuesday, December 25, 2018

Aquatic Adventure, Uncanny Valley and Omega Strike now available on Switch!

We helped port these titles for Digerati, and as of Christmas they're now available on the eShop, check it out!

Image result for omega strike game

Omega Strike is a contra-esque platformer with metroidvania elements.  It's got nice sharp pixel art and tight controls, and well worth checking out if you're a fan of the genre.

Image result for uncanny valley game

Uncanny Valley is a sort of story-based survival horror title.  It's unique in that there's really no game-over, more a series of branches that lead to different events and ultimately to different endings.

Image result for aquatic adventure of the last human

The Aquatic Adventure of the Last Human is hard to describe, it's almost an underwater Shadow of the Colossus style experience, with a series of well-designed boss fights connected by an open world.  We really grew to dig this title, in particular, while working on it.



Tuesday, December 11, 2018

Desert Child now available on all platforms!


We helped Akupara bring their pixel-based racing title to all three consoles and all three PC platforms, on a variety of storefronts.  Check it out on whatever platform you prefer!

Official Desert Child page here!

Thursday, August 30, 2018

What About Localizing My Game?

This started as a Reddit post in reply to a question about how to handle localization, but it got quite long so I figured I'd post it here.  Hopefully this is useful to someone.

There's not a ton of work required, on a technical level, to build yourself a functioning localization solution.  Someone experienced can probably build what's necessary in an afternoon, and none of the work is exotic.  Unity doesn't have one in place by default however, and so I think a lot of folks who are just learning tend to gloss over the infrastructure or not realize how much difficulty they could have down the line trying to retrofit support.  Luckily it's pretty easy and I encourage preparing any serious project for it from the beginning.  Here's roughly what we use.

Firstly, we have a TextManager that is a mostly-static singleton class.  This gets initialized on first use if needed, but also manually initialized in the game's startup scene, and that mostly consists of setting the desired language, then reading in the actual strings and sticking them in a dictionary.  We only read in a single language at a time, and the languages exist in a set of parallel files that use the Android XML format.  If you set TextManager to german, it loads the german.xml file, if you set it to english it loads the english.xml file, etc.  You can then use a static method like TextManager.GetString("level_readout") to retrieve an entry.  It searches for the "level_readout" key and returns the actual text for your loaded language.

We've got a lengthy history of Android dev, which is really the only reason we use that file format.  A CSV or set of CSVs is probably the best in my opinion, since when you get to the point of actual localization you'll very commonly end up sending spreadsheets to people.  A format that makes that simple is useful.  Alternatively iOS or Android style files are useful since a lot of services can work with them directly.  Remember you will very likely have to merge things eventually, so whatever you do don't use a binary file for this.  UTF-8 text is ideal.


CSV file with key in first column, languages in following columns

Requirements for the entries are quite simple, whatever your format.  You have a dictionary-style key, which is what you'll use to refer to the strings in code, and then the actual text entries which are what will ultimately be what you put on-screen.  No matter how good the idea sounds at the time, do not use your english string as the key.  Strings change, your first draft is often temp, and you don't want to have to edit your code in order to fix grammar or spelling or whatever.  In fact, I encourage forcing your keys to lowercase, and disallowing things like spaces as well.  Treat your string keys like variable names.

Now here's the important part: make sure you use it.  Whenever you put text on the screen, call TextManager.GetString() and use the result.  That real-text string is what you put on screen.  Directly putting user-visible strings in your code is now verboten, and you should feel dirty for doing it.  This means that in addition to lengthy strings like dialogue or item descriptions, you'll end up with short strings that are simply labels, or that have replacement fields in them like "Level: {0} of {1}".  Replacements like that are better than assembling a few tiny strings because some languages may prefer a formatting with a different order.

This sounds like a hassle, but it quickly becomes not a big deal -- you're probably only making a couple entries at a time, and you'll mostly only end up dealing with english (or whatever your native language is) during development.  I assure you, doing it as you go is far, far less painful than tracking down hundreds of strings scattered across your codebase later down the road.

You'll probably end up with some convenience functions for common types of usage.  If you're using Unity, a component that automatically fills in a text component on the same GameObject is an obvious addition, as well.  Whether you want to try to deal with switching language on the fly is something to consider, and that's getting more complicated since it means menus and stuff will need to be notified and update all their text fields.

If you really want to help your translators, add a section to your CSV (or whatever) that lets you fill in context for the string.  You don't need to read this in yourself, but remember your translator will often only have the list of strings to refer to and won't have the time to find that text in the actual game.  A note that says "Refers to the player's current experience level" can be hugely helpful, since what if that string is talking about unlockable areas or something?  Things like that often come back as questions if the context isn't obvious.

If a key is not found upon attempting to retrieve a string, make it very obvious.  We literally return something like "MISSING: level_readout" so it screams incorrect to the viewer.  During development I could see having your non-native languages fall back to the native string that (almost certainly) exists, but once you get into a QA finishing-things stage you should remove that so nothing gets missed.

There's a whole pile of features you could add to a system like this, but for my money they pretty quickly start seeming not worth the trouble.  We have some python scripts to convert our XML files into a couple of other formats like CSV, but have never had much call to do something like upload them dynamically to Google Drive or whatever.  You could invent your own super-easy-to-parse format if you wanted to, too.  So long as what you have is consistent and you use it, that's the big thing.


Thursday, August 23, 2018

HackyZack now available on Switch!



It's a tricky platformer, where you're also playing hacky sack to reach your destination!  We worked to bring this to Switch, and added support for two player cooperative play at the same time.  Have a friend join in and work together to get your Hacky to the goal!

Take a look!
HackyZack!