Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How to make a prefab only spawn on a particular object?

Discussion in 'Scripting' started by Sentokoo, Nov 9, 2020.

  1. Sentokoo

    Sentokoo

    Joined:
    Oct 6, 2020
    Posts:
    5
    I'm trying to make a 3D game similar to Crazy Taxi, but it will be a meal delivery service where you pick up food from one location and then deliver it to another location.

    Right now the food and dropoff points can spawn in random locations, and are only constrained within a certain X and Z range.

    However this leads to a problem: they can spawn inside of buildings, trees, and other obstacles.

    My proposed solution is to only allow my food and dropoff points to spawn on objects tagged as "road". But I'm not sure how to accomplish this.

    Other option is that I could tag all buildings, trees, fountains, etc as "scenery" and then make it do a check that it's not trying to spawn in an area occupied by one of these objects?

    In 2D games there is the option to paint with tilemaps to say what kind of areas are and aren't accessible... but I'm less familiar with 3D games and I don't know if this is an option?

    Mostly I want to avoid having to individually code each space that objects can or cannot spawn.

    Here is my current code:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SpawnManager : MonoBehaviour
    6. {
    7.     public GameObject food;
    8.     public GameObject dropoff;
    9.     private float spawnRangeX = 40;
    10.     private float spawnPosZ = 40;
    11.     public bool foodOnScreen = false;
    12.  
    13.     // Update is called once per frame
    14.     void Update()
    15.     {
    16.         if (foodOnScreen == false)
    17.         {
    18.             Invoke("SpawnRandomFood", 1);
    19.             foodOnScreen = true;
    20.         }
    21.         }
    22.     void SpawnRandomFood()
    23.     {
    24.         Instantiate(food, new Vector3(Random.Range(-spawnRangeX, spawnRangeX), 7, Random.Range(-spawnPosZ, spawnPosZ)), food.transform.rotation);
    25.     }
    26.  
    27.     public void SpawnDropoff()
    28.     {
    29.         Instantiate(dropoff, new Vector3(Random.Range(-spawnRangeX, spawnRangeX), 7, Random.Range(-spawnPosZ, spawnPosZ)), dropoff.transform.rotation);
    30.     }
    31.  
    32. }
    33.  
    I have a separate DetectCollisions script which deals with creating and destroying these objects as well, if you need to see that too:

    Code (CSharp):
    1.  
    2.     private void OnTriggerEnter(Collider other)
    3.     {
    4.         // If car collides with food, destroy it and create a dropoff point.
    5.         if (other.CompareTag("Food"))
    6.         {
    7.             Destroy(GameObject.FindGameObjectWithTag("Food"));
    8.             GameObject d = GameObject.FindGameObjectWithTag("Respawn");
    9.             dropoff = d.GetComponent<SpawnManager>();
    10.             dropoff.SpawnDropoff();
    11.         }
    12.         // If car collides with a dropoff point, destroy it and create a new random food
    13.         else if (other.CompareTag("Finish"))
    14.         {
    15.             Destroy(GameObject.FindGameObjectWithTag("Finish"));
    16.             GameObject g = GameObject.FindGameObjectWithTag("Respawn");
    17.             food = g.GetComponent<SpawnManager>();
    18.             food.foodOnScreen = false;
    19.         }

    BONUS POINTS: Ideally food would only spawn on roads within a certain distance of buildings tagged "Restaurant" and dropoff points would only spawn on roads within a certain distance of homes tagged "Residential."
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,768
    There's a few ways to author this.

    One simple way is to place some kind of preset batch of pickup and dropoff locations in the game, which is how the GTA taxi mode worked. That way you always had interesting things when you picked up and dropped them off.

    Another way is to put objects with some kind of 3D volume to them, and pick an object randomly, and pick a spot randomly within the volume.

    You could also mark parts of your world (With tags or with just an empty marker script) and raycast at the ground looking for likely places.

    I would go with #1 even if it's more authoring work because you'll probably be more pleased with the results because you can control it.
     
  3. Ray_Sovranti

    Ray_Sovranti

    Joined:
    Oct 28, 2020
    Posts:
    172
    You may want to consider adding a pathfinding NavMesh to your level, which has a lot of authoring tools builtin to the editor. Set up your level in the NavMesh. When your character spawns, you can then use NavMesh's SamplePosition method to find the nearest point on the NavMesh to your random point.

    ....and then, once you have that set up, you get pathfinding in your game for no extra effort! You can have pedestrians wandering around, make passengers run towards you even if you're just around a corner or something, or make a "helper arrow" for guiding you to your passengers' dropoff that takes into account dead-ends and such rather than just blindly pointing in the direction, or make AI cop cars chase the player, or...

    Basically, in addition to the NavMesh being a very straightforward way to solve your current problem, it ALSO gives you really really easy solutions to half a dozen future problems you're likely to have while developing this game.
     
    Kurt-Dekker likes this.
  4. Sentokoo

    Sentokoo

    Joined:
    Oct 6, 2020
    Posts:
    5
    This sounds like a good solution. How would I go about doing this? (Sorry I'm pretty new at Unity.) I'm thinking create an array with X/Y/Z co-ordinates for each spot, and then have the SpawnManager select one at random from the list? I think if I made about pre-set 50 locations this could work for a small-ish map and look random enough.

    Thanks, I'm definitely going to have to watch a few tutorials on NavMesh. I was planning to look into using that for either a "helper arrow" or a GPS-like path of breadcrumbs to lead to pickup/dropoff points, but I hadn't got that far yet, or realized I could also apply it to this problem!
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,768
    This seems very reasonable, probably how I would do it.

    I would make the list of known locations list be a list of GameObjects, and then you can optionally put a Script on them that has properties on it to say "This can be used for a pickup" and "This can be used for a dropoff" (Or both for instance). I use this for my landing site spawn definitions for my Jetpack Kurt game, to control if the player can spawn there or if only landing pad goals. It looks like this and just goes in the scene on the GameObjects that will become the spawn platforms:

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class SpaceFlightSpawnPointProperties : MonoBehaviour
    4. {
    5.     public bool DisallowBaseHere;
    6.     public bool DisallowPlayerHere;
    7.     public bool ProtectWithShieldSphere;
    8. }
    The third option only comes into play in combat mode.

    I iterate the GameObjects that are in the spawns array, and on each one I call GetComponent<>() to get the above script and inspect its fields. The end result can look something like this in the Inspector:

    Screen Shot 2020-11-09 at 8.17.12 PM.png