Tiled Level Editor Details

This post will go into the details of how I created the Tiled Level Editor for the game Spring It.  Spring It is 2D and grid based.  Unity 3D does not have an easy way to build levels to a grid we specify and would often cause the pieces placed by the player to not align to the piece we placed during level building. So I wanted to make a level editor that ensures we are placing our level pieces to the same grid the player is placing theirs.  One of my team members, Chris Flynn, suggested I use Tiled map editor as a start for this level editor I was hoping to make.

Tiled Ezample Full Shot.png

Tiled can export to JSON but Unity 3D does not have a built in JSON parser like it does for xml, so I decided to use MiniJSON by Calvin Rien.  Using MiniJSON proved to be very helpful and made the parsing much simpler.  First I created a string of names for the tiles so I knew what I was reading when looking through each of the layer's data sets.  Due to the way the data was stored and how I wanted to access it I needed to store each set of data to their own array and they each needed to be parsed in their own unique for loop.  Once they were all stored I could access each of them to determine what tiles existed in each level, then check with my tile names to pinpoint what object was where in the grid.  For example, if I read a number of 1 or 2, I know a conveyor is placed. The difference is that a 2, means the conveyor moves the robots right, while a 1 conveyor moves the robots left. 

Conveyor.png

Once all the data sets were in their appropriate spaces and the levels were built, I still needed to construct the nuts and bolts of the scene.  The Main Camera, Background, etc still needed to be put into the scene.  On top of that, the variables that were unique to each level still needed to be constructed.  This is where the Budget, AvailablePlaceable and RobotDataLocations layers came in handy as they were used to keep track of the correct money usable by the player, enabled objects the player can place, robots spawned, and robots needed to win.  This section of code just took a large amount of breaking down into smaller functions to help me keep track of everything that was happening. The code below is the end result of my work and will be updated as the project evolves. When using the code, just place the exported JSON document into the Text Assest mLevelFile.

//Below is an example of what you should get from the tiled level editor when exported
//The first set should be 8 layers similiar to what is shown below
//{ "height":15,
//"layers":[
// {
// "data":[0,0,...]
//"height":15,
//"name":"Level Layer",
//"opacity":1,
//"type":"tilelayer",
//"visible":true,
//"width":20,
//"x":0,
//"y":0
//},
//
//Below this point shows what each fo the tile sets will look like, there should be 8 of them in total as well.
//The difference with them is that their identifying attribute "firstgrid" will nto increase by 1 for each tile set becasue many of the sets have more than 1 tile.
//"tileheight":256,
//"tilesets":[
// {
// "firstgid":1,
// "image":"Tilesets\/Conveyor\/Conveyor.png",
// "imageheight":256,
// "imagewidth":512,
// "margin":0,
// "name":"Conveyor",
// "properties":
// {
//
// },
// "spacing":0,
// "tileheight":256,
// "tilewidth":256
// },
//
//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using MiniJSON;

public class UnpackLevel : MonoBehaviour {

public TextAsset mLevelFile;
public int mNumberOfTileSets = 8;
public int mLevelScaling = 2;

private Dictionary<string, object> mJSONData;

//Stuff for loading
private long mLevelWidth;
private long mLevelHeight;
private List<string> mTileNames;

private List<int> mLevelLayerDataLocations = new List<int>();
private List<int> mStructuringDataLocations = new List<int>();
private List<int> mPlaceableDataLocations = new List<int>();
private List<int> mBackgroundDataLocations = new List<int>();
private List<int> mAvailiblePrefabs = new List<int>();
private List<int> mRobotDataLocations = new List<int>();
private List<int> mBudgetDataLocations = new List<int>();
private List<int> mInterractableDataLocations = new List<int>();

//Stuff for building
private List<GameObject> mLoadedResources = new List<GameObject>();
private List<GameObject> mSceneItems = new List<GameObject>();


void Start () {

mJSONData = MiniJSON.Json.Deserialize(mLevelFile.text) as Dictionary<string, object>;
UnpackTileSets();
UnpackLocations();
BuildLevelLayer();
/*BuildStructureLayer();
BuildPlaceableDataLocations();
BuildBackgroundDataLocations();
BuildInterractables();*/

}

void UnpackTileSets(){

mTileNames = new List<string>();

for(int i = 0; i < mNumberOfTileSets; i++){
mTileNames.Add(((mJSONData["tilesets"] as List<object>)[i] as Dictionary<string, object>)["name"].ToString());
}
}

// Each one of the for loops below are manually added for each new layer added to the editor
void UnpackLocations(){

mLevelWidth = (long)((mJSONData["layers"] as List<object>)[1] as Dictionary<string, object>)["width"];
mLevelHeight = (long)((mJSONData["layers"] as List<object>)[1] as Dictionary<string, object>)["height"];


for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[0] as Dictionary<string, object>)["data"] as List<object>)[i];
mLevelLayerDataLocations.Add((int)dataMiddleMan);
}

