December 21, 2013

Howto: Use Unity3D as a level editor for your own game

I ran into a small problem while I was writing my own hobby game. I needed a level editor, and I wrote one. The real problem was, though, that the editor wasn't very quick to use, and it lacked a lot of optimization code. Instead of further developing this small editor, I came up with a thought: What if I used Unity3D as an editor?

... Challenge accepted!


It turned out to be quite a simple task, in the end. I wrote the export code as an editor extension, which would save the scene in my own binary format. This way, I can design a level just like I would when developing a "full" Unity3D game.

So, let's begin! First, create a new Unity3D project:


After you've created the project, create a new C# script. I called it "ExportScript.cs":


Now, the actual script! In its basic form, the export script is like this:


using UnityEditor;
using UnityEngine;
using System.Collections;
using System.IO;

// This class is responsible for exporting the scene to our own file format
public class ExportScript : EditorWindow
{
    [MenuItem("File/Export")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow(typeof(ExportScript));
    }
 
    void OnGUI()
    {
        // Create a button that will start the export function
        GUILayout.Label("Export Level", EditorStyles.boldLabel);
        if(GUI.Button(new Rect(10, 50, 100, 32), "Export"))
        {
            // Start the export
            Export();
        }
    }

    // Our output writer
 BinaryWriter writer;
 
 // The export function
 void Export()
 {
  // Create a new output file stream
  writer = new BinaryWriter(new FileStream("out.lvl", FileMode.Create));
  
  // TODO: Write all the data we need (gameobjects, lights, components etc.)
  
  // All done, close the writer
  writer.Close();
 }
}

And that's it! Now, what do we actually want to write to the file? It depends. In my own game project, I wanted to get textures, meshes, game objects, lights, colliders, joints, sounds, background music... Just about everything contained in the scenes. I will not show all the code for this as it would be too much, but the basic concept goes like this: First, write the basic information about the game objects, like name, location, orientation, and scale. Then, for each component you require, check if the game object has that component (say, a mesh filter), and if it does, write all the information available for that component.

It's really simple to do, although it takes some time to write all the component stuff down. When doing this, Unity3D's script reference helps a lot, and you can always check the inspector view to see if you've missed something. :)

Anyhoo, let's take a look at the final example. In this example, the script goes through all the game objects in the scene and saves their names and transforms, as well as checks for the mesh filter components.


    void Export()
 {
  // Create a new output file stream
  writer = new BinaryWriter(new FileStream("out.lvl", FileMode.Create));
  
  // Loop through the transforms in the scene, exporting only what's necessary
  foreach(Transform t in Object.FindObjectsOfType(typeof(Transform)))
  {
   Debug.Log("Exporting " + t.gameObject.name);
   WriteTransform(t);
  }
  
  // All done, close the writer
  writer.Close();
 }

    // Writes the transform object
 void WriteTransform(Transform t)
 {
  // Write the basic transform components (location, orientation, scale)
  writer.Write(t.position.x);
  writer.Write(t.position.y);
  writer.Write(t.position.z);
  writer.Write(t.rotation.x);
  writer.Write(t.rotation.y);
  writer.Write(t.rotation.z);
  writer.Write(t.rotation.w);
  writer.Write(t.localScale.x);
  writer.Write(t.localScale.y);
  writer.Write(t.localScale.z);
  
  // Write the mesh renderer (cast / receive shadows, materials)
  WriteMeshRenderer(t.GetComponent<meshrenderer>());
  
  // Write the actual mesh
  WriteMesh(t.GetComponent<meshfilter>());
 }
 
 void WriteMeshRenderer(MeshRenderer mr)
 {
  if(mr != null)
  {
   // Mesh renderer found, write '1'
   writer.Write(1);
   
   // Write the mesh renderer properties
   writer.Write(mr.castShadows);
   writer.Write(mr.receiveShadows);
   
   // TODO: Write the materials
   
  }
  else
  { 
   // No mesh renderer found, write '0'
   writer.Write(0);
  }
 }
 
 void writeMesh(MeshFilter mf)
 {
  if(mf != null)
  {
   writer.Write(1);
   Mesh mesh = mf.mesh;
            // TODO: Write the mesh data (vertices, indices, UVs etc.)
  }
  else
  {
   writer.Write(0);
  }
 }

There! When you want to run the script, go to the menu "File > Export". The level will be saved to a file called "out.lvl" in the Unity3D project root.

Now, things to consider: You should keep track of all the assets you've written (meshes, materials, textures, sounds etc.), and only write them once. You can also create multiple files, for example save materials to one file, meshes to one and so on. A more sophisticated approach is to create atlas files where you have every asset you need neatly packed. You can also create texture atlases on-the-fly, create dummy scripts in Unity3D to have references about doors between scenes, set dummy objects for player spawns, and just about whatever you need! Remember, you can take full advantage of the tags, layers and such Unity3D has to offer. You can also write the output in JSON, do conversions between classes with reflection, you name it!

I hope you found this small tutorial useful, and as always, feel free to comment and ask questions. You can also make requests about future blog posts and tutorials. :)

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete