Titanium for iOS: Faking a Lightbox style Modal Window in your App

This is an example of how to do a Lightbox style modal window (a View posting as a Window) in a Titanium 2.x SDK iOS app.

Why bother?

Sometimes it’s useful for in app messaging, when you want to avoid opening an actual Window via Ti.UI.createWindow and stay in the same execution context as recommended by the best practices section of Titanium docs. You can also use this approach when the look & feel of the default iOS alert window just isn’t your style.

Sample usage

Here’s how you’d use it:

   var FakeModalWindow = require('/ui/FakeModalWindow');	
  
    var someText = "OK, some new text. Blah Blah Blah Blah Blah . \n\nThis new one here. \n\nYou new copy. \n\n Some More stuff and stuff.."; 
  	var fmw = new FakeModalWindow();
	self.add(fmw); 		
		
	//to change the copy in the fmw's TextArea: 
	fmw.fireEvent('setCopy_fake_modal_win', {newCopy:someText}); 
			
	self.addEventListener('close_fake_modal_win', function(e){		
		fmw.hide();		
	});
	
	self.addEventListener('open_fake_modal_win', function(e){		
		fmw.show();		
	});	

Source

Here’s the actual FakeModalWindow.js module code:


module.exports = function FakeModalWindow() {

	var theCopy = "Lorem Headline" + 
		           "\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "+
				   "\n\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + 
				   "\n\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +				  
				   "\n\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "; 
	}
	
	// some variables we'll need
	var OS = Ti.Platform.osname;	
	var SLASH = Ti.Filesystem.separator;
	var imgPath = SLASH + 'images'; 	
	
	//create component instance
	var self = Ti.UI.createView({
		width: Ti.UI.FILL,
		height: Ti.UI.FILL,
		backgroundColor:'#65000000',
		zIndex : '50',
	})	
	
	/**		
	 * Main container w/ white background & rounded corners
	 */	
	var content_win = Ti.UI.createView({
		backgroundColor:'#ffffff',	
		layout: 'vertical',
		width: (OS==='ipad') ? '542dip' : '270dip',	//'542dip', //
		height: Ti.UI.SIZE, //'526dip' (OS==='ipad') ? '526dip' : '15dip',	
		borderRadius: (OS==='ipad') ? 6 : 2	
	});	
	
	/**		
	 * Horizontal container 1
	 */	
	var title_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#FF00FF',						
		//borderWidth: 1				
	});	
	
	/**
	 * Horizontal container 2
	 */	
	var content_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#FF0000',
		//borderWidth: 1	
	});		
	
	/**
	 * Horizontal container 3
	 */	
	var footer_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: (OS==='ipad') ? '20dip' : '10dip',
		//borderColor: '#FF0000',
		//borderWidth: 1	
	});			
		
	/**
	 * Begin content pieces  
	 */
	var orange_bar = Ti.UI.createView({		
		backgroundColor: '#f37416',	
		width: Ti.UI.FILL, 
		height: (OS==='ipad') ? '57dip' : '26dip',	
		borderRadius: (OS==='ipad') ? 6 : 2,
		borderColor: '#white',
		borderWidth: (OS==='ipad') ? 5 : 2,
	});			
	
	var close_button = Ti.UI.createView({		
		backgroundImage: '/images/shared/btn_close.png',	
		left: (OS==='ipad') ? '485dip' : '5dip',	 
		width: (OS==='ipad') ? '37dip' : '18dip', 
		height: (OS==='ipad') ? '37dip' : '18dip' 	
	});	
		
	var body_copy = Ti.UI.createTextArea({
		//borderWidth: 2,
	    //borderColor: '#CCFF33',	    
		value: theCopy,		
		editable: false,
		textAlign: 'left',
		top: (OS==='ipad') ? '15dip' : '7dip',
		left: (OS==='ipad') ? '20dip' : '10dip',
		color: '#000000',		
		font: { fontSize: (OS==='ipad') ?'20dip' : '12dip' },
		width: Ti.UI.FILL,
		height: Ti.UI.SIZE 				
	});			
	content_win.updateLayout({height:Ti.UI.SIZE});
		
	orange_bar.add(close_button);			
	title_bar.add(orange_bar);		
	content_bar.add(body_copy);			
	contentWin.add(title_bar);	
	contentWin.add(content_bar);	
	contentWin.add(footer_bar);
	self.add(content_win);

	/**
	 * Event listeners 
	 */
  	self.addEventListener('setCopy_fake_modal_win', function(e){			
		body_copy.setValue(e.newCopy);				
		body_copy.updateLayout( {height: Ti.UI.SIZE } );				 		
	});	
	
	self.addEventListener('click', function(e){  // if the user interacts anywhere, close the modal window
		self.fireEvent('close_fake_modal_win');	
	});
		
	return self;
}  