for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[1] as Dictionary<string, object>)["data"]as List<object>)[i];
mStructuringDataLocations.Add((int)dataMiddleMan);
}

for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[2] as Dictionary<string, object>)["data"]as List<object>)[i];
mPlaceableDataLocations.Add((int)dataMiddleMan);
}

for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[3] as Dictionary<string, object>)["data"]as List<object>)[i];
mBackgroundDataLocations.Add((int)dataMiddleMan);
}

for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[4] as Dictionary<string, object>)["data"]as List<object>)[i];
mAvailiblePrefabs.Add((int)dataMiddleMan);
}

for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[5] as Dictionary<string, object>)["data"]as List<object>)[i];
mRobotDataLocations.Add((int)dataMiddleMan);
}

for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[6] as Dictionary<string, object>)["data"]as List<object>)[i];
mBudgetDataLocations.Add((int)dataMiddleMan);
}

for(int j = 7; j < (mJSONData["layers"] as List<object>).Count;j++){
for( int i = 0; i < mLevelHeight*mLevelWidth; i++){
long dataMiddleMan = (long)(((mJSONData["layers"] as List<object>)[j] as Dictionary<string, object>)["data"]as List<object>)[i];
mInterractableDataLocations.Add((int)dataMiddleMan);
}
}
}

void BuildLevelLayer(){

IdentifyTiles();
InstantiateTheLevelObjects();
BuildSceneItems();


}

void IdentifyTiles(){

for(int i = 0; i < mNumberOfTileSets;i++){

if(Resources.Load ("Prefabs/" + mTileNames[i], typeof(GameObject)) != null){
mLoadedResources.Add (Resources.Load("Prefabs/" + mTileNames[i], typeof(GameObject)) as GameObject);
}
}
}

void InstantiateTheLevelObjects(){

for(int i = 0; i < mLevelLayerDataLocations.Count; i++){
GameObject tempObject;
int switchVariable = 0;

//This is a manual way for me to match each tile found to which type of item is being created for the Level layer Only
if(mLevelLayerDataLocations[i] == 0) switchVariable = 0;
if((mLevelLayerDataLocations[i]== 1) || (mLevelLayerDataLocations[i] == 2)) switchVariable = 1;//Conveyor
if((mLevelLayerDataLocations[i]== 3) || (mLevelLayerDataLocations[i] == 4)) switchVariable = 2; //Spring
if((mLevelLayerDataLocations[i] > 4) && (mLevelLayerDataLocations[i] < 9)) switchVariable = 3;//Fan
if(mLevelLayerDataLocations[i] == 9) switchVariable = 4;//Spawner
if(mLevelLayerDataLocations[i] == 10) switchVariable = 5;//Collector
if(mLevelLayerDataLocations[i] == 11) switchVariable = 6; //Robot

switch(switchVariable){

default:
break;

case 0:
break;

case 1: //Conveyor

tempObject = Instantiate(mLoadedResources[0],getInstantiatePosition(i), Quaternion.identity) as GameObject;
tempObject.name = mLoadedResources[0].name;

if(mLevelLayerDataLocations[i] == 1){
tempObject.transform.Rotate(0,180, 0);
}
break;

case 2: //Spring
tempObject = Instantiate(mLoadedResources[1],getInstantiatePosition(i), Quaternion.identity) as GameObject;
tempObject.name = mLoadedResources[1].name;

if(mLevelLayerDataLocations[i] == 3){
tempObject.transform.Rotate(0,180, 0);
}
break;

case 3: //Fan
tempObject = Instantiate(mLoadedResources[2],getInstantiatePosition(i), Quaternion.identity) as GameObject;
tempObject.name = mLoadedResources[2].name;

if(mLevelLayerDataLocations[i] == 5){
tempObject.transform.Rotate(0, 0, 90);
}

if(mLevelLayerDataLocations[i] == 7){
tempObject.transform.Rotate(0, 180, 0);
}

if(mLevelLayerDataLocations[i] == 8){
tempObject.transform.Rotate(0, 0, -90);
}
break;

case 4: //Spawner
tempObject = Instantiate(mLoadedResources[3],getInstantiatePosition(i), Quaternion.identity) as GameObject;
tempObject.name = mLoadedResources[3].name;

break;

case 5: //Collector
tempObject = Instantiate(mLoadedResources[4],getInstantiatePosition(i), Quaternion.identity) as GameObject;
tempObject.name = mLoadedResources[4].name;

break;

case 6: //Robot
tempObject = Instantiate(mLoadedResources[5],getInstantiatePosition(i), Quaternion.identity) as GameObject;
tempObject.name = mLoadedResources[5].name;

break;

}
}
}

