iPhone Skrollin Example

iSkrollinTitle

Overview

This is a tutorial and example project that covers a basic solution to iPhone Scrolling in Unity-iPhone, including flick and glide, margin resistance and scroll vector icon.

This tutorial will cover importing text assets that get assigned to a string variable for display in a custom sized window, and a scrolling group window containing buttons in a clickable options menu style.

This method presumes that you will not need to scroll text during frame-rate critical parts of your game, as it is built in OnGUI() & GUI.x, which are resource heavy. For the purpose of displaying text, menus or options buttons while the game is paused, it’s efficient, but it probably should not be used to display large volumes of scrolling text during game play.

Currently there are some limitations to this implementation. This method was used for the menus and text assets in Moonbase: Lander. As such, there are still a few elements that are not fully genericized and may have to be manually adjusted to suit your project. In addition, the scroll vector icon is a fixed graphic in this project, which is different from the behavior of the scroll vector icon in many applications created directly in the Apple iPhone SDK which is scales on the y-axis depending on text volume and margin pressure. I plan on working that aspect of this project, but it’s not on the immediate schedule.

The Example Package

Start by downloading the example package here:

Open Unity iPhone* and create a new Project. *[Unity now has the iPhone add-on integrated into the base editor!]

From Assets > Import Package … import the isSkrollin.unityPackage.

Looking at the project in Unity iPhone, you can see that this example is split into seven simple directories: Scenes, GUISkin, Images, Scripts, Text Assets, Type Faces and iPhone Graphics.

The Main Scene is simply the default main camera with the StartSkrollin.js script attached.

The GUI Skin is a simple default skin with custom styles for the various type faces and styles in the example project. (At this point I will not cover the use of GUI Skins beyond mentioning that the custom styles need to be set up in your project with your custom type faces if you want something other than the default Unity look.)

The Images are a series of small non-power-of-two images used by OnGUI(). They are provided for the purposes of this tutorial, but won’t be discussed in detail.

The Script, StartSkrollin.js, is the main force driving this project, and we will get to that later in more detail. This script is attached to the main camera. StartSkrollin.js is extensively annotated to help understand how the script works.

The Text Assets are a series of random bits of text for the purpose of showing how to display and scroll text. They should all be copyright free and available to use as place holder text, including the familiar Lorum Ipsum asset.

All of the typefaces should be accompanied by their respective licenses (if available), and were found at either:

http://www.dafont.com/
or
http://www.1001freefonts.com/

[Please see this post for additional font resources.]

Lastly there are two iPhone graphics. One is for the 57 pixel icon and the other is the optional “loading…” splash graphic for iPhone Advanced users.

StartSkrollin’! – The Setup

The basic concept behind this script is to have a scrolling text asset system based on creating a window frame in the desired location for content on the screen. A second window containing the content, either text only or a group window of text and/or graphics, is created that sits within the first window frame. This content window is then scrolled up & down with the touch of the user’s finger to reveal the text and/or graphics within the window frame.

KarolMagniOpen StartSkrollin.js in your editor and take look. It is relatively well annotated to give the reader a better understanding of how the script is laid out.

StartSkrollin.js is driven by two major sections: OnGUI() and Update(). OnGUI() controls the images & windows and how all the elements relate to each other. Update() controls the scroll vector and how it behaves. The scroll vector drives the location of the scrolling content window.

OnGUI() is also made up of two major divisions: Normal OnGUI() and CreateWindow. (In one case CreateGroupWindow is used for a scrolling field of buttons.)

However, before we look closely at OnGUI() and Update(), let’s stop and look at the setup of variables at the beginning of the script. Most of these are pretty obvious, but just in case, let’s quickly cover them.

In the “Asset Variables” section, you need to set up your references to the GUI Skin, the Text Assets & their Strings, and the GUI Textures. NOTE: That when setting up the Text Assets there is an exposed public TextAsset variable and an accompanying private String variable with a related name (e.g.: var introTextAsset : TextAsset; & private var introText : String). This is so we can easily edit our text as text files in an external editor and save them as text assets in the assets directory of Unity-iPhone. These text asset files are dragged onto the appropriate TextAsset variable in the inspector to form a link between the asset and the variable. Then, when we run the game, in Start(), we assign the Text Asset to the text String.

function Start () {
  introText = introTextAsset.text;
  ...
}

This assignment gives us the base String variable we need when working with our windows in OnGUI(), but allows us to edit the TextAsset in and external editor. (NOTE: That we don’t assign introText = introTextAsset; but we link it to the associated variable text as introTextAsset.text.)

Returning to the Setup in StartSkrollin.js, let’s look at the “Basic Variables” section.