Unity3D for iOS: “Apple LLVM Compiler 3.0 Error”… “iPhone_target_Prefix.pch’ has been modified since the precompiled header was built”

The Error

I upgraded to the latest minor version of Unity 3.5, specifically 3.5.5f. All was fine until I tried to create an Archive so I could publish an .ipa file from Xcode. Once I hit Product > Archive >, I got Build Failed with this error:

Apple LLVM Compiler 3.0 Error:
fatal error: file '/YourPathToProject/YourProjectName/Classes/iPhone_target_Prefix.pch' has been modified since the precompiled header was built

The Solution

After trying to Clean the project & a few other things, nothing worked. Finally, found this stackoverflow solution that fixed it. The Library folder path for the folder that had to be deleted was under
/YourUserName/Library/Developer/Xcode/DerivedData/{project name + gobly-gook}.

Unity3D for iOS: ParseInstance.GET(string) throws “InvalidCastException: Cannot cast from source type to destination type” for Date objects from Parse

The Error

//UniParse boiler plate:
var pObj = new ParseClass("/classes/Tournaments");			
string query = "...your query here..."; 	
ParseInstanceCollection MyParseList = pObj.List( query );			
while(!MyParseList.isDone) yield return null;					
ParseInstance[] items = MyParseList.items; 		

//get custom Date from a Parse.com Class (database table)
string MyDateFromParse = items[0].Get<string>("MyCustomDate"); 

The last line above throws this error because MyCustomDate is of type “date” in the Parse class:

/*
InvalidCastException: Cannot cast from source type to destination type.
ParseInstance.Get[String] (System.String key) (at Assets/Plugins/ParseClass.cs:238)
MyManager+<getMyCustomDateFromParse>c__Iterator15.MoveNext () (at Assets/Scripts/MyManager.cs:9)				
*/

The Solution

Read up on Data Types in Parse.com’s documentation.

Hashtable d = (Hashtable)items[0].Get<object>("MyCustomDate");
string MyDateFromParse = d["iso"];

Unity3D for iOS: Etcetera plugin, “error CS0103: The name `EtceteraBinding’ does not exist in the current context””

"Assets/Plugins/Etcetera/testSupport/EtceteraGUIManager.cs(38,38): error CS0103: The name `EtceteraBinding' does not exist in the current context"

The error poped up from this line inside EtceteraGUIManager.cs’s Start() method (Line 11):

EtceteraBinding.setPopoverPoint( 500, 200 );

The solution turned out to be simple: I forgot to set the project to the iOS Platform. Go to File > Build Settings, choose iOS and click Switch Platform.

If you open the EtceteraBinding.cs class in MonoDevelop (/Plugins/EtceteraBinding/EtceteraBinding.cs) before you switch your project to target iOS, you’ll notice that much of the code is grayed out. That’s because the entire class is enclosed in iOS Preprocessor Statements like #if…#endif:

#if UNITY_IPHONE
...
#endif

Once you switch your platform in Build Settings, the code inside the #if will have normal highlighting and the above error will go away.

Unity3D for iOS: Basic AR via the Free VUFORIA for iOS Plugin

Qualcomm’s dev site & docs for Vuforia

QDevNet’s version of Vuforia for iOS for Unity

  • Comes with Sample projects, each as a .unitypackage.
  • Doesn’t show up as a selectable package in the Project Wizard in Unity. You can still import it via Assets > Import Package > Custom Package.

Unity Asset Store version of Vuforia for iOS

  • Contains no sample projects/files.
  • The Vuforia package shows up Project Wizard when you do File > New Project in Unity.
  • After downloading Vuforia for iOS from the Asset Store, I started with a tutorial called “Getting Started – Unity Extension > Compiling a Simple Project” on QDevNet’s Vuforia section
  • The Asset Store version of Vuforia for iOS didn’t seem to have any sample usage files or demos, as of May 21, 2012. This part was a bit confusing but I was able to get it working after poking around on QDevNet.
  • Comes with empty StreamingAssets/QCAR and Editor/QCAR/ImageTargetTextures folders. This means when you try to follow the “Compiling a Simple Project” tutorial under “Getting Started – Unity Extension” here, and try to add the ImageTarget prefab to your Hierarchy as the tutorial shows, the Image Target Behavior script won’t show any public vars like “Data Set” or “Image Target”.
    The Unity Assets Store version doesn’t seem to have any sample Targets – 1. download a sample project from https://ar.qualcomm.at/ and copy/paste or 2. create your own using their online Target Management System as described here.

