How do I get a preview of a prefab I'm about to instantiate?

Hello Unity forums, I have some questions on how to do something. I want to know the process of getting a prefab to preview at mouse location before spawning.

Here is an example picture in GREEN:

I already have a function that spawns a cube to mouse location already. Here is the sample:

 void SpawnCubeAtMouse()
    {
        Plane plane = new Plane(Vector3.up, transform.position);
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        float point = 0f;

        if (plane.Raycast(ray, out point))
        {
            targetPosition = ray.GetPoint(point);
        }
        PhotonNetwork.Instantiate("Cube", targetPosition, Quaternion.identity, 0, null);
    }

Typically you would spawn a dummy version with only the rendering components. You can manipulate the alpha of the render to indicate that the object has not yet been placed. You can manipulate the colour to indicate valid placement positions.

Once the user clicks you then remove your dummy object and replace it with the real object, complete with all the needed components.

2 Likes

Oh that makes sense now, thanks :slight_smile:

Or maybe more simply have your cube have 2 states - placed and not placed.
User selects the cube and it is now spawned in with the not placed state. In this state the color is green and transparent and can be moved around. Once the user clicks to place it, the state changes to placed and now its color changes and it can no longer move.
If the user cancels the placement, then just destroy / move it back to your object pool.
It might be easier to do it this way instead of making multiple versions of your objects.

1 Like

I may have done something wrong, I’m fairly new to coding, and I’m wondering how do I correctly make the object actively follow my mouse, and when I let go of a button, place it where I want. I managed to work with how the materials work, but I can’t seem to do what I mentioned before. Here is my code:

void SpawnCube()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            isPlacing = true;

            Plane plane = new Plane(Vector3.up, transform.position);
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            float point = 0f;

            if (plane.Raycast(ray, out point))
            {
                targetPosition = ray.GetPoint(point);
            }

            GameObject newCube = (GameObject)PhotonNetwork.Instantiate("Cube", targetPosition, Quaternion.identity, 0, null);
            newCubeMaterial = newCube.GetComponent<CubeMaterials>();
            newCubeMaterial.materialIndex = 1;
            //New Cube position needs to follow mouse. Code below doesn't work.
            placingRay = Camera.main.ScreenPointToRay(Input.mousePosition);

            if(Physics.Raycast(placingRay, out placingHit, 5000, buildZone))
            {
                newCube.transform.position = placingHit.point;
            }
            //^Above code doesn't work.
        }
        else if (Input.GetKeyUp(KeyCode.Q))
        {
            newCubeMaterial.materialIndex = 0;
            //Finally, place the object in its final destination, when letting go of Q.
          
            //
        }
    }

Looks like a simple logic problem. GetKeyDown will only return true on the first frame the key goes down. You want something like this. Note I didn’t check the rest of the code, let me know if this wasn’t the problem.

void SpawnCube()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            isPlacing = true;

            Plane plane = new Plane(Vector3.up, transform.position);
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            float point = 0f;

            if (plane.Raycast(ray, out point))
            {
                targetPosition = ray.GetPoint(point);
            }

            GameObject newCube = (GameObject)PhotonNetwork.Instantiate("Cube", targetPosition, Quaternion.identity, 0, null);
            newCubeMaterial = newCube.GetComponent<CubeMaterials>();
            newCubeMaterial.materialIndex = 1;
            //New Cube position needs to follow mouse. Code below doesn't work.
           

        } else if (isPlacing == true){

            placingRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(placingRay, out placingHit, 5000, buildZone))
            {
                newCube.transform.position = placingHit.point;
            }
                    
            if (Input.GetKeyUp(KeyCode.Q))
            {
                newCubeMaterial.materialIndex = 0;
                isPlacing == false;
                //Finally, place the object in its final destination, when letting go of Q.
         
               //
            }
        }
    }

Thanks for the post, but newCube doesn’t exist in the current context after }elseif(isPlacing ==true){

Move the declaration of newCube up a couple of levels

GameObject newCube;

void SpawnCube()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            isPlacing = true;

            Plane plane = new Plane(Vector3.up, transform.position);
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            float point = 0f;

            if (plane.Raycast(ray, out point))
            {
                targetPosition = ray.GetPoint(point);
            }

            newCube = (GameObject)PhotonNetwork.Instantiate("Cube", targetPosition, Quaternion.identity, 0, null);
            newCubeMaterial = newCube.GetComponent<CubeMaterials>();
            newCubeMaterial.materialIndex = 1;
            //New Cube position needs to follow mouse. Code below doesn't work.
         

        } else if (isPlacing == true){

            placingRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(placingRay, out placingHit, 5000, buildZone))
            {
                newCube.transform.position = placingHit.point;
            }
                  
            if (Input.GetKeyUp(KeyCode.Q))
            {
                newCubeMaterial.materialIndex = 0;
                isPlacing == false;
                //Finally, place the object in its final destination, when letting go of Q.
       
               //
            }
        }
    }

As an optimisation you might want to consider coding this as a coroutine, its probably more efficient then calling it every frame. And it means you can still use local scoped variables.

Thanks, I will consider coding this as a coroutine. Works perfectly now, with a small problem, it appears halfway inside my plane (while holding Q) and 50/50 falls through the plane, or jumps up and lands back down(when I let go of Q). Either way, I’m grateful for your help, thanks again :slight_smile:

