Ok, I took a long break since the last part. I'm pretty bad at being active especially with a lot of work to focus on but without further adieu we will start building our meshes to be viewed in 3d. This is probably what a lot of people had in mind when they started this tutorial series but I promise, with the understanding gained from the earlier tutorials this part will come a lot easier. However users that haven't followed the last 4 parts are welcome to start here but I won't be going into as much detail about how meshes are created.
Also this tutorial and the next one I'm writing as I go instead of using parts of my finished code (of course still using that as reference) so I'm hoping that will help stop me from making as many mistakes as I shouldn't skip things or have to make changes to things as I past them to the tutorial as much.
Let's start by making a new scene in unity with an empty game object. Name that object "Chunk", this will be the mesh for a 16x16x16 area (Or larger) of our world. Also make a new script and call it "Chunk" as well because it goes on the chunk object. Let's make our code create one cube to start.
First of all set up the variables:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Chunk : MonoBehaviour {
private List<Vector3> newVertices = new List<Vector3>();
private List<int> newTriangles = new List<int>();
private List<Vector2> newUV = new List<Vector2>();
private float tUnit = 0.25f;
private Vector2 tStone = new Vector2 (1, 0);
private Vector2 tGrass = new Vector2 (0, 1);
private Mesh mesh;
private MeshCollider col;
private int faceCount;
When you're adding these remember to include the line "using System.Collections.Generic;" from line 3 up there. Now, this should look pretty familiar, same lists of verticies, tris and UVs, a tUnit and texture coordinates, our mesh filter and mesh collider and lastly a faceCount which is just a new name for the squareCount we used in the other script.
Set up the start function to set some of our variables:
mesh = GetComponent<MeshFilter> ().mesh;
col = GetComponent<MeshCollider> ();
After you've done this go to unity and add a Mesh Filter, a Mesh Renderer and a Mesh Collider to our Chunk game object.
Now create two functions:
void CubeTop (int x, int y, int z, byte block) {
}
void UpdateMesh ()
{
}
And call them in the start loop:
void Start () {
mesh = GetComponent<MeshFilter> ().mesh;
col = GetComponent<MeshCollider> ();
CubeTop(0,0,0,0);
UpdateMesh ();
}
CubeTop will be one of six functions that generate a side of the cube and update mesh will commit the verticies and things to the mesh filter. We'll just start with the top to get it working.
Because this is actually very similar to the 2d example I'll take this in some larger steps than normal now, here is what we need in the CubeTop function:
void CubeTop (int x, int y, int z, byte block) {
newVertices.Add(new Vector3 (x, y, z + 1));
newVertices.Add(new Vector3 (x + 1, y, z + 1));
newVertices.Add(new Vector3 (x + 1, y, z ));
newVertices.Add(new Vector3 (x, y, z ));
newTriangles.Add(faceCount * 4 ); //1
newTriangles.Add(faceCount * 4 + 1 ); //2
newTriangles.Add(faceCount * 4 + 2 ); //3
newTriangles.Add(faceCount * 4 ); //1
newTriangles.Add(faceCount * 4 + 2 ); //3
newTriangles.Add(faceCount * 4 + 3 ); //4
Vector2 texturePos;
texturePos=tStone;
newUV.Add(new Vector2 (tUnit * texturePos.x + tUnit, tUnit * texturePos.y));
newUV.Add(new Vector2 (tUnit * texturePos.x + tUnit, tUnit * texturePos.y + tUnit));
newUV.Add(new Vector2 (tUnit * texturePos.x, tUnit * texturePos.y + tUnit));
newUV.Add(new Vector2 (tUnit * texturePos.x, tUnit * texturePos.y));
}
And here is the UpdateMesh Function:
void UpdateMesh ()
{
mesh.Clear ();
mesh.vertices = newVertices.ToArray();
mesh.uv = newUV.ToArray();
mesh.triangles = newTriangles.ToArray();
mesh.Optimize ();
mesh.RecalculateNormals ();
//col.sharedMesh=null;
//col.sharedMesh=mesh;
newVertices.Clear();
newUV.Clear();
newTriangles.Clear();
faceCount=0; //Fixed: Added this thanks to a bug pointed out by ratnushock!
}
What's happening here? Well CubeTop runs first and it creates verticies for a square facing upwards using the x,y,z coordinates, then it creates numbers for the triangles using the faceCount and lastly it applies the texture at the coordinates to the face. For now though texturePos is just set to tStone. We'll add some ifs to set the texture once we have more than one cube.
Now lets hop over to unity and place our gameobjects so that we can run. Put the Chunk at 0,0,0 and set the camera's position y to 10 and rotation x to 45. This should put it dead center.
This is what you should see when you run |
For the rest of the faces the functions will be quite similar, all we'll be doing is adjusting the verticies. Pretty much the rest of the function will be the same for every face so what we'll do is remove the common parts of the functions and put them in a separate function instead of having it written out for each face.
So create a new function called Cube with a Vector2 called texturePos as a parameter, this function will be called for every face and run all the code common to all faces. Move the newTriangles lines to it and the newUV lines to it. Then add "faceCount++" to the end.
void Cube (Vector2 texturePos) {
newTriangles.Add(faceCount * 4 ); //1
newTriangles.Add(faceCount * 4 + 1 ); //2
newTriangles.Add(faceCount * 4 + 2 ); //3
newTriangles.Add(faceCount * 4 ); //1
newTriangles.Add(faceCount * 4 + 2 ); //3
newTriangles.Add(faceCount * 4 + 3 ); //4
newUV.Add(new Vector2 (tUnit * texturePos.x + tUnit, tUnit * texturePos.y));
newUV.Add(new Vector2 (tUnit * texturePos.x + tUnit, tUnit * texturePos.y + tUnit));
newUV.Add(new Vector2 (tUnit * texturePos.x, tUnit * texturePos.y + tUnit));
newUV.Add(new Vector2 (tUnit * texturePos.x, tUnit * texturePos.y));
faceCount++; // Add this line
}
Now your CubeTop function should be a little shorter but call Cube(texturePos); at the end of the function. We decide the texture coordinates in the function unique to each side because the Cube function doesn't have any block data to decide what texture to use and because textures might be based on which face of the cube we're making. Your CubeTop function should look like this now:
void CubeTop (int x, int y, int z, byte block) {
newVertices.Add(new Vector3 (x, y, z + 1));
newVertices.Add(new Vector3 (x + 1, y, z + 1));
newVertices.Add(new Vector3 (x + 1, y, z ));
newVertices.Add(new Vector3 (x, y, z ));
Vector2 texturePos;
texturePos=tStone;
Cube (texturePos);
}
Now we can make the other five functions, they'll look just the same as this one except that they'll use different coordinates for the verticies. Later on they will also decide what textures to use in the unique functions but for now just keep using texturePos=tStone.
Now create 5 new functions with the same content as CubeTop called CubeNorth, CubeEast, CubeSouth, CubeWest and CubeBot but change out the newVerticies lines with these:
//CubeNorth
newVertices.Add(new Vector3 (x + 1, y-1, z + 1));
newVertices.Add(new Vector3 (x + 1, y, z + 1));
newVertices.Add(new Vector3 (x, y, z + 1));
newVertices.Add(new Vector3 (x, y-1, z + 1));
//CubeEast
newVertices.Add(new Vector3 (x + 1, y - 1, z));
newVertices.Add(new Vector3 (x + 1, y, z));
newVertices.Add(new Vector3 (x + 1, y, z + 1));
newVertices.Add(new Vector3 (x + 1, y - 1, z + 1));
//CubeSouth
newVertices.Add(new Vector3 (x, y - 1, z));
newVertices.Add(new Vector3 (x, y, z));
newVertices.Add(new Vector3 (x + 1, y, z));
newVertices.Add(new Vector3 (x + 1, y - 1, z));
//CubeWest
newVertices.Add(new Vector3 (x, y- 1, z + 1));
newVertices.Add(new Vector3 (x, y, z + 1));
newVertices.Add(new Vector3 (x, y, z));
newVertices.Add(new Vector3 (x, y - 1, z));
//CubeBot
newVertices.Add(new Vector3 (x, y-1, z ));
newVertices.Add(new Vector3 (x + 1, y-1, z ));
newVertices.Add(new Vector3 (x + 1, y-1, z + 1));
newVertices.Add(new Vector3 (x, y-1, z + 1));
Now you should have 6 functions and one common function for the faces of the cube. Go to the start function and add the five new functions after CubeTop with the parameters 0,0,0,0 for all of them. If you run it in unity now you should see a cube, not so visible in the camera view but if you look around in the scene view you'll see it.
You should see this |
col.sharedMesh=null;And you'll have a cube with a collision mesh!
col.sharedMesh=mesh;
I'll end this part here, it seems like a logical point to stop but what we have now is more than a cube, what we have is a system to create individual faces which will come in very handy when we're creating a surface that resembles cubes but is actually made of just the visible faces. But that's for next time.
Until then please message me with any problems you find and follow me on twitter or google plus to get updated!
Part 6