Random Notes on Creating Trackable Images (Image Targets) w/ Target Management System (TMS)

  • TMS lets you upload a photo & lets you know if it works for Vuforia using a 5 star rating system. Zero starts means the image is totally unusable. Once you figure out a descent 4 or 5 star rated image you can click “download selected trackables” and you get a .unitypackage with the necessary .xml file and visual assets. Double clicking on the downloaded .unitypackage imports these assets into the correct locations (assuming you have Vuforia for iOS already installed in Unity)
  • A trackable image needs to have “Sharp edges & high contrast”:
    “Example of image that provides no detection and no tracking… There are no features in this image because it lacks visual elements with sharp edges and high contrast. Detection and tracking are not possible.” The TMS didn’t like my first notebook image because of the spiral edge pattern at the top. I got this message: “Avoid repetitive patterns or motifs:”

    The system provided a link to a slide show of various images along with explanations on why they work well or not. That was helpful.
    This image got 4 out of 5 stars from TMS and ended up working well:

    An image that was rated 1 out of 5 starts by TMS produced a super jittery 3D model that did appear when I held my phone up to it from just the right angle but didn’t look great. Developer Guide > Trackables > Image Targets and Developer Guide > AR App Design Guide > Image Target Design Properties and Image Target Enhancement Tricks talk more about which images are acceptable.

  • 2MB is the max file size limit for a TMS uploadable image as of May 2012 – aka, a photo taken on an iPhone 4S & email at Actual Size might be too big. 

Licensing, etc

Unity3D for iOS: Switching From GUI to UIToolKit for Text & Menus to Improve Performance

UIToolkit is an open source Unity plugin specifically created for optimized performance on mobile devices (via reduced number of draw calls). Here’s a good summary of why Unity3D’s default GUI is not a good choice for mobile devices, key ones being:

  • Excessive draw calls…
  • Draws excessive processing power when using GUI, even worse with GUILayout… In-game GUI still requires high performance, and the overhead of calling GUI, or GUILayout are just too big to overlook even on today’s high-powered phones.

Setting Up

1. Download the .unitypackage from UIToolkit’s downloads page on GitHub.

2. I mostly followed the developer’s instructions, using a mix of:

  • “Setup” instructions on their Github page
  • the two intro videos
  • the code examples in the demo scenes that come with the package download (Github).

You can also try this quick “how to” from UnityAnswers. Just remember, to add UIToolkit elements via code you also have to create a public var in your script & use the Inspector to assign a one of the UIToolkit instances under the UI GameObject in your Hierarchy.

NOTE: don’t expect TweenLite or iTween style performance from UIToolkit’s built-in animation engine. Also note, UIToolkit-based UI can’t be animated with iTween, as far as I’ve seen.

Make the texture atlases for your buttons

Created 2 texture maps for the buttons containing Up and Down states for the 4 buttons using the free version of TexturePacker. The 2nd texture map had the “2x” added to each file name, for iPhone retina display. CORRECTION: When creating the individual graphic slices in GIMP or Photoshop, make sure to name the regular and “2x” versions of the same slice the same exact file name (no “2x”). This will ensure that the respective .txt JSON files generated by TexturePacker will have the 2 sizes named the same and as long as the retina sprite sheet .png file generated by TexturePacker has the “2x” in it’s file name, UIToolkit will grab the correct image size automatically.

Note: the free version of Texture Packer won’t allow you to efficiently size the image to minimize empty space.

UPDATE, 08.09.2012: Unity3D doesn’t allow TexturePacker Pro’s “Allow free sizes” checkbox to do it’s thing. It’s tempting to use it because it cuts the overall size of the sprite sheet but looks like Unity ends up flipping out and stretches the resultant image, forcing it into value it recognizes like 256, 512, 1024, 2048.

