Help with prefab spawning script

Hi there hello,

I’m trying to create a prefab spawning script that when the player clicks it will spawn a prefab but it can only spawn the prefab on a surface. However, my code below has a problem where it keeps thinking that the prefab is not being spawned on a valid surface. How do I fix this?

Here is my code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnPrefab : MonoBehaviour
{
    public GameObject spawnPrefab;
    public float spawnDistance = 2.0f; // Adjust this distance as needed

    void Update()
    {
        if (Input.GetMouseButtonDown(0)) // Left mouse button is pressed
        {
            SpawnPrefabInDirection();
        }
    }

    void SpawnPrefabInDirection()
    {
        // Get the camera's position and direction
        Camera mainCamera = Camera.main;
        Vector3 cameraPosition = mainCamera.transform.position;
        Vector3 cameraDirection = mainCamera.transform.forward;

        // Calculate the spawn position in front of the camera
        Vector3 spawnPosition = cameraPosition + cameraDirection * spawnDistance;

        // Raycast to determine the surface normal in both directions
        RaycastHit hitDown;

        Quaternion rotation; // Declare the rotation variable

        RaycastHit hitForward = new RaycastHit(); // Initialize the hitForward variable

        if (Physics.Raycast(cameraPosition, Vector3.down, out hitDown) || Physics.Raycast(cameraPosition, cameraDirection, out hitForward))
        {
            if (hitDown.collider != null || hitForward.collider != null)
            {
                // Choose the hit point that is closest to the camera
                if (Vector3.Distance(cameraPosition, hitDown.point) < Vector3.Distance(cameraPosition, hitForward.point))
                {
                    spawnPosition = hitDown.point; // Set the spawn position to the ground hit point
                    rotation = Quaternion.FromToRotation(Vector3.up, hitDown.normal);
                }
                else
                {
                    spawnPosition = hitForward.point; // Set the spawn position to the forward hit point
                    rotation = Quaternion.LookRotation(hitForward.normal, Vector3.up);
                }

                // Check if there's a collider beneath the spawn point
                if (IsSpawnPointValid(spawnPosition))
                {
                    Debug.Log("Spawn point is valid. Spawning at " + spawnPosition);
                    GameObject newPrefabInstance = Instantiate(spawnPrefab, spawnPosition, rotation);
                }
                else
                {
                    Debug.Log("Spawn point is not valid.");
                }
            }
        }
        else
        {
            Debug.Log("No surface hit.");
        }
    }

    bool IsSpawnPointValid(Vector3 spawnPoint)
    {
        RaycastHit hit;
        if (Physics.Raycast(spawnPoint, Vector3.down, out hit, 1.0f)) // Use a raycast distance (e.g., 1.0f)
        {
            return true; // A collider is beneath the spawn point
        }
        return false; // No collider is found beneath the spawn point
    }
}

Hi Smoothbrick578, I found 2 issues in the script…

  1. At line 40, if either hitDown.collider or hitForward.collider is null, then the distance comparison will be invalid, e.g. if hitDown.collider is null, then hitDown.point will be (0, 0, 0).
    Maybe you can modify it as follows:
float distanceToDownHitPoint = hitDown.collider == null ? Mathf.Infinity : Vector3.Distance(cameraPosition, hitDown.point);

float distanceToForwardHitPoint = hitForward.collider == null ? Mathf.Infinity : Vector3.Distance(cameraPosition, hitForward.point);

// Choose the hit point that is closest to the camera
//if (Vector3.Distance(cameraPosition, hitDown.point) < Vector3.Distance(cameraPosition, hitForward.point))
if (distanceToDownHitPoint < distanceToForwardHitPoint)
{
...
}
else
{
...
}
  1. At line 72, if the spawnPoint is hitDown.point or hitForward.point, then it is already on the surface of the hit collider, so raycast from this point downward will not hit that collider any more.
    Maybe you can skip checking IsSpawnPointValid(spawnPosition) if this spawnPosition is a raycast hit point?
    Or you may slightly shift up the spawnPoint as follows:
Physics.Raycast(spawnPoint + Vector3.up * 0.1f, Vector3.down, out hit, 1.0f)