Tag Archives: ios

Handling Game Center With iOS Multitasking

Apple’s Game Kit Programming Guide contains the following passage:

A game that supports multitasking and Game Center must take additional steps when authenticating the local player. When your game is in the background, the status of the authenticated player may change. A player can use the Game Center app to sign out. Another player might sign in before your game returns to the foreground.

Multitasking is available from iOS 4.0 onwards, though not all devices running 4.0 support multitasking. Game Center was added in iOS 4.1.

So what exactly should my game do when the Game Center player changes behind it’s back? (Note: I’m concentrating on achievements here, as Pawns does not have leaderboards.)

The simplest method that also allows for offline play is:

  • The iOS game tracks achievements that were earned on that device, and stores that information locally.
  • If Game Center is available, achievements are also pushed to the server.
  • All achievements are pushed to the server, not just as they are earned, but whenever a Game Center player is authenticated. This handles the case where the player may have earned some achievements offline.

In short: make your game work offline, then push achievements to Game Center (one-way) whenever it is available without worrying about which player is now logged in.

This method is straightforward but will lead to two glaring side effects:

  1. If a player logs into a second Game Center profile, the new profile will automatically get some or all of the achievements that were earned on the first profile.
  2. Since this is a one-way synchronization (i.e. from game to Game Center) a player with two devices may see some achievements on Game Center but not on one of the devices.

We can try to fix these issues but it quickly gets complicated. (If you aren’t interested in a discussion of the complications, feel free to scroll down to the next section: The Experiment.)

For example, fixing the first requires you to be strict about which achievements are earned by which player. You could read the achievements back from Game Center after every profile switch, but then you have to handle the case where the player earned some achievements offline- this requires merging the online and offline lists. If you do that you’ve also solved the second problem– a nice feature, but I personally think it’s pointless unless the player’s entire saved progress (saved games, list of finished levels, etc.) is also stored in the cloud using iCloud, Dropbox, or some other service.

The situation is even more complicated if the game itself supports multiple local user profiles (as opposed to games that treat everyone on the device as the same player.) Note that when the user switches to a new local profile there’s no way to switch their Game Center profile for them automatically. We can only do the reverse: switch local profiles when the Game Center player has changed. This approach would work especially well for an online-only game.

In short, there are a lot of decisions to make unless we opt for the simple, “stupid” approach. Being naturally lazy, my inclination was to do this and hope that players (and Apple) would put up with the side-effects. But try as I might I couldn’t find any blogs or forum posts describing how other developers handle this, and that made me very nervous.

The Experiment

I decided to see how other game developers handle this. I spent a happy evening playing six iOS games that I’d already earned achievements in, but as a different Game Center player. I also switched to a second device to see if any of them read the player’s Game Center achievements back from Apple server.

It was a tough job, but someone had to do it.

The results? Judging from the side effects, the following five games use the simple achievements model I described above:

All of them had the side effect where the original profile’s achievements get copied to the new profile. This was even true for Plants vs Zombies, which lets you switch local profiles. The local profiles are completely ignorant of which Game Center profile you are logged into.

The one game which didn’t take the easy way out was Space Miner. After switching to the second Game Center profile, Space Miner warned me whenever I tried to load a saved game that had been saved by a different Game Center player. (It was my choice whether to go ahead and load the saved game or cancel.) In addition, it seemed that some but not all of the old achievements were copied to the new Game Center profile! I’m guessing that the achievements specific to a single campaign of Space Miner were NOT copied to the new profile, but achievements that you’d earn over multiple games were copied. I don’t claim to understand the nuances of Space Miner’s design just from this test, but clearly the developers went above and beyond to try to intelligently reconcile local saved games and achievements with Game Center’s achievements.

None of the 6 games ever seemed to read achievements back from Game Center. When I played on the second device, only my Game Center account showed the achievements that had not been earned on that device. (This looked especially odd in Pit Droids, where the achievement “Hutt Flats Unlocked” was shown in Game Center, even though the Hutt Flats puzzles were still locked on that device.)

Note that none of the six games I tested store their saved games in the cloud for access on more than one device. Such a game would not take the simple approach I described here. I believe the easiest approach there would be to force the user to select local or cloud storage for each saved game to avoid having to merge them, but with effort it should be possible to synchronize the two more seamlessly.

Conclusion

It appears that most games simply take the straightforward approach: make and display achievements as if the game were offline, and push achievements to Game Center (one-way) whenever it is available. This wasn’t too surprising for games that have no local player profiles to switch between. I wasn’t so sure what to expect for games that do have them, but in retrospect it makes sense. Developers are simply counting on the fact that players just aren’t likely to switch to a different Game Center profile.

