Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
Unity 3D how-to for supporting accelerometers, touch screens, & keyboards, using dynamic fonts to make resolution-independent UIs, gathering input with no keyboard, and build / implementation settings for iOS & Android.
Part 3 of this blog series is going to be a fun one because we're going beyond PC/Mac and beginning to support iOS and Android! Unity 4 made this possible by altering the basic version of the license for these platforms to be free (THANK YOU!). I learned a lot through this process and wanted to share it with all of you.
We will be building on Part 1 and Part 2 in this Unity 3D tutorial - so if you haven't read them yet, head on over there first. One thing you will notice here is that I've dropped the JavaScript that we covered before. I found that for what I am doing, the syntax is so similar that it is trivial to convert from one language to another. So, I'm just including my language of choice for Unity which is C#.
Changes to the game from the conference version detailed in Parts 1 & 2
Supporting accelerometers, touch screens, and keyboards
Using dynamic fonts to make resolution-independent UIs that work on handhelds and traditional computers
Gathering input when no keyboard (virtual or otherwise) will work
Unity build settings for iOS and Android
Implementing settings
The code has been updated on GitHub, so you are free to download it and alter it in any way you want to make your own game!
The game has been tested successfully on:
PC
Mac
iPad (4th Gen)
iPhone 5
Kindle Fire (1st Gen)
Samsung Galaxy S2
Samsung Galaxy S3
Samsung Galaxy Nexus
HTC Desire
DroidX
The conference version of the game was primarily created for marketing purposes. Therefore, before the user played the game, a screen would appear asking for information. The game would also only save the player's score the first time they played it. Neither of those work very well for general use and game playing fun! So, in this version, the information screen is gone and as long as the score is in the top 10 it will be saved. Also, instead of all the marketing information it now just asks for initials (loosely defined, as I threw in some punctuation characters and numbers just to make it interesting).
When the free versions of the mobile licenses were first announced, I wanted to see how close my game could come to running on mobile with no changes at all. So, I brought up the build settings and compiled for Android, then copied the file to my Samsung Galaxy S3 (SGS3) to try it out. I tapped the Start Game button, then on the entry fields and entered my information. So far so good!
Everything was fine until the screen that stated "Press Space to Start." But of course, there was no space bar and no way to get to the virtual keyboard. "This should be easy" I thought... and went to figure out how to make a tap act like a key press. Looking at the docs for Input.touches along with TouchPhase.Stationary led me to my first attempt at using touch as a keypress. I used Stationary because I thought "yah I only want it to tell me if the user's finger hasn't moved."
I also hunted around for a generic way to know if the code was running on a mobile device. The most useful thing I could come up with was to test for an accelerometer to avoid getting into checking operating system strings and getting too specific. While this may not account for 100% of the mobile devices out there, it has worked for everything I have tried, and in lieu of something like "supportsTouch" it is close enough for me.
bool touched = false; bool space = false; if (SystemInfo.supportsAccelerometer) { foreach (Touch touch in Input.touches) { if (Input.touchCount == 1 && touch.phase == TouchPhase. Stationary) { touched = true; break; } } } else if (Input.GetKeyDown(KeyCode.Space)) { space = true; } if (touched || space) { // fire weapon }
This worked...sort of! It turns out that Stationary means "keep returning yes for every frame while the user's finger is on the screen and hasn't moved" which meant I had accidentally implemented autofire without meaning to. I ended up using that as a power up after the user scores 10,000 points. To limit the firing to "once per tap," use TouchPhase.Began instead.
Okay, so touch is working for fire. Simply testing for Input.touchCount = 2 gave me a way to support turning on the shield where 'S' did before. The other "touch" addition was that if 3 fingers are held down on the main screen, the "Reset Data" button will appear. This is instead of typing "key" to show it like it did before (that still works in the keyboard version).
Now then, we can fire and have a shield, but we still can't move. That's where the accelerometer comes in. I wanted my transformation position to end up the same whether the user was using tilt or the arrow keys. This was accomplished by first checking to see if the keyboard is in use (discussed later in Settings). If it is not, then ask Unity what the current X and Y acceleration is. These values change based on how tilted the screen is from level on the horizontal and vertical axes. The farther the tilt, the higher the value.
After some experimentation, I determined that the default values didn't provide enough sensitivity. I played around with the "fudge factor" until 10.0f felt about right (I've bolded this value in the code below). This value probably should be exposed to Unity and also made a Setting so the user can adjust the sensitivity, but after testing with 10 different devices all of them felt about the same with that value. Further, this value made it feel similar to using the keyboard arrows.
So anyway, once the X and Y transform values were normalized, I told the transform for the player to move to that position, then clamped the position to the playfield so the user couldn't move off of the screen.
// From scriptPlayer.cs lines 38-61 void Update () { float transH = 0; float transV = 0; Vector3 dir = Vector3.zero; if (!useKeyboard) { dir.x = Input.acceleration.x; dir.y = Input.acceleration.y; transH = dir.x * (playerSpeedH + 10.0f) * Time.deltaTime; transV = dir.y * (playerSpeedV + 10.0f) * Time.deltaTime; } else { // calculate x and y movement based on time transH = Input.GetAxis("Horizontal") * playerSpeedH * Time.deltaTime; transV = Input.GetAxis("Vertical") * playerSpeedV * Time.deltaTime; } transform.Translate(transH, transV, 0); float z = transform.position.z; // ensure movement does not go outside playfield transform.position = new Vector3(Mathf.Clamp(transform.position.x, playerPosHMin, playerPosHMax), Mathf.Clamp (transform.position.y, playerPosVMin, playerPosVMax) , z);
With this code in place, the player can now move, fire, and invoke the shield. With this simple game that's all there is to change for the game to be playable.
Unity is an awesome cross-platform tool for game development! These changes only took a few hours to get going thanks to great documentation and a few Google searches for examples. When you get stuck, remember that someone else has too, and the answer is likely only a search or two away because of the fantastic community using and sharing information about Unity. With gameplay fixed, I next turned my focus to the UI, because there was definitely something amiss...
Designing a UI using basic Unity is a bit of a challenge. There are definitely libraries in the Asset Store that can help, but for this simple game I wanted to use only base Unity to remove the reliance on any libraries that would cost money. When I did the UI for the conference, I only had one screen resolution to worry about. Now, I had an unknown range of resolutions.
When testing the game, I noticed that the font was very small on the mobile devices, because they had very large screen resolutions in a very small display. So, I did what I always do in these situations, I started searching Google to see what other people had done before me.
In this case, I found solutions geared for Unity 3, which did not support dynamic fonts in mobile. I remembered reading about dynamic fonts for mobile in Unity 4, but I couldn't find any examples of it. Finally, I tried this script which uses Screen.dpi to determine the font size for a particular screen. I'm sure it would work great if Unity could return a value other than zero for displays that do not report their DPI. I needed something that would work on any platform and any screen, so that would not cut it.
Instead, I chose to base my font size on the screen resolution, because Screen.width always returns the width of the current window the game is running in, or the screen width if it is fullscreen. I only base the font size on screen width because I always run the game in landscape. Below are two screenshots to display the problem that I am solving.
The first screenshot is how the game looked without dynamic fonts. Squeeze this picture onto a mobile device and you can get a feel for just how small the default fonts rendered on the screen. The buttons were so small it was tricky to choose the right one.
The second screenshot shows what the screen looks like with dynamic fonts. I could fix up the vertical size of the box a little more, but the point is that the display is now consistently large no matter what screen resolution is being used.
The calculation for font size is based on the width of the screen times another fudge factor (which again could be exposed to Unity and made a Setting for the user to control). I tried to use the same code for laptop/desktops and mobile, but due to the large discrepancy in DPI between the two, I ended up using different factors. If the font size ends up being less than 5, I force it to 5.
These two fudge factors have a lot of power, because changing them just a little scales the UI a lot. Be sure to try it out to see what I'm talking about. Keep in mind that only by deploying to devices can you see the dramatic difference due to the DPI differences.
This is the first method in my class scriptFont.cs:
public static int GetSizeFromResolution() { int size = (int)(Screen.width * .016); if (SystemInfo.supportsAccelerometer) { size = (int)(Screen.width * .024); } if (size < 5) size = 5; return size; }
Now we know what font size to use, but how do we tell Unity about it? Well...in my experience the answer is "it depends" and I'll show you why. Unity now supports using XML-like tags to specify properties about a font.
Let's see how that works by looking at another method in scriptFont called MakeString(). I created this method as a shortcut to building the tagged string for displaying the font I wanted. I've shortened the method here to conserve screen space, but I always recommend using brackets around all if statements to keep from unintended side effects such as the example below, where callMethod2() is always called no matter what "blah" is set to. However, due to formatting, it is very difficult to see the problem and it can be very frustrating to track down depending on what the code is doing.
BAD CODE:
if (blah == true)
callMethod1();
callMethod2();
do something else...
So, don't do it, okay? :-) Anyway, the following method is an easy shortcut for making a tagged string a certain size, a certain color, bold, and/or italic. I also have other shortcut methods that leave out various parameters.
public static string MakeString(string text, int size, string color, bool bold, bool italic) { string newText = ""; if (color != null) newText += ""; if (bold) newText += ""; if (italic) newText += ""; newText += text; if (italic) newText += ""; if (bold) newText += ""; if (color != null) newText += ""; newText += ""; return newText; }
Cool, now we can make interesting strings, but two more gotchas are waiting! First, in GUIStyle is an instance variable called richText. If it is not set to true, the XML won't work! Second, GUIStyle can only be used inside of OnGUI. If you try to use it outside of that method Unity will complain.
There is a style for each type of UI control. In most cases, we just want to augment the style with our font information and go with it. However, for Label we want to create a brand new style and use that. I tried a variety of things to use the existing style, but it wouldn't accept my changes. This might be a bug in Unity or an error on my part, but when I used a brand new style it resolved the issue. So let's bring this all together with one of the UI screens to see how it works. The easiest is the actual gameplay screen, where the score and other information are shown in the upper left corner. The following is from scriptSceneManager.cs.
void OnGUI() { GUIStyle style = new GUIStyle(); style.richText = true; style.normal.textColor = Color.white; int size = scriptFont.GetSizeFromResolution(); int y = size; GUI.Label(new Rect(size, y, size * 10, size), scriptFont.MakeString ("Time : " + gameTime), style); y += size + 1; GUI.Label(new Rect(size, y, size * 10, size), scriptFont.MakeString ("Score : " + score), style); y += size + 1; GUI.Label(new Rect(size, y, size * 10, size), scriptFont.MakeString ("Lives : " + lives), style); if (!shieldOff) { y += size + 1; GUI.Label(new Rect(size, y, size * 10, size), scriptFont.MakeString("Shield : " + shieldStrength), style); } if (score >= bonusScore) { y += size + 1; GUI.Label(new Rect(size, y, size * 10, size), scriptFont.MakeString("AUTOFIRE", "red", true, true), style); }
I'm setting the text color to white because the background is black. Notice that the position and size of all of the UI elements are determined by using the calculated font size and a few offsets for space. Having the font size turns the screen into a virtual set of rows and columns to use instead of pixels. All strings utilize the MakeString method to build the XML-like text for the controls to use, making it so we don't have to see all those messy tags. Creating a new style works for Label, but for Button and Box we have to take a different approach and alter the built-in style. Why? Hmm, I wish I could answer that logically, but I figured it out from coding and testing. This code is from scriptScreenMainMenu.cs.
void OnGUI () { int size = scriptFont.GetSizeFromResolution(); GUIStyle boxStyle = GUI.skin.box; boxStyle.richText = true; boxStyle.normal.textColor = Color.white; GUIStyle buttonStyle = GUI.skin.button; buttonStyle.richText = true; buttonStyle.normal.textColor = Color.white; buttonStyle.fontSize = size;
GUI.skin contains the built-in styles, which we are free to retrieve and alter however we want. For Box, it is just a matter of turning on richText and setting the textColor. Buttons were more stubborn. Despite everything I tried, unless I set the fontSize in the Button's style it would not work. It always seemed to ignore the richText setting.
Sometimes, devices or operating systems do things that make no sense at all. I ran into two situations like that and I found other developers that have run into these as well. Just to spread the wealth, one was iOS and the other an Android device, so understand that I'm not favoring one environment over another - I would just like them all to work equally well!
iOS - with Android devices, you can pair a bluetooth keyboard (I used a Zagg), choose the keyboard setting (see the next section) and play using the keyboard instead of the touch screen & accelerometer. iOS on the other hand refuses to relay the keyboard events to Unity, so this does not work. I don't know if it is iOS or Unity's fault, but the end result is the same - iOS users are locked out of this useful feature.
So, in these cases, what recourse is there? For iOS the user is relegated to just using touch, but the HTC problem is more severe because even the virtual keyboard doesn't work. For my high score screen I wanted to allow the user to enter their initials (this is a change from the conference version which asked for more information). With no keyboard, I decided that buttons would be a better option. Since this was a retro-style game it worked to have a retro-style high score screen. Other games that rely more on the keyboard may need to explore other options for getting around this limitation.
Even with the choice of using buttons, I still have to make several iterations over the design in order to create a UI that was easy to use with touch. For example, don't put the current initials below the buttons, or the user won't be able to see them because their finger will be in the way! Also make the buttons big enough that the user can feel confident about which button they are pressing. Finally, I spaced the SAVE button down far enough that it wouldn't accidentally be hit.
When I realized that there was an option for Android devices to use a paired bluetooth keyboard, I was left with a quandary. I couldn't find anything in Unity for detecting the presence of a "real" keyboard, so I couldn't automate it. Many (if not all) games have a Settings area for configuring environmental factors such as sound effects, music, and sensitivity, so I decided this would be a good option for supporting the keyboard.
As with the high scores, PlayerPrefs is the place I chose to store this setting. I chose the key "keyshot.input" to store the setting, with simple "keyboard" or "touchscreen" strings for the value. If nothing has been set for the value, the default is "keyboard" if no accelerometer is detected, or "touchscreen" if one is. After adding a Settings button to the main menu, it was simple to add another screen and a few more buttons to support this functionality. It isn't the prettiest UI, but it gets the job done :-)
I'm including screenshots of my build settings for iOS and Android, because there are a few things to keep in mind when building for these platforms. Notice the "Use 32-bit Display Buffer" and "Use 24-bit Depth Buffer" are unchecked. When I had these checked, I had screen redraw issues and it looked terrible. I set the default orientation to landscape left and also set this on each of my scripts like this:
void Start() { Screen.orientation = ScreenOrientation.LandscapeLeft; }
Next, I chose the lowest level of the Android API I could so that devices like my son's DroidX would work. I also chose OpenGL ES 1.x for the same reason. I thought it was interesting that I could not disable Internet Access even though I knew I didn't need it.
You will need to install an ADT (Android Development Toolkit) to build with. I used the one here and followed the instructions. Then you just tell Unity where the ADT is when it asks. Even though Build Settings has a "Build and Run" option for Android, it never seemed to be able to find my devices. Instead, I connected my Androids as mass storage devices, copied the APK to the download folder, then used either the built-in file app (Samsung) or installed ES File Explorer to find the app and install it. You will have to enable installing apps from unknown sources to allow installing the game.
For iOS, I chose iPhone + iPad for the Target device and the native resolution since this app supports them all. Note that to compile for iOS you have to use a Mac, have the latest version of XCode installed, be an Apple developer, and have your app and device registered.
Once that ordeal is finished, from Unity you can choose "Build and Run" and Unity handles XCode all by itself and installs your app onto the device. This is super convenient and saves time switching to XCode to finish the builds.
Unity provides a wonderful environment to build games (and many other applications!) for every major platform out there, with more coming all the time. With all of the community support, along with Unity's excellent documentation, it is possible to achieve the goal of creating games for people to enjoy.
In the past, I spent months writing game engines from scratch (one was a 90 degree turning dungeon crawler on a Commodore Amiga, another was a Doom-style ray caster in Windows 95 with a Pentium 60), along with a bookshelf full of books on writing game engines, only to have new technology render them obsolete. Unity provides a platform in which they worry about keeping up with technology and I worry about creating the best game I can. I am truly excited about moving on to create a multiplayer game with my son that we have designed together. I am confident that with Unity having our back we will be successful in implementing a genre-changing fun experience that many people will enjoy.
Thank you so much for following along with my blogging. If you have questions please contact me. I'm learning along with everyone else but I will help in any way I can!
-- John Boardman, [email protected]
(Originally posted on the Keyhole Software Team Blog on June 24, 2013.)
Read more about:
Featured BlogsYou May Also Like