SceneManager

/*
------------------	Unity SceneManager	---------------------------

Simple 2D SceneManger that use othorgraphic projection. Scene Objects should be place side by side, featuring auto aligning and sliding transition between scenes.

Version			: 1.0.0
Updated			: 16.May.2011
Author			: thienhaflash (thienhaflash@gmail.com)

Dependencies	: iTween.cs
Callbacks 		: OnSceneBegin, OnSceneEnd, OnSceneReady
Usages 			: SceneManager.gotoScene(id, [dir = 0]);
				  SceneManager.nextScene([dir = 0]);
				  SceneManager.setToScene(id);
				  SceneManager.setToNextScene();

------------------------------------------------------------------
*/

download SceneManager.unitypackagedownload %LIB%.zip


click to see demo

After building some mini games with Unity i see that there are some common things that we can reuse across game projects and SceneManager is one of them ! SceneManager manages the game scenes: smoothing game scene transitions with callbacks to inform sceneObjects to reset or update when needed.

h4>There are some points about SceneManager‘s implementation :

  1. Why SceneManager is written in C# ?
    • C# is much more powerful than javascript, especially, we can have a Typed Array in the inspector that is ready for visual dragging & dropping to add and remove elements.
    • To make use of this power, library stuffs, core libs, things that can be reused multiple times should be done in C#
    • For specific game play and player controls implementation, working with javascript is kind of faster / easier.
  2. You’ve heard that SendMessage is slow, so why i used it ? (i’ve read somewhere that SendMessage is about 250 times slower than normal function calls)
    • Provide the flexibility of an event system so we don’t need to modify the script all the time
    • The event only triggered once when things comes in / out so it won’t hurt the performance very much
    • It’s not really slow down that much, as from my tests with SendMessage implementation, i can still got 250 fps on PC and 30 fps on mobile the iPad
  3. Why i need to place all scene assets / setups in the same Unity Scene ?
    • Unity needs time to reset / change scene, and currently we can’t have any kind of progress indicator showing that to user as everything is hang up on the way.
    • As the result, we won’t have smooth transition from one scene to another, the delay can be more than seconds to wait
    • Use only one Unity scene, we then can move camera around to make smooth transition and only pay for first time load, not every time we change the scene
    • Though more memory consumed, this approach gave the best user experience and lower CPU usages for mini games
    • As a side note, to get things work correctly, you will need to reset everything manually when the scene comes into view
  4. Why SceneManager is put in Standard Assets folder ? why shouldn’t i move it around ?
    • As script in Standard Assets folder got compiled first, putting SceneManager there will enable cross scripting, that means you can call SceneManager not only from C# but also from Javascript. That will give you the most flexibility to work with.
    • If you really have a reason to move it around (i can’t find one), remember that you will need to write the code in C# to make calls to SceneManager.

There are some cool features in SceneManager :

  1. Add / remove SceneObjects visually by dragging / dropping into SceneManager inspector’s sceneList
  2. Static access to SceneManger to change scene by calling SceneManger.nextScene() and SceneManger.gotoScene(id)
  3. Auto layout you can change the dimension of the game scene by setting width / height in SceneManager inspector, SceneObjects will be automatically resize and layout side by side
  4. Auto transition : currently only support camera sliding
  5. Supported optional callbacks : OnSceneStart(), OnSceneReady(), OnSceneEnd()

How to use

  1. Drag and drop SceneManager script into main Camera
  2. Create SceneObjects, which are empty Game Objects contain background planes and game stuffs inside.
  3. Drag and drop these SceneObjects into SceneManager inspector’s sceneList
  4. Attach scripts into SceneObjects, the script contains the handlers that will be called when SceneObjects are moving in/out of the view and call SceneManager to change scene

And that’s it, you are good to go. Here is the code for SceneManager for ones who wants to read.

static APIs – map to the singleton

private static SceneManager api;
	
public static void gotoScene(int id){ if (api) api._gotoScene(id, true, 0); }
public static void gotoScene(int id, int dir){ if (api) api._gotoScene(id, true, dir); }
	
public static void nextScene(){ if (api) api._nextScene(true, 0); }
public static void nextScene(int dir){ if (api) api._nextScene(true, dir); }
	
public static void setToScene(int id){ if (api) api._gotoScene(id, false, 0); }
public static void setToNextScene(){ if (api) api._nextScene(false, 0); }

Instance variables – configurable gameWidth / gameHeight / tweenTime

public int		defaultScene; //optional default scene
public GameObject[]	sceneObjects; //sceneObjects
	
//game config
public float	tweenTime	= 1.0f;
public int		gameWidth	= 1024;
public int		gameHeight	= 768;
	
//internal use
private int		nScenes;
private int		cScene;
private bool	isTweening;
	