UPDATE, 08.10.2012: UIToolkit currently doesn’t support TexturePacker Pro’s rotated sprites (“Allow rotation” checkbox). Looks like someone tried to add that functionality but didn’t have time to finish.

Convert fonts to bitmaps (.png and .fnt files) for use with TexturePacker and Unity: AngelCode’s BMFont on Windows

Converted my fonts to Bitmap font files with .fnt extension. Hiero, the free bitmap font conversion tool for Mac has some limitations (like it doesn’t allow you to turn off antialiasing for small copy fonts). There’s a free Windows tool that does called Angelcode Bitmap Font Generator. Glyph Designer is relatively inexpensive alternative.

I first tried using AngelCode’s BMFont tool on Windows XP via Parallels and it generated these two .png & a .fnt files for my font:

Prior to Unity import, I renamed .fnt files to .txt.

Here’re some screenshots of Font and Export settings I used in Angelcode’s bitmap font tool (click to for larger image):

After importing the .png, .txt (.fnt) and .txt (JSON) files into an empty Unity scene and setting it up correctly, I got no errors in the Console and no fonts on screen (link to .unitypackage of this project).

I suspect the problem is in the font files and not in my UIToolkit or Project setup because I also included the Text demo files from UIToolkit in the .unitypackage above. When I tried to simply switch the font to the “prototype” example and it’s associated texture in Inspector, everything worked. It’s only when I switched the .fnt and texture from Angelcode that the text failed to show up. UPDATED, 04.09.2012: After taking a look at UIText.cs, I realized I was wrong – the problem was in the way I was calling the UIText constructor. For correct usage, see the mainMenuScript.js code example below.

Convert fonts to bitmaps: Hiero on Mac

Hiero ran fine the first time for the standard fonts version, however when I tried to change the settings for the “2x” version of the font it got super sluggish on me. I reinstalled the Hiero .jnpl numerous times, restarted my system, etc., to no avail. Hiero ran super slow 1 minute or so after launch on OS X 10.6.8, java version “1.6.0_31”, JRE build 1.6.0_31-b04-413-10M3623. UPDATE: after looking into it more, it seems like the main performance culprit with Hiero is the ASCII button under the “Sample Text” text box. When, instead of hitting that button, I manually copy/pasted in some of the extra characters I needed, the freezing & sluggishness issues didn’t happen.

The PNG created from Hiero had the text upside down, so I had to open the PNG in Photoshop & do a Edit > Transform > Flip Vertical to fix that.

Still same problem – no Console errors and no text showing up on screen in Unity. Here’s the .unitypackage that uses the Hiero fonts. Now fixed, see below.

GlyphDesigner is a good not-free alternative to Hiero on Mac. It’s around $30.

Make texture atlases for your fonts with TexturePacker

Created 2 more texture maps for Fonts using the .png files created by your bitmap fonts program. One for each device resolution (standard & retina), using Unity’s “2x” naming convention for the retina sprite sheet’s name. Basically, you need this step to generate the JSON formatted .txt files that will serve as a map for the .png that contains all of the font characters.

NOTE: When creating the individual graphic slices in GIMP or Photoshop, make sure to name the regular and “2x” versions of the same slice the same file name. This will ensure that the respective .txt JSON files will have the 2 sizes named the same and as long as the retina sprite sheet .png file generated by TexturePacker has the “2x” in it’s file name, UIToolkit will grab the correct image size automatically.

If you used Hiero – your font .png files will be flipped upside down. Just open them in Photoshop & do a vertical flip.

Import .txt (.fnt), .png (font texture atlas) and .txt (JSON texture atlas info) into a Unity project

Here’s a working UIText .unitypackage for iOS and one w/ Build Settings changed to Android (still developed on Mac). NOTE: If you’re on Windows, the iOS sample might not import because you’re missing the iOS SDK (blame Steve Jobs). I included the Android version – hopefully it will work for Windows users (let me know in the comments, either way). This sample is using two UIToolkit objects under one UI parent, including a UIText instance. Compare the mainMenuScript.js with it’s counterpart in earlier unsuccessful attempt for iOS.

If the font shows up with vertical bleeding at the top of the letters and the bottom of the letters being cut off, here’s an easy fix. Open the gunplayDemoSheet2x.txt (JSON) file and change the “y” value of the “frame” from 0 to 2.

"frame": {"x":0,"y":2,"w":1024,"h":321},

mainMenuScript.js

#pragma strict

var textToolkit2:UIToolkit;
var buttonToolkit:UIToolkit;