The first group of variables is for the glide or coasting of the scroll, the size of the margin beyond the end of the text window and the springiness of the pressure when the contents are scrolled out of bounds. The variables marginSpringiness, touchSpeed, coastSpeed, scrollMargin are all set by the user and used in Update(). We will look at them more closely in context.

The second group of variables are for Update() to understand and track the current scrolling action. Primarily these are container variables for the value of a current state or current version. The variable scrollVector is the current value for where the content window is currently scrolled. The variable scrollMin is the container for the current scrollMin… value from the current view. The values of resistance & marginPressure are calculated by Update(). The variable deltaY is used to track how much scrolling/movement is taking place, and startTime is used to track the amount of time that has passed since an event for Lerping. The variables hazSkrollin and izSkrollin are used by Update() to know, first, if this layout requires scrolling, and if so, whether the current display is actually scrolling at any given point in time.

The next group of variables, scrollMin… & scrollMax…, are used to set the length along the Y axis of the scrolling content window. The variables scrollMax… are the start points of each window and always start at 0.0. The scrollMin… variables define the end of the scrolling area. Currently scrollMin… needs to be set by hand after all aspects of the text have been finalized. This value is very difficult (impossible?) to calculate as it changes depending on the volume of type, the size of the window, the content and where the lines break and the type face and style desired. We will go over how to set these values in detail later.

(The variable family scrollMax… and scrollMin… are arguably named backwards, but are technically correct. This is because the window starts from 0.0 and scrolls down to a negative number, thus a scrollMin…)

Window pad defines the amount of space between the text and frame edge. Next is a Rect that defines frame size & placement in the screen. There should be one Rect defined for each desired window shape. These values are passed to CreateWindow() when the container window is created.

Penultimately, there are a group of containers for the current created window, the current window size and current window style.

Lastly there is a group of functionally irrelevant booleans for the button window example. These have no bearing on the operation of the scrolling example..

The Meat of It – OnGUI()

Let’s jump ahead to OnGUI().

(I’m not going to go into detail on cosmetic issues like the backdrop or the GUI Skin. I will assume that you understand these issues. If you don’t, leave me feed back and I’ll address them in another post.)

The menu system is setup as a switch/case to look at the state of the Start Menu, using StartMenuLevel. When a button is selected the container variables we discussed are assigned in detail for the chosen text/view, and the StartMenuLevel state is changed:

if (GUI.Button (Rect (10, 175, 100, 35), "Baba Yaga")) {
   hazSkrollin = true;
   scrollMax = scrollMaxBaba;
   scrollVector = scrollMaxBaba;
   scrollMin = scrollMinBaba;
   windowText = babaText;
   windowRect = babaWindowRect;
   windowStyle = "babatext";
   menuLevel = StartMenuLevel.Baba;
}

We tell the system that the next view will use scrolling, we set the scroll max & min, we set the text and we set window style. Lastly, we change our case to a new StartMenuLevel.

The change of StartMenuLevel from StartMenuLevel.Start to StartMenuLevel.Baba means we now look at:

case StartMenuLevel.Baba:

This case, like all the other specific scrolling text views, has the same layout*. Ignoring the labels and buttons, first you create the new text window frame, then you check to see if the content window is scrolling (isSkrollin). If the content window is scrolling, then position & display the scroll vector icon “iconVector”.

* “Buttons” creates a specific window with a GUI Group, rather than text, so calls a different function that contains a GUI.BeginGroup().

The important line to look for here is:

createdWindow = GUI.Window (0, windowRect, CreateWindow, "");

This sets up the base window frame. The official reference for GUI.Window is here:

http://unity3d.com/support/documentation/ScriptReference/GUI.Window.html

Now if you move down to CreateWindow (), you will see that this is a very simple function. The size and position of the window frame was set up in OnGUI() with windowRect. Here CreateWindow() creates the content. This is a generic function that is defined by the container variables we set up when we selected our StartMenuLevel case, so each case (except “Buttons”) uses this same function . Looking at CreateWindow(), it simply contains one Label:

GUI.Label (Rect (windowPad, scrollVector, (windowRect.width - (windowPad * 2)), scrollMin), windowText, windowStyle);

This label is the content inside the window frame. This is our text. The label has a Rect, a content and a style.

http://unity3d.com/support/documentation/ScriptReference/GUI.Label.html

The Rect is defined by as Rect(x,y,width,height), or in this case (pad, the current scroll vector, the frame width – pad * 2, the length of the text). The content is the container variable windowText, which has been set to the current text String for the view we want. The style has been set to the style we chose for this view.

What is important to note is that the origin y is defined by the variable scrollVector. The variable scrollVector is defined by Update() each frame, which we will look at next. The variable scrollVector starts at 0.0, but each frame Update() looks at touches, the previous state, the margins, margin pressure and resistance and recalculates scrollVector. As OnGUI() is called every frame, each frame this window is rebuilt, with scrollVector in mind. This makes the text label inside the frame window scroll up and down in response to the user’s touch.

In the “Buttons” case, CreateGroupWindow() is called rather than CreateWindow(). If you look at CreateGroupWindow(), you’ll notice that the only real difference is the content. CreateWindow() has only one line used to define the single text string content. CreateGroupWindow() creates one set of contents, but that content is a GUI Group.

http://unity3d.com/support/documentation/ScriptReference/GUI.BeginGroup.html
and
http://unity3d.com/support/documentation/ScriptReference/GUI.EndGroup.html

This GUI Group defines a number of buttons that are influenced by and set PlayerPrefs. As opposed to straight text, this is a way that you can have scrolling GUI content that is graphical and interactive. Currently these are for example purposes only, and the code does not properly set or retrieve information from PlayerPrefs

The Meat of It 2 – Update()

We’ve seen how each case defines a window frame and a window content – either a text Label or a graphical Group. We know that the content is build based on y value of scrollVector, and that scrollVector is defined by Update() each frame.

So – let’s see how.

Jump down to Update().

First, Update() is trapped with an if() so that it only calculates scrollVector is the current state “hazSkrollin”, otherwise Update() does nothing.

Then there is some business to do. Save the previous scrollVector as savedScrollVector. Set resistance to 1.0 and marginPressure to 0.0. This essentially neutralizes their values.

The next step is to check to see if the scroll vector is within the ‘normal’ scroll area or whether is has been pushed out of bounds into the margins. The “normal” scroll area is defined by scrollMax (0.0) and scrollMin (a value set by the user as the end of the text field). The size margin beyond the “normal” scroll area is set by scrollMargin.

If the scrollVector is out of bounds, then calculate the resistance and the margin pressure. The variable resistance is simply a Lerp from 1.0 to 0.0 depending on how far the scrollVector has pushed into the margin. Normally, when looking at Lerp:

http://unity3d.com/support/documentation/ScriptReference/Mathf.Lerp.html

static function Lerp (a : float, b : float, t : float) : float

… Normally, when looking at Lerp, t : float is usually time. Not so in this case. What you want to Lerp is the value of resistance depending not on time, but how far you are pushing out of bounds into the margins. Those margins are set by the user defined variable scrollMargin. The math works out like this: With Mathf.Lerp(), “t= 0 returns from. When t = 1 return to. When t = 0.5 returns the average of a and b.” Looking at the first case:
if (scrollVector >= scrollMax) {
    resistance = Mathf.Lerp (1.0, 0.0, Mathf.Abs ((Mathf.Abs (scrollVector) - scrollMax) / scrollMargin));
   ...
}

… this if() only triggers if scrollVector >= scrollMax, assuming that the margin is set to 50, the math would be 0.0 – 0.0 / 50 = 0.0. So resistance would be 1.0, as t = 0.0. If you were to push the scrollVector beyond the bounds into the margin, scrollVector would approach the value of scrollMargin. When scrollVector reaches the value of scrollVector, you would get 50 – 0.0 / 50 = 1.0, so resistance would be 0.0, as t = 1.0.

Now we look at the variable marginPressure. The key to marginPressure is 1.0 – resistance. This is then multiplied by marginSpringiness and Time.deltaTime, as a way to control the spring behavior, but if resistance is 1.0, then marginPressure is multiplied by 0.0 and there is no marginPressure. With a resistance of 1.0 and a margin pressure of 0.0, you have no effect. With a resistance of 0.0 and a margin pressure of 1.0, the marginPressure is high, but the scrolling content is essentially clamped by the resistance. However, marginPressure is only used when there are no touches.

Now, before we look in detail at how resistance and marginPressure are used, let’s look at the user’s touches.

The next two sections are concerned with touchCount. Is the user touching the device? If the user *is* touching the device, has the touch moved? If so, move the scrollVector. If the user is *not* touching the device, then calculate glide or coasting. Ultimately, in these two sections, we are looking for a calculation of scrollVector to set the window scrolling movement with.