private Vector3 leftPos;
private Vector3 rightPos;
private Vector3 scale;
	
private float widthf;
private Dictionary<int, Renderer[]> renderCache;

Use lazyloading to improve init time performance – only hide gameObjects that are not in the defaultScene, only resize the sceneObject that is the defaultScene, other sceneObjects will be resized to the correct size for the first time they need to be tweened in

void Start () {
	if (!api) api = this;
				
	GameObject t;
	Renderer[] rd;
		
	Camera.main.orthographicSize = 0.005f * Mathf.Max((float)gameWidth/2, (float)gameHeight); //magic fomular to fit the orthographic camera view
	widthf = gameWidth/100f;

	renderCache = new Dictionary<int, Renderer[]>();
	leftPos		= new Vector3(-widthf, 0, 0);
	rightPos	= new Vector3(widthf, 0, 0);
	scale		= new Vector3(widthf/10, (float)gameHeight/1000f,1);

	nScenes		= sceneObjects.Length;
	for (int i=0; i<nScenes; i++){
		t = sceneObjects[i];
		rd = _getRenderers(t);
		if (i != defaultScene) {
			_hide(rd);
			//prevent mouseClicks - much more lightweight than 
			//disable / enable the object itself in term of performance
			//because Unity engines need to dispose / recreate
			//the colliders each time we change gameObject.active
			//move it faraway as we not know yet the size
			t.transform.localPosition = new Vector3(100f, 0,0);
		}
		renderCache.Add(i, rd);
	}
		
	cScene = -1;//goto default scene
	_gotoScene(defaultScene, false, 0);
}

Check and make old sceneObject transitioning out, new ones being transitioning in, dispatch OnSceneStart, OnSceneEnd. When the tween is completed, dispatch OnSceneReady

private void _gotoScene(int id, bool doTransition, int dir){
	if (isTweening) return; //TODO : consider support Overwrite ?
		
	bool r2l		= dir==0 ? id > cScene : dir==1 ? true : false;
	id			= id % nScenes;
	isTweening	= true;

	//reset new gameObject's size / position
	GameObject t	= sceneObjects[id];
	if (t.transform.localScale.x != scale.x || t.transform.localScale.y != scale.y) t.transform.localScale	= scale;
	t.transform.localPosition	= r2l ? rightPos : leftPos;
	//add transition in, dispatch OnSceneBegin
	iTween.MoveTo(t, iTween.Hash( "x"			, 0
							, "islocal"		, true
							, "time"		, doTransition ? tweenTime : 0
									
							, "oncompletetarget", gameObject
							, "oncomplete"		, "onTwComplete"
							, "oncompleteparams", id
					));
	t.SendMessage("OnSceneBegin", null, SendMessageOptions.DontRequireReceiver);
		
	//transition out the current scene (if any), dispatch OnSceneEnd
	if (cScene!=-1){
		_show(renderCache[id]);//show the new sceneObject
			
		GameObject o = sceneObjects[cScene];
		iTween.MoveTo(o, iTween.Hash( "x"		, r2l ? -widthf : widthf
								, "islocal"	, true
								, "time"	, doTransition ? tweenTime : 0));
		o.SendMessage("OnSceneEnd", null, SendMessageOptions.DontRequireReceiver);
	}
}
private void _nextScene(bool doTransition, int dir){
	_gotoScene(cScene+1, doTransition, dir);
}
	
void onTwComplete(int id) {
	//do hide old scene, if any
	if (cScene != -1) _hide(renderCache[cScene]);
		
	cScene = id;
	isTweening = false;
	sceneObjects[id].SendMessage ("OnSceneReady", null, SendMessageOptions.DontRequireReceiver);
}

Utils to get Renderers (then cache for faster visibility toggling), hide / show gameObjects by setting renderer.enabled to false / true.

Renderer[] _getRenderers(GameObject go){
	Component[] tmp = go.GetComponentsInChildren(typeof(Renderer));
	Renderer[] ren = new Renderer[tmp.Length];
	for (int i=0; i<tmp.Length; i++){ ren[i] = tmp[i] as Renderer;}
	return ren;
}
	
void _hide(Renderer[] children){ for (int i=0; i< children.Length; i++){ children[i].enabled = false; }}
void _show(Renderer[] children){ for (int i=0; i< children.Length; i++){ children[i].enabled = true; }}

iTween is used to make basic sliding transition. There are many more things i am thinking of to improve this one, but as this one is working fine targeting iPad (250 fps on my mac pro and 30 fps on my ipad), i believe that it could be a very lightweight and quick mini game template to try out !

Have fun, and if you have any idea to improve this, don’t hesitate to drop a line in the comment.

Advertisements

One thought on “SceneManager

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s