Monday, December 8, 2014

How to make a game like Fez in Unity: Part 1




I ran across a technical presentation by Renaud Bédard , the programmer who worked on the hit indie, Fez! In this presentation titled, “Cubes All The Way Down”, Renaud discusses the strategy he used in creating the game engine and level editor used in Fez. The more I watched, the more interested I was in trying to replicate the entertaining 3D rotating mechanic.


In Fez, you play as a little guy named Gomez who finds a 3-dimensional fez hat that allows him to move throughout 3D space. The Fez rotation is entertaining and provides an interesting challenge to develop. As explained by Renaud, the game is built in a 3D level editor specifically designed for the game. 3D cubes are made up of 6 individual flat faces, which are joined at the edges; each face of the cube has a texture applied. This allows the player to rotate about the Y-axis and experience each side of the cube as its own 2D display. Unity 3D is a great place to start in developing something like this because it allows us to build a game in 3-Dimensions and display one side at a time using an orthographic camera.

It may not be clear how to do this right off, but over the course of a few tutorials I’ll walk you through how to build a game like Fez in Unity. In this example, we will build a cube that we can use as the basis to build our level. Let’s get started.

- Start Unity and create a new project (I recommend version 4.6 or later) 
- Create a Cube, you’ll find the option under the “GameObject” heading at the top
- Position your cube at (0,0,0)  We will later develop a script to snap our cubes into single unit positions on a grid based on cubes of scale (1,1,1)
- Create a new C# script and name it UVCube and add the following code:

using UnityEngine;
using System.Collections;

public class UVCube : MonoBehaviour {
 private MeshFilter mf;
 public float tileSize = 0.25f;


 // Use this for initialization
 void Start () {
 
  ApplyTexture ();

 }
 
 public void ApplyTexture()
 {
  mf = gameObject.GetComponent<MeshFilter> ();
  if(mf)
  {
   Mesh mesh = mf.sharedMesh;
   if(mesh)
   {

    Vector2[] uvs = mesh.uv;
    //FRBLUD - Freeblood


    // Front
    uvs[0] = new Vector2(0f, 0f); //Bottom Left
    uvs[1] = new Vector2(tileSize, 0f); //Bottom Right
    uvs[2] = new Vector2(0f, 1f); //Top Left
    uvs[3] = new Vector2(tileSize, 1f); // Top Right
    
    // Right
    uvs[20] = new Vector2(tileSize * 1.001f, 0f);
    uvs[22] = new Vector2(tileSize * 2.001f, 0f);
    uvs[23] = new Vector2(tileSize * 1.001f, 1f);
    uvs[21] = new Vector2(tileSize * 2.001f, 1f);


    // Back
    uvs[10] = new Vector2((tileSize * 2.001f), 1f);
    uvs[11] = new Vector2((tileSize * 3.001f), 1f);
    uvs[6] = new Vector2((tileSize * 2.001f), 0f);
    uvs[7] = new Vector2((tileSize * 3.001f), 0f);

    // Left
    uvs[16] = new Vector2(tileSize * 3.001f, 0f);
    uvs[18] = new Vector2(tileSize * 4.001f, 0f);
    uvs[19] = new Vector2(tileSize * 3.001f, 1f);
    uvs[17] = new Vector2(tileSize * 4.001f, 1f);

    // Up
    uvs[8] = new Vector2(tileSize * 4.001f, 0f);
    uvs[9] = new Vector2(tileSize * 5.001f, 0f);
    uvs[4] = new Vector2(tileSize * 4.001f, 1f);
    uvs[5] = new Vector2(tileSize * 5.001f, 1f);


    // Down
    uvs[12] = new Vector2(tileSize * 5.001f, 0f);
    uvs[14] = new Vector2(tileSize * 6.001f, 0f);
    uvs[15] = new Vector2(tileSize * 5.001f, 1f);
    uvs[13] = new Vector2(tileSize * 6.001f, 1f);


    mesh.uv = uvs;


   }
  }
  else
   Debug.Log("No mesh filter attached");

 }
}

What are UVs? U and V are percentages of an image that can be mapped onto the various vertices that make up 3D meshes. It’s a common convention used to display textures in game engines and 3D modeling software. Unity Cubes are UV mapped to support adding a material that will cover the entire cube. This would only allow us to add a single texture stretched across the entire object. In order to imitate the Trixels used in the Fez game engine, we will need to allow a different texture to be placed on each side of a cube. A Trixel is a term used by the Fez guys, but really they are commonly referred to as Voxels or volumetric pixels. The UV cube script will apply a texture to our cube, we will be using it on every cube in our project to give us control over what image is placed on each side of the cube.

