Building script

Hi there!

I want to make a game where I can build objects in the scene, I found a small script here somewhere which I tried to convert from JavaSript to C#. Im only getting an error for the last bit of code, which returns a value, but I dont know what the method type should be. I’m pretty new to coding so forgive me if it’s a simple mistake…

Anyway… Here is the code:

using UnityEngine;
using System.Collections;

public class building : MonoBehaviour {

public LayerMask blockLayer = 1; 
public float range = Mathf.Infinity; 
public RaycastHit hit;
	
	
void Start () {
		
}
	

void Update () { 
if (Input.GetMouseButtonDown(0)) Build(); 
if (Input.GetMouseButtonDown(1)) Erase(); 
}

void Build() { 
if (HitBlock()) 
	{ 
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); 
cube.transform.position = hit.transform.position + hit.normal; 
	} 
}

void Erase() { 
if (HitBlock())
	{
		Destroy(hit.transform.gameObject); 
	}
}

bool HitBlock() {
return Physics.Raycast(transform.position, transform.forward, hit, range, blockLayer); 
}
}

I want to extend this code later on to be able to do the following:

  • switch game mode between building and fighting (with keycode)
  • choose from different object and walls to build with (for example with a GUI)

If you could give me some tips on that, It would be much apreciated

Thanks!:smile:

What do you exactly need tips for? Is this your first project?
I’d go for something more simple in that case, because you’re not going to be finishing this I’ll tell you.

I’ve player around with unity for some time now, and I’m currently trying to learn C#, but I’ve encountered this problem and a referencepoint I was using before (unify community, which has some good tutorials on C#) is down ATM. The main thing is to get rid of this error Im getting, it says:

