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
On to Part 2
Can you please make a pdf? I work offline mostly.
ReplyDeleteBut I like it!
ReplyDeleteFor some reason, some sides are slanted
ReplyDeletewhy did you keep the realistic clouds in the background? it kills the feeling of the game. otherwise, amazing tutorial. thanks.
ReplyDeleteBro, 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!
ReplyDeleteI couldn't get the texture to work, I couldn't put it on the cube or on a material
ReplyDeleteif anyone meet the uv mapping problem, please modify uvcube script as following.
ReplyDelete// 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);
Hey Greg and or Duck,
ReplyDeleteplaying 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?
This comment has been removed by the author.
ReplyDeleteVery interesting, good job and thanks for sharing such a good information.Crunchyroll Mega Fan Upgrade
ReplyDelete