Megabite #19 - Working with Grids (part 1)

Theres an obvious popularity in the world of independent development when it comes to grid-based games, or worlds constructed of blocks or squares.  Because Unity has no easy one-click functionality for such a thing, I wanted to start a project and show the world how I went about achieving something similar without using a voxel engine.

The results of what I have so far come out something like this:

As you can see, the ground has a grid pattern, and the blocks fit atop the terrain quite nicely.  I had a small assortment of default textures assigned as well as a few free downloads from the Unity Asset store, and I'll be releasing all the important code for this project here so that it can be clearly seen and utilized.

As a note, this is not 100% finished - this is a work in progress but was far enough along that I could demonstrate the usage and ability of the code to create blocks, assign a shader, and batch the draw calls despite changing the renderer on-the-fly.

There are only 3 total components in this scene:

  • A camera
  • a directional light
  • a piece of terrain measuring 600px x 600px

You can see in the picture here that this is all very basic - it was designed to be.  The cubes in this project are not prefabs - they are generated on-the-fly via scripting.  The width I chose for my cube world was 3 units, and you can see via the picture above that the "good dirt" texture is set to simply tile every 3 units.  It was offset by 1.5 in both directions simply so that we can start block placement at Vector3(0,0,0) without confusion.

To make the texture appear as a grid, I simply opened the texture in photoshop and placed a small black border around the picture.

Only 2 scripts run this project - one to move the camera however we like, and the other runs the block generation.  Instead of placing blocks simply one at a time, the script is fully capable of drawing lines in both horizontal and vertical directions.  Eventually the script will allow clicking and dragging out a grid which will place a square or rectangle of individual blocks as well.

Below I have pasted the entire code that generates the blocks:

private var readyClick : boolean = true;
var currentInfo : Transform;
private var lastBlock : Transform;
var cube : Transform;
var boxTex : Texture[];
private var curPos : Vector3;
private var blockNum : int;
private var blockArr = new Array();
var shader : Shader;
function Start() {
 for (var x=0;x<boxTex.Length;x++) {
 var cube : GameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
 cube.transform.localScale = Vector3(3,3,3);
 var theMat = new Material(Shader.Find(" Diffuse"));
 cube.renderer.material = theMat;
 cube.renderer.sharedMaterial.mainTexture = boxTex[x];
 blockArr.Push(cube);
 }
}
function Update() {
 var point : Vector3;
 var theRay: Ray = Camera.main.ScreenPointToRay(Input.mousePosition);
 var theHit: RaycastHit;
 if (Physics.Raycast(theRay, theHit)) {
 mousePoint = theHit.point;
 point = GetTerPoint(theHit.point, 1.5);
 if (theHit.transform.name == "Terrain" && dragOn == false) {
 ClearLast();
 theCube = Instantiate (cube, point, Quaternion.identity);
 lastBlock = theCube;
 curPos = point;
 }
 }
//This is the code that would place a single block instead of allowing dragging
// if (Input.GetKey("mouse 0") && curTex != null && point.y != 0) {
// Instantiate (blockArr[blockNum], point, Quaternion.identity);
// print(blockNum);
// }
 
 if (Input.GetKey("mouse 0") && dragOn == false) {
 dragOn = true;
 startPoint = point;
 DoDrag(startPoint);
 }
 var xPt : int = 3 * (Mathf.Floor(mousePoint.x / 3));
 var zPt : int = 3 * (Mathf.Floor(mousePoint.z / 3));
}
private var startPoint : Vector3;
private var mousePoint : Vector3;
private var dragOn : boolean = false;
private var dragNum : int;
function DoDrag(start : Vector3) {
 tempCube = Instantiate (cube, start, Quaternion.identity);
 yield StartDrag(start);
 Destroy(tempCube.gameObject);
 dragOn = false;
}
function StartDrag(start : Vector3) {
 while (true) {
 while (Input.GetKey("mouse 0")) yield;
 var rawNum : int;
 var numBlocks : int;
 if (mousePoint.x > start.x + 3) {
 //do right side blocks
 rawNum = (3 * (Mathf.Floor(mousePoint.x / 3))) - start.x;
 print("raw: " + rawNum);
 numBlocks = rawNum /3;
 for (x = 0; x < numBlocks; x++) {
 Instantiate (blockArr[blockNum], Vector3(start.x + (x*3), start.y, start.z), Quaternion.identity);
 }
 }
 if (mousePoint.x < start.x + 3) {
 //do left side blocks
 rawNum = (3 * (Mathf.Floor(mousePoint.x / 3))) - start.x;
 print("raw: " + rawNum);
 numBlocks = -(rawNum /3);
 for (x = 0; x < numBlocks; x++) {
 Instantiate (blockArr[blockNum], Vector3(start.x - (x*3), start.y, start.z), Quaternion.identity);
 }
 }
 if (mousePoint.z > start.z + 3) {
 //do up side blocks
 rawNum = (3 * (Mathf.Floor(mousePoint.z / 3))) - start.z;
 print("raw: " + rawNum);
 numBlocks = rawNum /3;
 for (x = 0; x < numBlocks; x++) {
 Instantiate (blockArr[blockNum], Vector3(start.x, start.y, start.z+ (x*3)), Quaternion.identity);
 }
 }
 if (mousePoint.z < start.z + 3) {
 //do down side blocks
 rawNum = (3 * (Mathf.Floor(mousePoint.z / 3))) - start.z;
 print("raw: " + rawNum);
 numBlocks = -(rawNum /3);
 for (x = 0; x < numBlocks; x++) {
 Instantiate (blockArr[blockNum], Vector3(start.x , start.y, start.z - (x*3)), Quaternion.identity);
 }
 }
 return;
 }
}

 

function ClearLast() {
 if (lastBlock) {
 Destroy(lastBlock.gameObject);
 }
}
function GetTerPoint(thePoint : Vector3, height : float) {
var numX : float = 3 * (Mathf.Floor(thePoint.x / 3));
var numZ : float = 3 * (Mathf.Floor(thePoint.z / 3));
var theVec : Vector3;
theVec = Vector3(numX,height,numZ);
return theVec;
}
private var curTex : Texture;
function OnGUI () {
GUI.Box(Rect(0,0, Screen.width,50), "");
 for (var x = 0; x < boxTex.Length; x++) {
 if (GUI.Button(Rect(2 + (x * 50),2,48,48), boxTex[x])) {
 SwitchTex(x);
 }
 }
GUI.Box(Rect(0,53,50,50), "");
if (curTex) {
 GUI.DrawTexture(Rect(2,55,48,48), curTex);
}
}
function SwitchTex(num : int) {
 curTex = boxTex[num];
 blockNum = num;
}

A few explanations.  The only Prefab you see here is for a temporary cube.  This object shows up while the mouse is touching the terrain, and it shows where blocks will be placed.  Because the ability to draw out a rectangle while dragging is not complete, right now the code results in drawing out an L shape.  There are also no safeguards to keeps blocks from drawing on top of each other for now.

I'll continue to build on this project and post all of the code here within the Megabite articles.  When it is finished, what I want to have ready is a web-based (or downloadable) project that just allows a user to build whatever they like very quickly - whether it be a castle, a terrain, etc.

If anyone tries the code out or would like to help me expand upon the idea, simply let me know in the comments below :)