function Start() {

	//this gives no errors BUT fonts don't show up...: 
	//var text = new UIText( textToolkit2, "gunplayDemoSheet", "gunplay.png" ); //UIText(obj, "name_of_texture_in_Resources_folder", 'line 3 in .fnt file')	
	
	//set Game panel to iPhone 4G Tall (640x960): 
	var text = new UIText( textToolkit2, "gunplay", "gunplay.png" ); //UIText(obj, "name .fnt (.txt) file in Resources folder", 'line 3 in .fnt file')	
		
	var x = UIRelative.xPercentFrom( UIxAnchor.Left, .1f ); //0.1f = 10% from Left
	var y = UIRelative.yPercentFrom( UIyAnchor.Top, .05f ); //0.5f = 5% from Top
	text.addTextInstance( "Finally some \ntext. Oops, there's \nbleeding. Need to \nfix the \ntexture map.", x, y );	
	

	var newGameBtn = UIButton.create( buttonToolkit, "playAgainUp.png", "playAgainDown.png", 0, 0 );
	newGameBtn.positionFromTopLeft(.5f, .2f); 	
	newGameBtn.highlightedTouchOffsets = new UIEdgeOffsets( 30 );

}

Here’re the settings screenshots to hook everything up (NOTE: In the screenshots below and code sample above I’m using a True Type font called Gunplay; in the AngelCode and Hiero screenshots I was using Gotham – sorry, if that’s confusing, I haven’t had the time to update those.):

Here’s an example of the UI parent object set up with more than one child. UI > “UIToolkit – buttons” object:

UI > “UIToolkit – text” object:

An iOS Port of a Unity3D Asteroids Example, Part 2: Saving Game Settings on Your Device

Building on Part 1, I added some basic game settings storage code using two C# classes. I used a great (faster than Unity’s default) PlayerPrefs class from PreviewLabs and a GameSettings class that talks to it. I found the basic idea for the GameSettings class in the first few videos of this useful 6-part tutorial.

GameSettings.cs & PlayerPrefs.cs

I added a simple PlayerPrefs.GetAll() method to the PreviewLabs.PlayerPrefs class. It simply checks if there are any prefs saved and returns a Hashtable containing them if they exist. If not, it returns null.

The GameSettings class is hooked up to a container GameObject called __GameSettings in the Hierarchy the “mainMenu” scene for easy access by other scripts in this scene.

GameSettings.cs

using UnityEngine;
using System.Collections;
using PlayerPrefs = PreviewLabs.PlayerPrefs;

public class GameSettings: MonoBehaviour {
	
	void Awake(){
		DontDestroyOnLoad(this);				
	}	

	public void SavePlayerData(int myScor, int myLives, bool myRes){ //(dynamic Obj)
		
		/* 
		 * Since GameSettings is inside Plugins folder & is compiled first, 
		 * it doesn't have a reference to playerScript.js which is in Scripts folder
		 * http://www.41post.com/1935/programming/unity3d-js-cs-or-cs-js-access
		*/
		//GameObject go = GameObject.Find("Player");
		//playerScript psClass = go.GetComponent<playerScript>(); or //Obj psClass = go.GetComponent<Obj>(); ??
		
		PlayerPrefs.SetInt("PlayerScore", myScor);	
		PlayerPrefs.SetInt("PlayerLives", myLives);		
		PlayerPrefs.SetBool("GameResumable", myRes);
		
		Debug.Log("GameSettings::SavePlayerData() called, GameResumable = " + PlayerPrefs.GetBool("GameResumable"));
	}
		
	public Hashtable LoadPlayerData(){		
	
		return PlayerPrefs.GetAll();
		
		/*	for larger data: 
							  		   * 
		foreach(String str in someCollection){
			if(PlayerPrefs.HasKey(str)) {
				PlayerPrefs.GetInt(str);
			}		 	
		}			
		or 		
		http://the.darktable.com/post/5612486609/jsonfx-json-serialization-for-the-unity-engine
		*/		
	}
	
	public void doFlush()
	{
		PlayerPrefs.Flush();
	}	
	
	public void gameOverDeletePrefs()
	{
		PlayerPrefs.DeleteAll();	
		doFlush();
	}	
}

Main Menu JS using GameSettings.cs to get data

Here’s an example of mainMenuScript.js using an instance of GameSettings.cs in Unity’s Awake() and OnGUI() functions. The main menu scene uses LoadPlayerData() to check if the game is resumable.

