Tag Archives: iPad

Fixing Unity and OpenFeint iPad Rotation Issues

Unlike iPhone games, Apple requires iPad games to support orientations symmetrically. For example, if a game works in one landscape orientation, it should also work when rotated 180 degrees. And this should be true whether the iPad is rotated before the game is run, or while it is played.

There are well-known Unity scripts to address the two major rotation issues most iPad developers run into. I’ll review those solutions first, then describe how to fix new problems introduced with OpenFeint.

Basic iPad Rotation Behavior

There are two behaviors that Unity games should implement for the iPad:
• Change the game’s orientation when a new (supported) orientation occurs
• Turn off the “rotating black square” seen as iOS rotates its menu bar (the one that is normally hidden behind your Unity game.)

I take no credit for the following solution, although I combined them into one script for my own convenience:

public class iPadRotationFixer : MonoBehaviour 
{
    void Start()
    {
        // Turn off the visible iOS menu rotation
        iPhoneKeyboard.autorotateToPortrait = false;
        iPhoneKeyboard.autorotateToPortraitUpsideDown = false;
        iPhoneKeyboard.autorotateToLandscapeLeft = false;
        iPhoneKeyboard.autorotateToLandscapeRight = false;
    }
	
    void FixedUpdate () 
    {
        // In this example, only the two landscape modes are supported
        if ((Input.deviceOrientation == DeviceOrientation.LandscapeLeft) 
                && (iPhoneSettings.screenOrientation != iPhoneScreenOrientation.LandscapeLeft))
        { 	
            iPhoneSettings.screenOrientation = iPhoneScreenOrientation.LandscapeLeft;
        }
	   
        if ((Input.deviceOrientation == DeviceOrientation.LandscapeRight) 
                && (iPhoneSettings.screenOrientation != iPhoneScreenOrientation.LandscapeRight))
        { 
            iPhoneSettings.screenOrientation = iPhoneScreenOrientation.LandscapeRight;
        }
    }
}

Just put this script on an empty GameObject in every scene of your game. (I usually name the object something like “ziOSRotationFixer” so that it sorts to the bottom of the Hierarchy view.) The Start method is only needed in the initial scene, but does no harm to run it more often.

Startup Logo

In your iOS project settings you can also set the Default Orientation, but I don’t think it matters for iPad games. Unity for iPhone 1.7 and earlier had a problem where the Unity splash screen would only show in one orientation on the iPad- you can still find old discussions about how to fix that, but as of Unity 3.0 the splash screen orients correctly on startup.

OpenFeint and iOS 4.2

The above solution has worked with only minor changes since the iPad first came out. So you can imagine my dismay when a tester found rotation problems after I added OpenFeint to Pawns. The tester found that when he double-clicked the Home button during a game, the multitasking bar would sometimes be upside-down. I found the game’s OpenFeint dashboard had the same problem.

Both problems stemmed from my misuse of OpenFeint. Just as Unity needs to be told to rotate the screen orientation, OpenFeint also needs to be told. This just requires adding a couple lines to the FixedUpdate method above, which I’ve marked as new.

    void FixedUpdate () 
    {
        if ((Input.deviceOrientation == DeviceOrientation.LandscapeLeft) 
            && (iPhoneSettings.screenOrientation != iPhoneScreenOrientation.LandscapeLeft))
        { 	
            iPhoneSettings.screenOrientation = iPhoneScreenOrientation.LandscapeLeft;
            OpenFeint.SetDashboardOrientation(DeviceOrientation.LandscapeLeft); // ** NEW **
        }
	   
        if ((Input.deviceOrientation == DeviceOrientation.LandscapeRight) 
            && (iPhoneSettings.screenOrientation != iPhoneScreenOrientation.LandscapeRight))
        { 
            iPhoneSettings.screenOrientation = iPhoneScreenOrientation.LandscapeRight;
            OpenFeint.SetDashboardOrientation(DeviceOrientation.LandscapeRight);   // ** NEW **
        }

        // The above is for Landscape. Don't forget to add Portrait cases if you need them
    }

(Source: http://forum.unity3d.com/threads/50822-OpenFeint-iPad-screen-orientation-on-startup)

This solution works great when the orientation changes as the game is running.

However, the initial OpenFeint dialog can still show upside-down! This problem was discussed in the above thread and in other places, but there doesn’t seem to be widespread understanding of the cause or how to fix it. So I looked into it myself and came up with the following.

The problem is simply that, when you first set up the OpenFeint plugin for Unity, you have to specify the startup orientation. OpenFeint relies on this for the initial orientation.

(Why is the initial orientation fixed and not dynamic? I think it’s mainly because it was originally developed for the iPhone. Also note that the developer can trash OpenFeint’s Portrait or Landscape resources if the game doesn’t need them to save space. So OpenFeint can’t just orient itself dynamically to any direction it pleases. It needs to be told what orientations to use.)

Happily, we can make it check the device’s orientation by adding a few lines to the OpenFeint initialization code in the Xcode project. This is going to work like the FixedUpdate method: we’ll explicitly code for the orientations we need to support.

Here is the old code that we’ll change. It takes the Initial Dashboard Orientation setting and puts it right into the dictionary. (Note: this is the same chunk of code that gets edited when you want to enable Game Center.) Currently the code is in the file classes/AppController+OpenFeint.mm.

    NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
         [NSNumber numberWithInt:UOF_INITIAL_DASHBOARD_ORIENTATION], OpenFeintSettingDashboardOrientation,

The replacement code checks the device’s orientation dynamically. Again, I am only supporting left and right landscape modes for my game:

    id orientation = [NSNumber numberWithInt:UIDeviceOrientationLandscapeLeft];
    if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight)
    {
        orientation = [NSNumber numberWithInt:UIDeviceOrientationLandscapeRight];
    }
    NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
                orientation, OpenFeintSettingDashboardOrientation,

Now OpenFeint’s initial orientation is correct, and so is the iOS multitasking bar.

Note that this code may get overwritten if I install a new version of OpenFeint’s API into my project. That’s the downside of going in and tweaking OpenFeint’s Unity plugin or their code. Like my last tweak (adding support for percent-complete achievements) it’s possible OpenFeint will fix it themselves in a future version. While they are at it, maybe they’ll add the Game Center switch to the Unity plugin as well. Then there would be no need to edit this code at all.

Coming soon- Pawns for iPad and iPhone!

The page is up for my forthcoming game, Pawns for iPad and iPhone! More than just a port of the Mac & Windows game, this version has many more puzzles, rated for difficulty. The user interface has been redesigned for touch screens. Not to mention the two most-requested features of all: knights and queens!

The game is currently in beta-testing and I hope to release it in about a month. Stay tuned!

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.