In the first section, if the user has touched the device, and the touch has moved, then a change in touch position is recorded. This is modified by touchSpeed, to regulate behavior, and resistance, in case the touch has pushed the content out of bounds. As this value is a product, if resistance is 1.0, then it has no effect. On the other hand, as the scrollVector pushes into the scrollMargin and the value of resistance drops towards 0.0, the amount of movement diminishes towards 0.0 as well. When it reaches 0.0, or the end of the margin, there will be no further out of bounds movement in scrollVector. (Somewhat unnecessarily, the value of scrollVector is clamped within the scrollMargins anyway, “just in case” with “scrollVector = Mathf.Clamp…”.)

In the second section, if there are NO touches, glide or coasting is calculated.

The first part looks at the change in y. This is what allows the user to flick the window with their finger and it continues to scroll, slowing down over time and potentially bouncing against the margins. First look at existing scroll speed (or the speed from the last frame) and Lerp it towards 0.0 based on the change in time * coastSpeed * resistance. Here again, coastSpeed is to regulate behavior, and resistance is in case the touch is out of bounds – resistance being 1.0 has no effect and 0.0 reduces movement to 0.0.

The second part applies the change in y to calculate a new scrollVector. This is where marginPressure comes into play. You don’t have marginPressure when the user is touching the screen. The user can scroll the content into the margin until resistance reaches 0.0 (and it’s clamped anyway), stopping the content from going passed the end of the margin. It’s when the user is no longer touching the device that marginPressure pushes the content back within the boundaries. This is done simply by adding marginPressure to the current scrollVector + deltaY. This works whether the user has pushed the scrollVector into the margin, or the speed of the scrolling content has coasted into the margin.

Finally, the variable isSkrollin is calculated. This indicates to OnGUI whether the scroll vector icon should be drawn. This code will set isSkrollin to true if either the content is scrolling OR if the user has a touch on the screen.

How do I make it work?

This is relatively easy, especially if one understands that base code behind it. There are some tricks to this project, however.

As mentioned in the overview there are some limitations to this implementation. As such, there are still a few elements that are not fully genericized and may need to be manually adjusted to suit your project.

Return to Unity-iPhone and select the default main camera with the script StartSkrollin.js attached.

From the bottom up, looking at the exposed variables in the inspector, the various Window Rects can be adjusted to suit your needs. These are standard Unity Rects.

http://unity3d.com/support/documentation/ScriptReference/Rect.html

The window pad can be adjusted to suit your needs. Currently, the CreateWindow() code puts the pad once on the left side and twice on the right to accommodate the scroll vector icon. (Arguably there could be pad applied to the top and bottom as well if necessary…)

Next are the scrollMin… values and the exposed scrollVector. The scrollMin… values need to be set by hand with the help of scrollVector. Let’s return to this at the end.

The values for scrollMargin, coastSpeed, touchSpeed, and marginSpringiness are relatively arbitrary, but the current values seem to work fine and they are good simple numbers as well.

  marginSpringiness = 300
  touchSpeed = -1.0
  coastSpeed = 0.5
  scrollMargin = 50

Ultimately you need to set the scrollMin… values. You should set the scrollMin… values to a very large number. A value like 2000 should be enough. If not, just up the value until it is larger than the volume of text that you have, considering type face and style.

Next hit play and navigate to the view/case/state who’s value you are going to set. Now using the Unity remote, scroll down until the end of the text is where you want it aesthetically. Note the current exposed scrollVector value. Stop playing the game, and enter that number in the scrollMin… value. Save your project.

Repeat this last step until all of the scrollMin… values are calculated.

This may seem to be a bit of a manual kludge, and I think it is. On the other hand, it’s very quick to do and I have not found an easy way to calculate the scrollMin… value efficiently considering frame size, window size, type volume, type face & type style.

The Future…

There are some issues that I would like to resolve if I get the chance, but that will wait for a project that needs them.

I feel that there needs to be some more finesse with the window pad value, especially at the top and bottom of the window.

I feel there needs to be more finesse done with the scroll vector icon, especially making it feel more “Apple-like” by scaling the icon along the y-axis depending on volume of text/window size and margin pressure.

The current scroll vector icon is manually adjusted for in the code, and could be genericized to take a scroll vector icon of any size and shape.

Currently, Update() looks at then entire screen for scrolling the text and does not check the touch position against the window who’s content is being scrolled. For just one piece of text per view/state, it’s relatively unimportant, but users should be aware that this code is indiscriminate when looking for touches. This could easily be solved by checking against the value of the rect containing the text on the screen.

Now, I don’t consider myself any code guru, and I have received a lot of help form the Unity Community, so I see this as a way of giving back a tiny bit. If anyone sees any improvement to this code, let me know and I’ll incorporate it here and in the example project.

No Comments

Leave a Comment

Please be polite. We appreciate that. Your email address will not be published and the required fields are marked.