Next we will need a texture to display on our cube. I created this example based on the “freeblood” method. Its name is derived from the abbreviation FRBLUD which stands for Front, Right, Back, Left, Up and Down. This is the order in which our image should be constructed, it provides an easy way for us to keep track when designing textures for new levels.



Right-click and Save this image for your use, we will use this image as our cube texture



Use these import settings for the texture



Add the texture to our cube's material like shown above.


Configure the UVCube tile size to 0.125 this is beacause each face of our texture is 16 pixels wide, the entire width of the .png is 128. Therefore each of the faces we will use on our cube is 12.5% of the width of the image. The script uses this percentage and slices the image into faces which it puts on our cube. It won’t look like much yet but if we hit Play, you should see something like this:


The reason we don’t get a nice looking cube before we hit play, is because the ApplyTexture()  function is only executed at run-time. Let’s make a script to update our cube in our scene before we play the game. This will make our lives easier when we go to build our level.


Create a Folder called “Editor” and a new C# script inside that folder called ObjectBuilderEditor.

Scripts in the Editor folder can be used to change functionality in Unity’s editor. This allows us to extend Unity’s functionality however we would like!



Use the following code in your new editor script:


using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(UVCube))]
public class ObjectBuilderEditor : Editor
{
 public override void OnInspectorGUI()
 {
  DrawDefaultInspector();
  
  UVCube myScript = (UVCube)target;
  if(GUILayout.Button("Apply Texture"))
  {
   myScript.ApplyTexture();
  }
 }
}


We’re telling Unity to apply this script to other scripts of the UVCube type.DrawDefaultInpector() is called automatically and creates a button with the text “Apply Texture” If that button is pressed, we will call the ApplyTextureFunction() on our cubes.


 





Now we will be able to update our cube texture without hitting Play by using the ApplyTexture button!










Next, we will create snap to grid functionality within the Unity editor. To do this, create a C# script in the Editor folder and name it AutoSnap. Use the following code in your new script:
Note: This code is a modified version of the script posted here:

  

using UnityEngine;
using UnityEditor;
using System.Collections;
public class AutoSnap : EditorWindow
{
 private Vector3 prevPosition;
 private bool doSnap = true;
 private float snapValue = 1;
 private bool Initi = false;
 [MenuItem( "Edit/Auto Snap %_l" )]
 static void Init()
 {

  var window = (AutoSnap)EditorWindow.GetWindow( typeof( AutoSnap ) );
  window.maxSize = new Vector2( 200, 100 );
 }
 void Start()
 {


 }
 public void OnGUI()
 {
  if(!Initi)
  {
   SceneView.onSceneGUIDelegate += SceneGUI;
   Initi = true;
  }
  doSnap = EditorGUILayout.Toggle( "Auto Snap", doSnap );
  snapValue = EditorGUILayout.FloatField( "Snap Value", snapValue );
 }
 public void SceneGUI(SceneView sceneView)
 {
  if ( doSnap
      && !EditorApplication.isPlaying
      && Selection.transforms.Length > 0
      && Selection.transforms[0].position != prevPosition )
  {
   Snap();
   prevPosition = Selection.transforms[0].position;
  }
 }
 private void Snap()
 {
  foreach ( var transform in Selection.transforms )
  {
   var t = transform.transform.position;
   t.x = Round( t.x );
   t.y = Round( t.y );
   t.z = Round( t.z );
   transform.transform.position = t;
  }
 }
 private float Round( float input )
 {
  return snapValue * Mathf.Round( ( input / snapValue ) );
 }
} 


To use this script, you must select it in the project and press Ctrl+L (On Windows) or CMD+L (On MAC). You will see a window in which you can toggle Auto Snap On or Off.  Make sure the Auto Snap box is checked  and the unit is set to 1. Now whenever you manipulate the cubes in the scene, they will automatically snap to units of 1 on the grid. This will be a requirement we take advantage of in future scripts to allow us to more easily implement some of the Fez-like rotational scripts.  Whenever we place any cubes throughout the rest of this tutorial we will want to first activate this script to make sure we are on our grid.
Note: If you are unable to see the Auto Snap window by using the hotkeys, try starting the game and cancelling it. Now highlight the AutoSnap script again and press Ctrl+L (On Windows) or CMD+L (On MAC).