OpenFeint, Game Center Integration Tips for iOS

I have been adding OpenFeint Achievements (with Game Center integration) to my game, Pawns! for iOS. Here is a collection of time-saving tips that I wish I’d had to start with, particularly the Troubleshooting section. I include links to documents and forum posts that gave me useful answers.

Some of this information is specific to games made with Unity, but most isn’t. Some details may change when new versions of the OpenFeint SDK are released.

General Resources

OpenFeint’s developer website is a little disorganized, in my humble opinion. (For example, the developer forums are not listed in the main menu of the Developer Support page; they are mentioned in an announcement further down the page. ) Here are the main links developers need:

OpenFeint Developer Support: http://openfeint.com/ofdeveloper
OpenFeint Developer Forums: http://openfeint.com/developers/forum/
OpenFeint Developer Dashboard: https://api.openfeint.com/dd

Plus there are the usual Unity resources: answers.unity.com, the Unity forums (more active than OpenFeint’s), and this humble blog. 🙂

Setting up OpenFeint in Unity

° The instructions in the UnitySupport folder in the OpenFeint SDK covers most of what you need to know.

° As instructed I added the file openfeint_offline_config.xml to the Unity editor directory “Offline Config File Goes Here“. However, it didn’t copy automatically into my Xcode project so I had to add it manually. OpenFeint’s documentation does not tell you exactly where to put it. Inside the Xcode project folder OpenFeint/resources works, though it’s possible other places work as well. (Source: http://openfeint.com/developers/forum/showthread.php?89-Offline-Mode-Setup)

Achievements and Offline Mode

OpenFeint supports offline Achievements. After the first successful OpenFeint login and connection to the network, the player can view OpenFeint’s Achievements dashboard even if the device is offline. Achievements earned offline will be synchronized with OpenFeint’s servers later. But users who refuse OpenFeint in the first place cannot see the list.

Because of this I decided to implement my own Achievements page, as well as my own notification at the end of each level when they’ve earned one or more Achievements. It was more work than relying on OpenFeint’s dashboard and notifications, but it’s nice to know that even players who don’t use OpenFeint will be able to see their achievements.

Adding Game Center

° OpenFeint’s instructions for adding Game Center can be found in the Developer Support knowledge base. Currently the document is here: http://www.openfeint.com/ofdeveloper/index.php/kb/article/000089 It’s essential reading (it doesn’t cover Unity specifics, though.)

° As of OpenFeint 2.7.5, the Unity Editor plugin does not give access to the setting that turns on Game Center integration. For now you’ll have to go into the Xcode project and add it to the OpenFeint initialization settings manually, exactly as explained in the instructions. (Currently the code to change is in the file classes/AppController+OpenFeint.mm.)

° The instructions state to add your “OFGameCenter.plist” file to your Xcode project, but as usual they don’t suggest where. Put it in the folder OpenFeint/resources.

° Some versions of the instructions call the file “OFGame Center.plist”, but the filename should NOT have a space in it. (Thank you, Pete Royle for posting this tip in the comments!)

° Note that when you define your Game Center achievements with Apple’s dashboard, there’s nothing stopping you from reusing the OpenFeint Achievement ids. This makes your plist mapping even simpler:

    <?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>Achievements</key>
          <dict>
                <key>677092</key>
                  <string>677092</string>
                <key>718142</key>
                  <string>718142</string>

                <-- etc. -->

          </dict>
    </dict>
    </plist>

° For comparison, here is an achievement image I uploaded to iTunes Connect, followed by the version Game Center presents it to the player. Keep the final appearance in mind when designing your achievement icons (e.g. keep away from the borders.)

Achievement icon as uploaded to iTunes Connect


Achievement as displayed by Game Center

Testing OpenFeint and Game Center

° Before testing your OpenFeint features, go to your OpenFeint developer dashboard and register the OpenFeint user you’ll use for testing. I used my regular OpenFeint user for this, or you could create a new account beforehand using any OpenFeint-enabled app.

If you have beta-testers you’ll unfortunately need the email and password of each of their logins, and your beta testers may not wish to share their passwords with you. So you will either have to create new email accounts and associated OpenFeint users for the testers to use, or ask them to create new test accounts first and send you the particulars.

° Apple recommends you do not use your real Game Center user for testing. To set up a new test account, simply run Game Center on your iOS device, and use it to log your regular account out before you start to test. This tells Game Center to prompt you to login (or create a new user) when it starts up in your game.

° When your app is first run, OpenFeint will let you pick from a list of existing users on the device (if any) or allow you to type a new one. If your test user wasn’t already active on this device you will need to link this new profile to the test user’s account. Do this in OpenFeint’s in-game dashboard: open the user profile and give the test user’s email address and password.

Once that’s done OpenFeint should then attempt to connect to Game Center. Since you logged out from Game Center beforehand it will prompt you to login. This is where you can create a new Game Center user.

° Don’t forget to also test the offline mode. After a successful OpenFeint test, try running the app again with no network connection, and try out the OpenFeint buttons.

Troubleshooting

° If OpenFeint doesn’t mention Game Center at all at startup, double-check that you properly enabled Game Center in OpenFeint’s initialization settings.

° If you get an error that “This game is not recognized by Game Center”, try deleting your app off the device and let Xcode reinstall it from scratch. (Source: http://www.cocos2d-iphone.org/forum/topic/10528)

° When testing offline mode, if you get an error that says that offline mode was not enabled, check that an up-to-date copy of your openfeint_offline_config.xml is in your Xcode project, inside the OpenFeint/resources folder.

° OpenFeint not posting to Facebook and Twitter: this is normal when testing with an app that hasn’t yet been released. Once it is submitted to OpenFeint (to let them know the version with OpenFeint has been submitted to Apple) they’ll turn on the social media links. (Source: http://openfeint.com/developers/forum/showthread.php?273-Can-t-able-to-see-published-event-in-facebook-after-submission-got-succeded&highlight=twitter)

° I had a problem with OpenFeint 2.7.5 notifications never showing when an Achievement was unlocked. I posted a solution for this last month.
http://www.mindthecube.com/blog/2010/11/openfeint-2-7-5-not-notifying-of-achievement
Alternatively, see the section “Achievements and Offline Mode” above for reasons to write your own notifications instead.

* If OpenFeint is working but there is no Achievements dashboard for your game, or some achievements are missing, make sure your game’s bundle version is set high enough. For example, you may have configured your OpenFeint achievements to be visible with version 1.1 or higher. (In Unity, the version is set in the project’s Player settings before rebuilding the XCode project. Non-Unity developers would update directly the Bundle Version in their game’s Info.plist file.)

If the achievements still aren’t visible, then make sure you registered the test user in OpenFeint’s Developer Dashboard. Also double-check that you added the test user’s email and password on the player dashboard on the device.

Conclusion

Adding OpenFeint and Game Center wasn’t difficult, but there was some trial and error, a couple of gotchas, and I had to do a bit of searching for some of the answers. Hopefully these tips will smooth the way for others, and for myself when I start working on my next game.

Adding iPhone Touch Scrolling to a UnityGUI ScrollView

When running on the iPhone, UnityGUI ScrollViews act as if the player is using a mouse. The player can tap on or drag the scrollbar, but they can’t drag the list itself up and down with their finger. And naturally, that’s the first thing my playtester tried to do. So it would be useful to be able to add touch behavior to UnityGUI scrolling lists.


Pawns Puzzle Picker

Here’s how I did it. This article may be useful to someone porting UnityGUI code to the iPhone, or who just wants a working example of how to use the different iPhone touch phases.

(For a downloadable Unity package demonstrating everything in this post, scroll to the bottom.)

Design

The desired behaviors are:

  1. Allow the player to drag the list up and down
  2. Single-tap selects a row
  3. List has inertia, drifts to stop when the user lets go

Note that when a touch begins, we don’t yet know whether the player is starting a tap or a drag. And if they drag a short distance and then let go, did they tap that row or not? To avoid ambiguity many apps have the user press a “Done” button to confirm their selection. Other possibilities are to require a double-tap on a row, or treat it as a tap only if it is released quickly.

I decided to copy behavior I liked in Things for iPhone, which highlights the row when it is first touched. But if the user starts to drag it, the highlight disappears. So a tap can be slow as long as the finger doesn’t move. I like this because you can see what you’ve selected, and cancel it (with a short drag) if you missed your target.

Implementation

It should be possible to access touches during the OnGUI logic. I didn’t do it that way. Instead, my OnGUI code draws the scrolling list but no longer responds to clicks (although as I note below, that code can be left in for cross-platform purposes.) The code for detecting and responding to touches is now all in Update. (This is just a matter of preference, but by separating the row drawing from the code that handles input, I suspect it will be easier to replace the iPhone touch logic when porting the code to a new platform.)

1. Responding to Drags

The code for dragging the list turns out to be easy. All we need to do is detect when a touch delta has occurred. We then adjust the list’s current scroll position by the same amount.

if (touch.phase == TouchPhase.Moved)
{
    // dragging
    scrollPosition.y += touch.deltaPosition.y;
}

Depending on what controls you have in your scrolling list, you may find that the one that is being touched for the drag highlights, and stays highlighted after the player lets go. The solution is either to use a control that doesn’t behave that way (e.g. a Label instead of a Button) or to change the GUIStyle/GUISkin to make selected controls look the same as unselected.

2. Responding to Taps

Things can get a little trickier for detecting single-taps on a control in the list.

It’s tempting to keep the standard GUI click detection code in place. That can be used in some cases, but not if we want the behavior that I described above, where the row is highlighted when the touch begins, and can turn into a selection or a drag depending on if the finger moves or not.

So instead I took out the GUI click-response code and manage the taps myself. The trick here is in figuring out which row was tapped from the touch coordinates. Touch coordinates need to be adjusted as follows:

  • UnityGUI y-coordinates are inverted when compared to touch coordinates.
  • Touch coordinates need to be adjusted by the amount that the list has been scrolled.
  • UnityGUI windows and scrollframes also add offsets to the coordinates.

In my example the scrolling list is in a ScrollView, which in turn is in a Window. So the code to calculate which row a tap was on looks like this:

private int TouchToRowIndex(Vector2 touchPos)
{
    float y = Screen.height - touchPos.y;  // invert y coordinate
    y += scrollPosition.y;  // adjust for current scroll position
    y -= windowMargin.y;    // adjust for window y offset
    y -= listMargin.y;      // adjust for scrolling list offset within the window
    int irow = (int)(y / rowSize.y);
 
    irow = Mathf.Min(irow, numRows);  // they might have touched below last row
    return irow;
}

We also need to test if a tap is actually inside the scrollable list. (Again, we have to offset the list rectangle with the coordinates of the Window it is inside.)

bool IsTouchInsideList(Vector2 touchPos)
{
	Vector2 screenPos    = new Vector2(touchPos.x, Screen.height - touchPos.y);  // invert y coordinate
        Vector2 listSize     = new Vector2(windowRect.width - 2*listMargin.x, windowRect.height - 2*listMargin.y);
	Rect rAdjustedBounds = new Rect(listMargin.x + windowMargin.x, listMargin.y + windowMargin.y, listSize.x, listSize.y);
 
	return rAdjustedBounds.Contains(screenPos);
}

Now the code to handle drags and taps is as follows. I also added code to handle the Cancelled touch phase, which like the Moved phase cancels the selection. (Taps that are not inside the list are ignored; drags that go outside the list cancel the touch.)

fInsideList = IsTouchInsideList(touch.position);
if (touch.phase == TouchPhase.Began && fInsideList)
{
	selected = TouchToRowIndex(touch.position);
}
else if (touch.phase == TouchPhase.Canceled || !fInsideList)
{
	selected = -1;
}
else if (touch.phase == TouchPhase.Moved &&  && fInsideList)
{
	// dragging
	selected = -1;   // cancel the selection in favor of the drag
	scrollPosition.y += touch.deltaPosition.y;
}
else if (touch.phase == TouchPhase.Ended)
{
       // Was it a tap, or a drag-release?
       if ( selected > -1 )
       {
           Debug.Log("Player selected row " + selected);
       }
}

We also need to highlight the row that is being touched, at least until the player starts to drag it. I couldn’t find a documented way to tell UnityGUI to draw the next row with a different state (i.e. Hover or Focus, instead of Normal.) So instead I have a separate GUIStyle for selected rows. In the OnGUI code that draws the rows, I have the following:

       	if ( iRow == selected )
               	GUI.Button(rBtn, rowLabel, rowSelectedStyle);
       	else
       		GUI.Button(rBtn, rowLabel);

3. Inertia

When the user drags and then lets go a standard iOS scrolling list, it doesn’t stop moving immediately. We want that same effect of drifting to a stop. I do this by calculating an initial velocity when the touch ends. This moves the list for half a second, or until a new touch begins.

When the code detects that the touch has ended (TouchPhase.Ended) it stores the y-component of last touch’s deltaPosition. Dividing this by the deltaTime gives the starting velocity, which is gradually reduced to zero over the next half-second. This doesn’t have exactly the same feel that iOS lists have, but it’s very close.

Here is the code that sets up the inertia when the touch phase has just ended. It stores the time of the release and how fast the list was being scrolled at the time.

if (touch.phase == TouchPhase.Ended)
{
     // Was it a tap, or a drag-release?
     if ( selected > -1 )
     {
         Debug.Log("Player selected row " + selected);
     }
     else
     {
         // impart momentum, using last delta as the starting velocity
	 // ignore delta < 10; rounding issue can cause ultra-high velocity
	 if (Mathf.Abs(touch.deltaPosition.y) >= 10) 
	     scrollVelocity = (int)(touch.deltaPosition.y / touch.deltaTime);
	 timeTouchPhaseEnded = Time.time;
     }
}

I ran into a surprising bug where small finger movements would scroll the list extremely quickly. It turns out that a tiny movement of the finger can get rounded up to a delta of 5 pixels. This is a small amount, but given a fast framerate, this rounded amount represents a monstrously high velocity! The simple solution was for my inertia code to ignore very small movements. Less than 10 pixels seemed to work well in my tests on an iPhone 2g. (It’s possible that higher-resolution iPhones and iPads round by a different amounts.)

Here is the code that runs when there are no touches (or multiple touches, which I chose to ignore.) If the list has some leftover velocity from the last drag, it moves the list for a while longer. This uses the variables that were stored by the previous code sample.

if (Input.touchCount != 1)
{
    selected = -1; 
 
    if ( scrollVelocity != 0.0f )
    {
	// slow down over time
	float t = (Time.time - timeTouchPhaseEnded) / inertiaDuration;
	float frameVelocity = Mathf.Lerp(scrollVelocity, 0, t);
	scrollPosition.y += frameVelocity * Time.deltaTime;
 
	// after N seconds, we've stopped
	if (t >= inertiaDuration) scrollVelocity = 0.0f;
    }
   return;
}

Testing reveals that the list works well now. But there is a question of what to do with the original scrollbar. Dragging on it is a bit weird because it moves in the opposite direction as the list. In other words, you drag the list up to move down, but you drag the scrollbar up to move up. For Pawns I simply made the scrollbar very narrow to discourage tapping on it, but it remains as a visual cue that this is a scrollable list.

Miscellaneous

Note that if you make your controls respond only to touches, they can only be tested via UnityRemote or with an actual iPhone or iPad. So I found it useful to keep the original click logic in the OnGUI method as well. The code can be wrapped with a check to make it run only for non-iOS builds:

        if ( fClicked && Application.platform != RuntimePlatform.IPhonePlayer )
        {
           Debug.Log("Player mouse-clicked on row ");
        }

However, note that this code still runs in Unity Remote, which can lead to cases where a touch is interpreted both as touch and as a click.

One last optimization: we can reduce the number of drawcalls a lot by only drawing rows that are actually visible- a trick I blogged about earlier.

// draw call optimization: don't actually draw the row if it is not visible
// rBtn is the rectangle of the row we are about to draw
// rScrollFrame is the rectangle of the scrollview
if ( rBtn.yMax >= scrollPosition.y && 
     rBtn.yMin <= (scrollPosition.y + rScrollFrame.height) )
{
    	// ... GUI drawing commands for the current row ...
}

Demo

The following Unity 4 package (UPDATED 7/4/2013) contains sample GUI scripts that demonstrate a simple scrolling list that responds to iPhone/iPad touches. (In theory these should work on Android as well.) One script is written in C#, the other in JavaScript, but they are otherwise identical.
GUITouchScroll Package for Unity 4

Import it into your project. This will create a new folder, GUITouchScroll. Drag one of the GUITouchScroll prefabs that’s in the folder into your Scene view to create the sample scrolling list– one prefab uses the C# script, the other Javascript. The number of rows, and the window and list dimensions can be adjusted in the Inspector. The example uses the default UnityGUI skin, but this also can be replaced in the Inspector. You can also adjust the inertia, which is how quickly the list stops moving after you let go of a drag. (Fun tip: if you set inertia to 10 seconds a fast drag can leave the list bouncing up and down a few times!)

To test the scrolling and tap behavior, run the scene with Unity Remote or on an iOS device. When a row is tapped, a line of text will be output to the Unity debugging console. (Keep in mind that the scrolling will not look or feel very smooth on Unity Remote, due to the reduced frame rate.)

Important: If you are developing for Android please note that the touch events apparently work a little differently, which breaks the inertia. Please see the comments below from Ali, Greg, and Roberto for ways to fix it. My thanks to them!

(7/4/2013) I have added code to section 2 for detecting whether the touch is inside the scrolling list or not. Thanks for reporting that bug, everyone! This code has also been tested with Unity 4 now and it works without modification.