#pragma strict

import GameSettings;

static var BTN_WIDTH:float = 300;
static var BTN_HEIGHT:float = 120;
static var startNewGame:boolean = true;
static var gameResumable:boolean = false;

var skin1:GUISkin;
var instructionHdr:String;
var instructionText:String;
var go2:GameObject;
var gameSettings:GameSettings;

function Awake() {

	go2 = GameObject.Find('__GameSettings');				
	gameSettings = go2.GetComponent('GameSettings');		
	var gameData:Hashtable = gameSettings.LoadPlayerData();		    
	
	//if PlayerPrefs.txt is not empty and it exists, populate the gameResumable var
	
    if(gameData != null) {	    	    	        
		mainMenuScript.gameResumable = gameData["GameResumable"]; 	
	}	
		
  	instructionHdr = 'Instructions';
	instructionText = 'Tilt your phone left or right to move the ship. Touch screen to fire.';	
}

function OnGUI () {

  	GUI.skin = skin1;

	GUI.Label(Rect(Screen.width/2 - 145, Screen.height/2-310, 300, 100), instructionHdr); 	
	GUI.Label(Rect(Screen.width/2 - 145, Screen.height/2-240, 350, 200), instructionText);
	//GUI.TextArea(Rect(Screen.width/2- 145, Screen.height/2-210, 300, 150), instructionText);

	if(GUI.Button(Rect(Screen.width/2-150, Screen.height/2.35, mainMenuScript.BTN_WIDTH, mainMenuScript.BTN_HEIGHT), 'START GAME')) {

		startNewGame = true;
		Application.LoadLevel(1); //1 comes from Build Settings list of items in Scenes in Build 						
	} 

	
	if(mainMenuScript.gameResumable) {		
		if(GUI.Button(Rect(Screen.width/2-150, Screen.height/1.65, mainMenuScript.BTN_WIDTH, mainMenuScript.BTN_HEIGHT), 'RESUME GAME')) {
	
			startNewGame = false;
			Application.LoadLevel(1); //1 comes from Build Settings list of items in Scenes in Build 						
		} 
	}					
}

For now, the game is only resumable if the user quits the app before he loses 3 lives or scores at least 800 points (see playerScript.js below). If the game is resumable, the mainMenuScript.js shows an extra button – RESUME NOW, which lets you start the game with your previous score and number of lives (stored locally on your phone).

If you’re testing your game on a Mac, the Unity IDE will store the PlayerPrefs.txt file with it’s name/value pairs here:

/Users/yourUserName/Library/Caches/CompanyName/ProductName/PlayerPrefs.txt. 

Note: On some settings on OS X 10.7 Lion, /Users/yourUserName/Library directory may not be visible via Finder. It’s still accessible via Terminal though. In Finder, you can click on the “GO” menu in the top left OS-level navbar and hold down “Option” – the Library will show up as a menu item.

Unity picks up “CompanyName” from the Company Name field in “File > Build Settings > Player Settings” (in the Inspector) and your “ProductName” from Product Name field under Player Settings.

The name value pairs in PlayerPrefs.txt look like this:

PlayerLives : 2 : System.Int32 ; GameResumable : True : System.Boolean ; PlayerScore : 100 : System.Int32

If you’re wondering about the GUIskin stuff, it allows you to customize fonts & their appearance. In general, Fonts in Unity3D 3.5 seem to be as much of pain to use as they are in Flash CSx and Flash Builder. Check the Unity documentation, Unity Community forum and Unity Answers if you’re having trouble with fonts.

Player JS using GameSettings.cs to get and set data

playerScript.js is the other place where GameSettings.cs is used in this case. Don’t forget to create a container GameObject to hold GameSettings.cs in the “level1” scene:

Here we’re using both the LoadPlayerData() and SavePlayerData() methods. As I’ve mentioned above, the game is only resumable if the app receives the OnApplicationQuit() or the OnApplicationPause() messages from iOS before you lose 3 lives or score at least 800 points.

playerScript.js

#pragma strict

import GameSettings;

static var LEFT_BOUND:float = -2.9;
static var RIGHT_BOUND:float = 2.9;
static var playerScore:int = 0;						
static var playerLives:int = 3;									

