In-Game Object Placement

Okay So I been looking all over the web for the past 2 days and have had zero luck. I find videos on what other people have created, but nothing on how they created it. What I am looking for a is a tutorial or even just some pointers or tips on how to create an in-game object placement script that allows the player to place the selected object in the game. However, I want them to only be able to place on a grid that they have to purchase (Ex: purchase a piece of property then place on the available grid spaces)

If anyone knows of a such tutorial please point me to it. Thanks.

I will point you in the direction of the places in the unity script references that you should look, but I will not make these scripts for you, and I will not encourage the use of Unity Answers as an alternative to Google (see the tutorial video on how to use Unity Answers).

There are essentially two aspects you should consider before deciding how you want to create GameObjects and components, at runtime (i.e. after the scene has been loaded).

  1. How large/diverse is the set of potential objects to be placed in game?
  2. How complex are the individual objects?

Prefabs are by far the defacto standard for getting complex objects into a game, but they are not always appropriate.

If you have a plethora of simplistic, but diverse objects to choose from, you may want to consider simply creating new GameObjects and adding components to them, manually.

Keep in mind that no matter what, instantiating GameObjects and populating them with components (as well as instantiating prefabs) are relatively computationally expensive operations, and it is not suggested that you attempt to instantiate a large number of them during normal gameplay, as it can produce quite a bit of lag, not to mention the memory they would take up.

Essentially what you’re trying to do (if you break it down into simpler steps) is to take the player, figure out where they are, and use that along with the rotation of the camera attached to them to figure out what they are looking at, and use that to approximate a grid position. This would be best done with a RayCast.

Averaging that down to a position on a grid is just a matter of deciding how big your grid spaces are, with respect to each axis, and doing a bit of algebra. The simplest version of this would likely just truncate the float position on each axis to an integer cough cast cough and call that the current space.

How you decide how to select an object is more up to you to decide. That could vary anywhere from some sort of GUI system to literally walking up to a specific object (in 3D space) and performing some manner of input (say, pressing a key or clicking a mouse button).

The GUI would be the “simplest” way, but the 3D space version would be essentially the opposite version of the placement part of the script, so it may actually be easier to program and design.

However, if you want more detailed answers, I would strongly suggest asking more detailed questions. Giving the ol’ “pizza order” request, listing off a bunch of features you want your game/mechanic/script to have, then saying “point me to someone who will show me how to make that” or “make it for me” or anything along those lines is not going to illicit a great response from most of the users here who would know how to fulfill your request, because the purpose of this place is not to give people free code. There are plenty of other places for that sort of thing (such as the Unify Wiki) and it’s certainly not to be used as a replacement for a simple Google search.

I really don’t think that “Let me google that for you” I appropriate. I also don’t like that there really is a lack of information on how do this professionally (i.e. create a preview of the object that’s transparent before snapping in, etc).

I do, however, love that it links back to this thread as the first result.

If someone is looking for inspiration or ideas for how to accomplish in-game object placement, you might find this script helpful. It’s designed for a FPS game, not RTS. It doesn’t feature a grid, but I think you could tweak it to make it work for that. Note that the overlap sphere is hardcoded, and so it may not work for every situation. This should just be used as a starting place anyway, not a fully polished script to toss in a game. One could try to generalize this to walls and not just horizontal surfaces by using RaycastHit.normal.

using UnityEngine;
using UnityEngine.InputSystem;

// place this on camera
public class ObjectPlacer : MonoBehaviour
{
    public GameObject ghostPrefab; // prefab displayed when finding placement
    public GameObject placementPrefab; // prefab instantiated
    public GameObject ghostParent; // parent of ghost (place 3 units in front of camera as child)

    public bool placingObject; // set to true to see ObjectPlacer working

    public Material ghostMaterialValid; // material set when placement is valid 
    public Material ghostMaterialInvalid;

    private MyCharacterInput characterInput; // new input system

    InputAction confirmAction; // confirm placement
    InputAction cancelAction; // cancel placement

    bool wasValidPosition = false;
    int floorLayer = 14; // layer that objects should be placed on

    MeshRenderer[] renderers;
    SkinnedMeshRenderer[] sRenderers;
    GameObject ghostInstance;


    // set up player input and instantiate ghost
    void Awake()
    {
        characterInput = new MyCharacterInput();
        if (placingObject)
        {
            startPlacing();
        }
    }