Assets/Blockbuilding.cs(37,16): error CS1502: The best overloaded method match for `UnityEngine.Physics.Raycast(UnityEngine.Vector3, UnityEngine.Vector3, out UnityEngine.RaycastHit, float, int)’ has some invalid arguments

Assets/Blockbuilding.cs(37,16): error CS1620: Argument #3' is missing out’ modifier

Im not sure what I’ve to put in front of the HitBlock method, because if I remove the “bool” in front of it, it says:

Assets/Blockbuilding.cs(36,1): error CS1520: Class, struct, or interface method must have a return type

Any ideas on that?

Put the keyword ‘out’ in front of the third argument, as it says.

Thanks! this solved the error! :smile:

I haven’t encountered this keyword yet so I’ve learned something new I guess :smile:

So now it’s creating a basic cube when I’m looking at a surface and press the build button, but how can I have it to place a prefab?

Thanks for the reply! will try it out soon.

Today I’ve made some progress adding a buildmode and , for now, shoot mode. I made it so I can switch between those modes with a key. If building mode is active I can place and delete object, if building mode is not active, I can shoot projectiles. But now I want to be able to disable object erase for certain objects, the terrain for example. But since I’m using a Raycast I’m not sure how I can check if the object is valid to remove. This is the script as it is now:

using UnityEngine;
using System.Collections;

public class Blockbuilding : MonoBehaviour {

public LayerMask blockLayer = 1; 
public float range = Mathf.Infinity; 
public RaycastHit hit;
public bool buildMode = false;
public GameObject target;
	
	
void Start () {
}
	

void Update () { 
if (Input.GetKeyDown(KeyCode.M)  buildMode == false)
		{
			buildMode = true;
		}
else if (Input.GetKeyDown(KeyCode.M)  buildMode == true)
		{
			buildMode = false;
		}


if (Input.GetMouseButtonDown(1)  buildMode == true) Build(); 
if (Input.GetMouseButtonDown(0)  buildMode == true) Erase(); 
}

void OnGUI()
	{
		GUILayout.Label("buildmode" + buildMode);
	}

void Build() { 
if (HitBlock()) 
	{ 
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); 
cube.transform.position = hit.transform.position + hit.normal; 
	} 
}

void Erase() { 
if (HitBlock())
	{			
		if (target != target.gameObject.CompareTag("Terrain"))
			{
		Destroy(hit.transform.gameObject); 
		}
	}
}
	
bool HitBlock() {
return Physics.Raycast(transform.position, transform.forward, out hit, range, blockLayer); 
}
}

I’ve tried A few things as you may notice in the Erase method. But it’s not working :frowning:

Any ideas on how to do this?

first off, a piece of advice:

if (Input.GetKeyDown(KeyCode.M)  buildMode == false)
		{
			buildMode = true;
		}
else if (Input.GetKeyDown(KeyCode.M)  buildMode == true)
		{
			buildMode = false;
		}

can be changed to

if (Input.GetKeyDown(KeyCode.M)) buildMode = !buildMode;

As for placing these cubes in your world, I would suggest making prefabs so you can set up your geometry and layers like you want. You’re not actually assigning your new cube to a layer.

As far as why you cant erase, my best guess is that

public GameObject target;

is never assigned, and you check against it in your erase function.

You can get around this by checking the hit.transform.gameObject in your Erase function, or assign that to the target variable in your HitBlock function.

Edit: nevermind

Thanks for the help. Also turned out that if I was trying to delete the terrain, the Raycast detected the player itself and deleted it, leaving nothing behind. I can now tag objects I dont want to be able to delete with a certain tag.

Thanks again! I’m slowly getting better in programming :smile:

EDIT: The code is now able to build with prefabs, instead of basic objects. Now I’m going to implement support for different objects, with GUI.

Also, I would like to give the player a “preview” of what he/she is going to build, before actually building it. The problem is, I have no Idea how to do this. Should I constantly let a semi transparent prefab follow the raycast when buildmode is true?

The way I would do that is have separate ‘preview’ gameobjects, one for each block type. Keep them inactive until the player enables block mode and selects a block type. When they do that just enable the matching ‘preview’ gameobject and put it where the player is currently pointing (where the ray is pointing). When they click, disable the preview object and follow your normal process for adding a real block to the world.

There are multiple ways to do it so this is just one suggestion.

But do you understand why this occurred? In your erase function you are checking the target’s tag. But the target is just a null gameobject; it has no tag so that if function is always true.

I would also suggest using layers instead of tags to separate your objects when dealing with physics interactions. I believe it is more efficient than querying for the gameobject’s tag. I could be wrong though but thats my understanding.

That was indeed my same thought. I only dont know how and where I should enable the preview block.
My first thought was to do this in the Update function, checking if the buildmode is true and if it is, display the preview of the currently selected block to build. But I’m pretty much sure there’s a better method for this. I don’t think this is an efficient one. Any suggestions on that? Here’s the code so far:

using UnityEngine;
using System.Collections;

public class Blockbuilding : MonoBehaviour {

public LayerMask blockLayer = 1; 
public float range = Mathf.Infinity; 
public RaycastHit hit;
public bool buildMode = false;
public GameObject buildPrefab;

public int boxWidth = 10;
public int boxHeight = 10;
public int boxY = 10;
public int boxX = 10;
	
void Start () {
}
	

void Update () { 
if (Input.GetKeyDown(KeyCode.M)  buildMode == false)
		{
			buildMode = true;
		}
else if (Input.GetKeyDown(KeyCode.M)  buildMode == true)
		{
			buildMode = false;
		}


if (Input.GetMouseButtonDown(1)  buildMode == true) Build(); 
if (Input.GetMouseButtonDown(0)  buildMode == true) Erase(); 
}

void OnGUI()
	{
		GUI.Box(new Rect(boxX, boxY, boxWidth, boxHeight), "buildmode " + buildMode);
	}

void Build() { 
if (HitBlock()) 
	{ 
	//check if build location is valid and 2 meters away from player
if (hit.transform.gameObject.tag == "Build"  hit.distance > 2) {
Instantiate(buildPrefab, hit.transform.position + hit.normal, buildPrefab.transform.rotation);
	} 
}
}

void Erase() { 
if (HitBlock())
	{
//		Debug.Log(hit.transform.gameObject.tag);
		if (hit.transform.gameObject.tag == "Terrain" || hit.transform.gameObject.tag == "Player"){
				Debug.Log("Invalid object");
			}
//		if(hit.transform.gameObject.tag == "Build")
		else{
		Destroy(hit.transform.gameObject); 
		}
	}
}
	
bool HitBlock() {
return Physics.Raycast(transform.position, transform.forward, out hit, range, blockLayer); 
}
}

For the layers you’re talking about, I don’t exactly know what it does. Could you reference or explain it to me?

thanks :smile:

If you have buttons on the GUI that let them select build mode, and then a type of block, I would enable the correct ‘preview block’ gameobject when they click on the button that selects the block type. That is assuming after they select the block type they are then free to place their block.

You should read up on layermaks. They are an easy way to control and group objects in your scene:

For instance if you put your terrain on its own layer, you could then pass that layer in as a parameter when you cast rays so that it is the only item checked against. same with your blocks, player, etc.

There is also an ‘ignore raycast’ layer by default in unity that will cause unity to ignore any objects that are on that layer when evaluating raycasts.

Thanks, I will look into the layers when I’ve got some bugs fixed. I made a new prefab with other dimensions. The object I was using earlier was assuming the object is of the same size on all sides (a cube for example). So now when I use a cube which is scaled on 1 axis, the objects get placed in each other. This is obviously not what I want. So, should I just make a value for each building object with the dimensions build in, or should I make some kind of grid which the buildingscript should follow? I prefer to not use the last option, because I think that takes away the flexibility of building. Maybe a snap to grid toggle option?