Unity3D for iOS: Storing a New High Score in Parse via UniParse while Adding a Value to a _ptr Field

So far this is the only way I’ve been able to successfully do this. I wonder if I’m missing something & there’s actually a better way to do this using only 1 call to Parse. Anyone? Feel free to prove me wrong. Please.

...
// localStorage is a PlayerPrefs instance that lets us grab values stored on the device that's running the app
...			 

// MyGameUsers is a Parse Class that stores unique user ID's as the parse Class' objectId's
// change MyGameUsers to your own Class name where appropriate.
var usersTablePointer = new ParseClass("/classes/MyGameUsers");

//"f2H4saD8" is an arbitrary value for demo purposes, a typical objectId from a Parse table  
string myQuery = "where={\"objectId\": \""+ "f2H4saD8" + "\" }";             				
			
ParseInstanceCollection prseList = gamerTablePointer.List(myQuery);		

//wait to get the Gamer object w/ our objectID specified in myQuery 
while(!prseList.isDone) yield return null;
			
ParseInstance[] items = prseList.items;		
				
// post a new record to the Score table in Parse:
// change MyGameScores to your own Class name where appropriate.
var myGameScoresClass = new ParseClass("/classes/MyGameScores");

//create a new record in the db table (represented by the ParseClass)
var myNewScore = myGameScoresClass.New();

//1034 is just an arbitrary score value; you can grab it from your own variable value			
myNewScore.Set( "Score", 1034 ); 	
myNewScore.Set( "MyGameUsers_ptr", items[0] as object );

//create the new record in the server side Parse Class (table) called "MyGameScores" (or your own class name)
myNewScore.Create();
				
//Wait until it's finished the save in Parse.
while(!myNewScore.isDone) yield return null;						

Debug.Log("myNewScore is done; check it in the MyGameScores table in the relevant Parse.com account.");		 

Unity3D for iOS: “MissingReference” Error After Calling Application.LoadLevel()

I got a MissingReference error recently when loading a scene for the 2nd time via Application.LoadLevel(). In other words, I had MENU scene that handled some game settings, etc. When you hit the PLAY GAME button, Unity loaded a GAMEPLAY scene. After finishing the game, the MENU scene would load again. Without quitting the game, if I hit the PLAY GAME button inside MENU again, it would once more call Application.LoadLevel() on the GAMEPLAY scene. GAMEPLAY scene loaded ok for the 2nd time but shortly after calls to certain UIToolkit objects would generate a “MissingReference” error, similar to this one:

MissingReferenceException: The object of type ‘Transform’ has been destroyed but you are still trying to access it.

Both scenes had a GameStructureManager script. Inside the GameStructureManager script I created some members of type GameObject inside Start() via GameObject.Find() and grabbed the script component via GetComponent() in a C# project. I also assigned Events via Delegates, a technique I learned from Prime31’s UIToolkit plugin:

    void Start() {          
    ...          
       MyGameObject1 = GameObject.Find("HUD_Manager");     
       MyHUD = (HUDscript) MyGameObject1.GetComponent<HUDscript>();       

        HUDScript.onContinue += onContinueStuffHandler;         
    ...
    }

Solution

Adding an OnDestroy() and using it to unsubscribe from events subscribed to in Start() fixed the MissingReference error in this case:

    void OnDestroy() {
    
       HUDscript.onContinue -= onContinueStuffHandler;     

       Destroy( MyGameObject1 );
       Destroy( MyHUD );
    }

Unity3D for iOS: UIToolkit & Setting a Custom RGB Color Value for UITextInstance

Using Color.red or Color.green is quick and easy in a lot of cases, but if you’re working with a Designer, you often have to choose a more customized color value based on what’s in a .PSD file.

Basic UIToolkit text set up (assuming the UI and UIToolkit objects are properly set up in the Hierarchy & Inspector):

var font1 = new UIText( textToolkit, "arialregular", "arialregular.png" );			
var myText = font1.addTextInstance( "SCORE 0", 0.0f, 0.0f, 0.6f, 0, Color.green, UITextAlignMode.Center, UITextVerticalAlignMode.Middle );							

Using R,G,B values as params for Color class’ constructor

Tried a bunch of variations and they didn’t work:

myText.setColorForAllLetters( new Color( 169, 238, 3 ) ); //RGB: 169, 238, 3		
myText.setColorForAllLetters( new Color( 0.78f, 0.99f, 0.93f ) ); //HSB: 78, 99%, 93%

This didn’t work either:

	
private Color cGreen;

void Start() {
		
	//cGreen = new Color( 0.6f, 0.2f, 0.03f); //169.0f, 238.0f, 3.0f
	//cGreen = new Color( 169.0f, 238.0f, 3.0f, 255.0f); //169.0f, 238.0f, 3.0f
	cGreen = new Color( 0.5f, 0.25f, 0.25f );
...
myText.setColorForAllLetters( (Color)cGreen );
}

Convert the R, G and B to individual Percentage values

This finally worked:

	
myText.setColorForAllLetters( new Color( 0.69f, 0.93f, 0.01f) ); 

I had to take the RGB value from the .PSD and convert it to percentages. There are sites that already have this conversion for you, like this one.

This works too:

var myText = font1.addTextInstance( "SCORE 0", 0.0f, 0.0f, 0.6f, 0, new Color( 0.69f, 0.93f, 0.01f), UITextAlignMode.Center, UITextVerticalAlignMode.Middle );

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.