    private void OnEnable()
    {
        confirmAction = characterInput.Gameplay.Action;
        confirmAction.performed += performInteract;
        confirmAction.Enable();

        cancelAction = characterInput.Gameplay.ClosePanel;
        cancelAction.performed += stopPlacing;
        cancelAction.Enable();
    }

    // create ghost and set material
    public void startPlacing()
    {
        ghostInstance = GameObject.Instantiate(ghostPrefab, ghostParent.transform);
        renderers = ghostInstance.transform.GetComponentsInChildren<MeshRenderer>();
        sRenderers = ghostInstance.transform.GetComponentsInChildren<SkinnedMeshRenderer>();
        if (wasValidPosition)
        {
            setGhostMaterial(ghostMaterialValid);
        }
        else
        {
            setGhostMaterial(ghostMaterialInvalid);
        }
    }

    void setGhostMaterial(Material newMaterial)
    {
        for (int i = 0; i < renderers.Length; i++)
        {
            Material[] mats = new Material[renderers*.materials.Length];*

for (int j = 0; j < mats.Length; j++)
{
mats[j] = newMaterial;
}
renderers*.materials = mats;*
}

for (int i = 0; i < sRenderers.Length; i++)
{
Material[] mats = new Material[sRenderers*.materials.Length];*
for (int j = 0; j < mats.Length; j++)
{
mats[j] = newMaterial;
}
sRenderers*.materials = mats;*
}
}

// call to end placement process
public void stopPlacing(InputAction.CallbackContext obj)
{
placingObject = false;
GameObject.Destroy(ghostInstance);
}

// called when player confirms placement via input
private void performInteract(InputAction.CallbackContext obj)
{
if (wasValidPosition && placingObject)
{
// instantiate prefab where ghost was
GameObject.Instantiate(placementPrefab, ghostInstance.transform.position, ghostInstance.transform.rotation);
}
}

// core functionality is here
void FixedUpdate()
{
if (placingObject)
{
int detectLayer = 1 << floorLayer;
RaycastHit hit;
// check if ray hits floor
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, 3.0f, detectLayer))
{
// move placement according to hit
ghostInstance.transform.position = hit.point;
ghostInstance.transform.rotation = Quaternion.identity;

// use overlap sphere to check if placement is invalid due to overlap with colliders
// sphere is positioned a little above the floor
Vector3 spherePosition = new Vector3(
ghostInstance.transform.position.x,
ghostInstance.transform.position.y + .6f,
ghostInstance.transform.position.z
);
Collider[] hitColliders = Physics.OverlapSphere(spherePosition, .3f);
bool validPlacement = true;
foreach (var hitCollider in hitColliders)
{
// if the name isn’t one that we should ignore and it isn’t a trigger
if (!(nameMatchesPrefab(hitCollider.name)) && !(hitCollider.GetComponent().isTrigger))
{
validPlacement = false;
Debug.Log("collider overlap " + hitCollider.name);
}
}
// if placement is valid
if (validPlacement)
{
// if it wasn’t previously a valid placement, it is now
if (!wasValidPosition)
{
setGhostMaterial(ghostMaterialValid);
wasValidPosition = true;
}
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.blue);
}
else
{
Debug.Log("cannot place because of collider overlap ");
// if it was previously a valid placement, it isn’t anymore
if (wasValidPosition)
{
setGhostMaterial(ghostMaterialInvalid);
wasValidPosition = false;
}
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.magenta);
}
}
// else raycast didn’t hit any floor layer colliders
else
{
// if it was previously a valid placement, it isn’t anymore
if (wasValidPosition)
{
setGhostMaterial(ghostMaterialInvalid);
ghostInstance.transform.localPosition = Vector3.zero;
wasValidPosition = false;
}
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 3.0f, Color.red);
}
}

// does collider name match any colliders in prefab
bool nameMatchesPrefab(string colliderName)
{
bool match = false;
if (colliderName.StartsWith(ghostPrefab.name))
{
match = true;
}
Collider[] colliders = ghostPrefab.GetComponentsInChildren();
for (int i = 0; i < colliders.Length; i++)
{
if (colliderName == colliders*.gameObject.name)*
{
match = true;
}
}

return match;
}

}
private void OnDisable()
{
confirmAction.Disable();
cancelAction.Disable();
}

}