Im not sure how good this is, maybe mormon can give his opinion, but hopefully this is a proper way to do things.

Click for code

using UnityEngine;

public abstract class PlaceableBehaviour : MonoBehaviour
{
    public bool isPlaced {get; private set;}

    public abstract void OnPreview();
    public abstract bool OnPlace();
    protected abstract bool CanPlace();
    protected abstract void SetPosition(RaycastHit hitInfo);

    public void Place()
    {
        if(OnPlace())
        {
            isPlaced = true;
        }
    }
}
using System;
using UnityEngine;

public class Placeable : PlaceableBehaviour
{
    public Color canPlaceColor;
    public Color cantPlaceColor;

    Renderer myRenderer;
    Material originalMaterial;
    Material temporaryMaterial;
    Collider myCollider;

    void Awake()
    {
        myRenderer = gameObject.GetComponent<Renderer>();
        originalMaterial = myRenderer.sharedMaterial;
        SetupTemporaryMaterial();
        myRenderer.material = temporaryMaterial;

        myCollider = gameObject.GetComponent<Collider>();
        myCollider.enabled = false;
        SetPosition(GetPlacementPosition());
    }

    public override void OnPreview()
    {
        if(isPlaced) return;

        if(CanPlace())
        {
            temporaryMaterial.color = canPlaceColor;
        }else{
            temporaryMaterial.color = cantPlaceColor;
        }

        SetPosition(GetPlacementPosition());
    }

    protected override bool CanPlace()
    {
        if(GetPlacementPosition().collider != null) return true;
        return false;
    }

    public override bool OnPlace()
    {
        RaycastHit hitInfo = GetPlacementPosition();
        if(hitInfo.collider != null)
        {
            SetPosition(hitInfo);
            myRenderer.material = originalMaterial;
            myCollider.enabled = true;
            return true;
        }
        return false;
    }

    protected override void SetPosition(RaycastHit hitInfo)
    {
        if(hitInfo.collider != null)
        {
            transform.position = hitInfo.point; //can do custom placement logic such as adding offsets...
        }
    }

    RaycastHit GetPlacementPosition()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit hitInfo;
        Physics.Raycast(ray, out hitInfo);
        return hitInfo;
    }

    void SetupTemporaryMaterial()
    {
        //Creates standard shader with transparency
        temporaryMaterial = new Material(Shader.Find("Standard"));
        temporaryMaterial.SetFloat("_Mode", 3);
        temporaryMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
        temporaryMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
        temporaryMaterial.SetInt("_ZWrite", 0);
        temporaryMaterial.DisableKeyword("_ALPHATEST_ON");
        temporaryMaterial.DisableKeyword("_ALPHABLEND_ON");
        temporaryMaterial.EnableKeyword("_ALPHAPREMULTIPLY_ON");
        temporaryMaterial.renderQueue = 3000;
    }
}
using System;
using UnityEngine;

public class Placer : MonoBehaviour
{
    public PlaceableBehaviour placeable;
    public KeyCode place = KeyCode.Mouse0;
    public KeyCode select = KeyCode.Alpha1;
    public KeyCode cancel = KeyCode.E;

    PlaceableBehaviour currentSpawned;
    bool hasSpawned;

    void Update()
    {
        if(!hasSpawned)
        {
            if(Input.GetKeyDown(select)) Spawn();
        }else{
            if(Input.GetKeyDown(place)) Place();
            if(Input.GetKeyDown(cancel)) Cancel();

            ShowPreview();
        }
    }

    void Spawn()
    {
        currentSpawned = Instantiate(placeable);
        hasSpawned = true;
    }

    void Place()
    {
        currentSpawned.Place();
        hasSpawned = false;
    }

    void Cancel()
    {
        Destroy(currentSpawned.gameObject);
        hasSpawned = false;
    }

    void ShowPreview()
    {
        currentSpawned.OnPreview();
    }
}

So, we have a PlaceableBehaviour which will be the base of all your placeable objects, and then we have an example Placeable object and the Placer.

The Placeable is an example of what you can do. You can probably set this component up better so it can handle all sorts of things.
You create a Placeable object prefab and place this Placeable component on it and set the colors up. I can set canPlaceColor to a transparent green and cantPlaceColor to a transparent red in the inspector.
I think I set things up correctly so that we dont create instance materials for each object you place so that batching still works.

You then put on your player or something, the Placer component. Press 1 to select/spawn the item, move mouse around to choose a spot, press left mouse to place or E to cancel.
You would probably want to set it up in a way so you can spawn different types of placeables by using an array of PlaceableBehaviours.

EDIT - I am getting a weird material bug where at certain positions, such as on a wall, the temporary material is doing weird things such as being transparent at certain angles. Only happens when I use the transparent option on the standard shader.
If I update it by making the temporaryMaterial public and setting it to opaque and back to transparent, it then shows properly. Weird.
Edit - I found a way around the bug by instead of copying the originalMaterial, I just create a new transparent material from scratch (got help in doing that from this site Sassybot)

As a cheap hack for this you can have an parent object on your prefab that sits in the position you want to hit the terrain. Everything else can move relative to that.