Now create two empty GameObjects in your scene and manually position them at (0,0,0). Name one of these “Buildings” and the other “Platforms”. Drag our cube onto the Platforms GameObject to parent it. The hierarchy should look like this:




As we continue in the next parts of this tutorial we will use this hierarchy to determine which objects are platforms and which are part of buildings. For now, let’s build a simple level! Remember to select the AutoSnap script and hotkey Ctrl+L (On Windows) or CMD+L (On MAC)  Duplicate cubes until you’ve created a platform similar to this:
Note: To duplicate use hotkey Ctrl+D (On Windows) or CMD+D (On MAC) 





 
To spice things up, let’s add a central pillar and build our level vertically. For this I’ve created another texture we can use. Use the same import settings with this texture as we did with the last. Create a new cube and parent it to the Building GameObject, don’t forget to add the UVCube script to our new cube!




                                            Right-click and Save this image for your use


You should wind up with something like this:



In the next part of this tutorial we will go over how to make a simple sprite character and how to move around. Congratulations, you’ve prepared a Fez-like level editor!


On to Part 2
 

10 comments:

  1. Can you please make a pdf? I work offline mostly.

    ReplyDelete
  2. For some reason, some sides are slanted

    ReplyDelete
  3. why did you keep the realistic clouds in the background? it kills the feeling of the game. otherwise, amazing tutorial. thanks.

    ReplyDelete
  4. Bro, I'm loving this, it's the only tutorial I could find to make that Fez-like rotation. Could you please help me with the texture options, I'm running the latest Unity build, 2018.4.21f1. I get the top side good looking, the front, and back side also, but the others 3 are bad positioned. Thank you so much!

    ReplyDelete
  5. I couldn't get the texture to work, I couldn't put it on the cube or on a material

    ReplyDelete
  6. if anyone meet the uv mapping problem, please modify uvcube script as following.

    // Front
    uvs[0] = new Vector2(0f, 0f); //Bottom Left
    uvs[1] = new Vector2(tileSize, 0f); //Bottom Right
    uvs[2] = new Vector2(0f, 1f); //Top Left
    uvs[3] = new Vector2(tileSize, 1f); // Top Right

    // Right
    uvs[16] = new Vector2(tileSize * 1.001f, 0f);
    uvs[19] = new Vector2(tileSize * 2.001f, 0f);
    uvs[17] = new Vector2(tileSize * 1.001f, 1f);
    uvs[18] = new Vector2(tileSize * 2.001f, 1f);


    // Back
    uvs[10] = new Vector2((tileSize * 2.001f), 1f);
    uvs[11] = new Vector2((tileSize * 3.001f), 1f);
    uvs[6] = new Vector2((tileSize * 2.001f), 0f);
    uvs[7] = new Vector2((tileSize * 3.001f), 0f);

    // Left
    uvs[20] = new Vector2(tileSize * 3.001f, 0f);
    uvs[23] = new Vector2(tileSize * 4.001f, 0f);
    uvs[21] = new Vector2(tileSize * 3.001f, 1f);
    uvs[22] = new Vector2(tileSize * 4.001f, 1f);

    // Up
    uvs[8] = new Vector2(tileSize * 4.001f, 0f);
    uvs[9] = new Vector2(tileSize * 5.001f, 0f);
    uvs[4] = new Vector2(tileSize * 4.001f, 1f);
    uvs[5] = new Vector2(tileSize * 5.001f, 1f);


    // Down
    uvs[14] = new Vector2(tileSize * 5.001f, 0f);
    uvs[13] = new Vector2(tileSize * 6.001f, 0f);
    uvs[15] = new Vector2(tileSize * 5.001f, 1f);
    uvs[12] = new Vector2(tileSize * 6.001f, 1f);

    ReplyDelete
  7. Hey Greg and or Duck,

    playing with your tutorial and it seems that anytime i press play the textures get all messed up, even after adding the UVCube code in. the only way that it seems to be correct is when I press the "apply textures" button that we created in the inspector.

    Would a work around of creating and unwrapping a cube in a rendering program work I assume?

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. Very interesting, good job and thanks for sharing such a good information.Crunchyroll Mega Fan Upgrade

    ReplyDelete