Vector3 getInstantiatePosition(int incomingData){

Vector3 tempVector = new Vector3(0,0,0);

tempVector.x = (incomingData % mLevelWidth) * mLevelScaling;
tempVector.y = mLevelScaling * (mLevelHeight - (incomingData/mLevelWidth));

return tempVector;
}

void BuildSceneItems(){

GameObject mainCamera = Resources.Load ("Prefabs/Main Camera", typeof(GameObject)) as GameObject;
GameObject endGameCamera = Resources.Load("Prefabs/EndGameCamera", typeof(GameObject))as GameObject;
GameObject background = Resources.Load ("Prefabs/background", typeof(GameObject)) as GameObject;
GameObject light = Resources.Load ("Prefabs/Directional light", typeof(GameObject)) as GameObject;

//These are all of the vriables that need to be set on the Main Camera's scripts
GameObject tempObject = Instantiate(mainCamera, new Vector3(mLevelScaling*mLevelWidth/2,mLevelScaling*mLevelHeight,-15), Quaternion.identity) as GameObject;
tempObject.GetComponent<PlayerObjectButtons>().mEnableEachObject = EnablePlaceables();
tempObject.GetComponent<Budget>().mTotalBudget = SetBudget();
tempObject.GetComponent<MainCameraGUI>().mRobotsNeededToWin = SetRobotsNeededToWin();
tempObject.GetComponent<MainCameraGUI>().mMaxRobotsReleased = SetRobotsReleased();
tempObject.GetComponent<CameraMovement>().mLevelSize.x = mLevelWidth * 2;
tempObject.GetComponent<CameraMovement>().mLevelSize.y = mLevelHeight * 2;

Instantiate(endGameCamera, tempObject.transform.position, tempObject.transform.rotation);
Instantiate(light, light.transform.position, light.transform.rotation);
Instantiate(background, background.transform.position, Quaternion.identity);
}

bool[] EnablePlaceables(){

bool[] tempbool = {false, false, false};

for(int i = 0; i < mLevelHeight * mLevelWidth;i++){

if((mAvailiblePrefabs[i]== 1) || (mAvailiblePrefabs[i] == 2)) tempbool[2] = true;//Conveyor
if((mAvailiblePrefabs[i]== 3) || (mAvailiblePrefabs[i] == 4)) tempbool[0] = true; //Spring
if((mAvailiblePrefabs[i] > 4) && (mAvailiblePrefabs[i] < 9)) tempbool[1] = true;//Fan

}

return tempbool;
}

int SetBudget(){

int budget = 0;

for(int i = 0; i < mLevelHeight * mLevelWidth;i++){

if(mBudgetDataLocations[i] == 13) budget += 100;
if(mBudgetDataLocations[i] == 14) budget += 500;
if(mBudgetDataLocations[i] == 15) budget += 1000;
if(mBudgetDataLocations[i] == 16) budget += 10000;
}

return budget;
}

int SetRobotsNeededToWin(){

int robotsToWin = 0;

for(int i = 0; i < (mLevelHeight * mLevelWidth)/2;i++){

if(mRobotDataLocations[i] == 11) robotsToWin++;//11 is the value for the robot

}

return robotsToWin;

}

int SetRobotsReleased(){

int robotsReleased = 0;

for(int i = (int)(mLevelHeight*mLevelWidth)/2; i < (mLevelHeight * mLevelWidth);i++){

if(mRobotDataLocations[i] == 11) robotsReleased++;//11 is the value for the robot

}

return robotsReleased;
}
}