What to expect from this blog post
There are very few resources on this topic and most of them actually suck or include too many bulky scripts. So, we’re going to write a simple script that will be less than 50 lines long which can be placed in any scene of any 3d project that you make in Unity. The script will basically allow you to preview and place a GameObject of your choice (for e.g, a Cube) at a position determined by the placement of your cursor. The script can be expanded upon to use more than 1 GameObject and switch between those. This will primarily be a guide for mobile (because I primarily publish Android games, duh) but I’ll explain the changes to make to allow full compatibility with desktop/console.
Here’s something to give you an idea of what an implementation of it looks like in a game:
The name of the game is SquareVille (yeah, shameless promotion smh).
Time to code
The code will be explained just below if that’s of any interest to you. Else, just make a script
named sandbox
and copy and paste the code in it. Just drag it on an empty GameObject in
your scene. To test if the script works, create a Cube in your scene, drop it on the targetObject
field of the script component, assign a material in the opaqueMat
field, create a UI
button in your scene and reference its OnClick()
function to call the PlaceObject()
function
of the sandbox
component on the GameObject it’s on.
Basically, when you pan around (if your Camera Controller can do that), the GameObject referenced in
the targetObject
field will be dragged and snapped around and upon clicking the UI button that
calls the PlaceObject()
function, a copy of that GameObject will be made and it will be assigned
the material you referenced in the opaqueMat
field, just so you know, to have a different color
and some visual feedback.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class sandbox : MonoBehaviour {
Vector3 targetPosition, mousePosition;
public Transform targetObject;
public float distance = 10f;
public Material opaqueMat;
List<Vector3> placedPositions = new List<Vector3>();
void Start() {
mousePosition = new Vector3(Screen.width / 2, Screen.height / 1.5f, 0);
}
void Update () {
// mousePosition = Input.mousePosition;
targetPosition = Camera.main.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, distance));
if (targetPosition.y < 1) targetPosition.y = 1;
targetPosition = Vector3Int.RoundToInt(targetPosition);
if (!placedPositions.Contains(targetPosition)) {
targetObject.position = targetPosition;
}
}
public void PlaceObject() {
Transform newInstance = Instantiate(targetObject, targetObject.transform.position, targetObject.transform.rotation);
Material[] intMaterials = new Material[newInstance.GetComponent<Renderer>().sharedMaterials.Length];
for (int i = 0; i < intMaterials.Length; i++)
intMaterials[i] = opaqueMat;
newInstance.GetComponent<Renderer>().sharedMaterials = intMaterials;
placedPositions.Add(targetObject.position);
}
}
I don’t think I should be explaining the 3 first lines and the class declaration statement.
In the sandbox
class, I’m declaring 2 (private) variables of the Vector3 type, a public variable
holding a float value named distance
(pretty self-explanatory) and a Vector3 List named placedPositions
which you’ll understand the utility of later. The other variables and their use have already been stated in
the paragraph(s) before the code was given.
In the Start()
function (which runs once per script when the game starts), we will process and store a Vector3 value
in the mousePosition
variable. This value corresponds to a position slightly above the center point
of the screen that the game is being played on. As screens are normally 2D, the Z-Axis value is assigned a value of 0.
In the Update()
function (which runs every frame), you can uncomment the first line to allow for desktop
input by actually moving the cursor. If you’re making the game for mobile, keep this commented so that the
script always ‘believes’ that the cursor is always a bit above the center point of the screen, allowing you
to pan your Camera Controller to change the position where you want objects to be spawned. For console,
you just have to change the x and y positions of the mousePosition
variable depending on a joystick input.
Based on the value held in mousePosition
, we are going to translate and store actual 3D world coordinates
in the targetPosition
variable and at a Z-Position determined by the value in our distance
variable.
Next, we check whether the Y-Position of the Vector3 value held in targetPosition
is less than 1, which is
not something we want since then our object will be spawned below the ground (assuming you have one). We will
also want to round all the values held in targetPosition
to the closest integer so that our object always
snaps to some kind of invisible grid made up of 1 x 1 x 1 units. You can comment this line to disable snapping
or modify the algorithm to use a custom grid snap value.
Then, we check if the placedPositions
list contains a Vector3 value equal to that of targetPosition
.
If not (hence the “!” sign), then we want to move our template GameObject (or the Cube
that we used as example in the paragraph(s) above the code) at that position. Else, the template GameObject
will remain where it was previously. This check is done so that situations where objects are placed at the same
position or overlap edges/faces of other objects never occur (big brain huh, jk).
Finally, PlaceObject()
can be called using a reference in the OnClick()
function of a simple UI Button.
That function will instantiate (spawn) a copy of targetObject
(our template GameObject) at its current
position and rotation. The renderer of that new instance will also be referenced and assigned the material
of opaqueMat
(named that variable like that because I was using a semi-transparent one for the template).
Lastly, we are adding the position where newInstance
was spawned to the placedPositions
list for the
reason discussed in the previous paragraph.
Hope I could help you! Drop me a mail if you didn’t understand something.