var playerSpeed:int = 0.1;
var bullet:Rigidbody; //ref to bulletPrefab in level1 scene
var explosion:Transform; //ref to explosionPrefab in level1 scene
var timeDiff:float;
var delay1:float = 1.5; //two second delay.
var skin1:GUISkin; //var style1:GUIStyle; //for a single GUI object only        
var go2:GameObject;// = GameObject.Find('__GameSettings');
var gameSettings:GameSettings;// =  go2.GetComponent('GameSettings');
var firstTime:boolean = true;

function Start() {

	go2 = GameObject.Find('__GameSettings');
	gameSettings = go2.GetComponent('GameSettings');

    timeDiff = Time.time + delay1;
    
    firstTime = false;        
	
	if (!mainMenuScript.startNewGame) {
		
		//RESUME GAME
	    var gameData:Hashtable = gameSettings.LoadPlayerData();	
	    
	    if(gameData != null) {	    	    	
	        
		    playerScore = gameData["PlayerScore"]; 
			playerLives = gameData["PlayerLives"];
			mainMenuScript.gameResumable = gameData["GameResumable"]; 	
					
			Debug.Log("---RESUMING PREVIOUS GAME" ); 
						
		}
		
	} else {
	
		playerScore = 0; 
		playerLives = 3;
	}
	
	Debug.Log("playerScore = " + playerScore);
	Debug.Log("playerLives = " + playerLives);		
}

function Update() {

    var dir:Vector3 = Vector3.zero;
    var quat:Quaternion = Quaternion.AngleAxis(180, Vector3.forward);

    dir.x = Input.acceleration.x;

    if (dir.sqrMagnitude > 1)
        dir.Normalize();
   
    transform.Translate ( (dir * playerSpeed) * Time.deltaTime); 

    for (var touch : Touch in Input.touches) {
        if (touch.phase == TouchPhase.Began) {
            /*
                Replace with Object Pool
                http://abitgames.com/2011/blog/10-things-to-know-programming-unity3d-games/

            */
            var tempBullet:Rigidbody;
            tempBullet = Instantiate( bullet, transform.position, transform.rotation );             
        }
    }

    if (Time.time > timeDiff) //delay by 2 seconds before switching to lost scene
    {
        timeDiff = Time.time + delay1;    

        //you won
        if(playerScore >= 800){        	
            Application.LoadLevel(3);
        }
        
        //you lost    
        if( (playerLives <= 0) ){         	
            Application.LoadLevel(2);
        }
        
        //for now: 
        gameUnResumable();
        //with more Levels, game should be resumable unless the last level has been played? 
        //and even then, you should be able to choose which level to start from        
    } 
}

function OnGUI() {
    
    GUI.skin = skin1;

    GUI.Label(Rect(30,50,200,50), "SCORE: " + playerScript.playerScore); //, style1
    GUI.Label(Rect(Screen.width-180,50,200,50), "LIVES: " + playerLives);
}

function OnTriggerEnter(otherObject:Collider) {
    
    var o:GameObject = otherObject.gameObject;

    if(o.tag == 'enemy'){ //Enemy's "is Trigger" is checked

        o.transform.position.y = 7;
        o.transform.position.x = Random.Range( playerScript.LEFT_BOUND, playerScript.RIGHT_BOUND );

        var tempExplosion:Transform;
        tempExplosion = Instantiate(explosion, transform.position, transform.rotation);

        playerLives--;
    }
}

function OnApplicationPause() {	
	
	//to prevent a Null object warning on Awake()
	if(!firstTime){ 
		gameResumable();	
	}	
}

function OnApplicationQuit() {	
	
	gameResumable();
	
}

function gameResumable(){

	mainMenuScript.gameResumable = true; //necessary? 
	
	gameSettings.SavePlayerData(playerScore, playerLives, mainMenuScript.gameResumable);		
	gameSettings.doFlush();	
}

// you lost or won, for now, game becomes unresumable
function gameUnResumable(){

	mainMenuScript.gameResumable = false; //necessary? 	
	gameSettings.SavePlayerData(playerScore, playerLives, mainMenuScript.gameResumable);
	gameSettings.doFlush();
	
	Debug.Log("gameUnResumable() called");
}

Mixing C# and Javascript in Unity3d 3.5

Since we’re using both languages, it’s important to remember to place the secondary language scripts, in this case C#, inside a location that Unity compiles first. In other words put the .cs files inside the Plugins folder in your Project panel (in Finder it’s Assets/Plugins). If Plugins doesn’t exist, do Create > Folder in the Project panel.