How to randomly generate GameObjects programmatically? - c#

I'd like some help with the following issue. I'm making a simple game where you have a character running and he has to jump over obstacles coming at him.
And I'm currently stuck when it comes to creating GameObjects and randomly generating them within the game scene at run time.
I've written a class to help accomplish this:
using UnityEngine;
using System.Collections;
public class randomObstacles : MonoBehaviour {
public GameObject myCube;
public Vector3 spawnLocation = new Vector3(0,2,0);
// Use this for initialization
void Start () {
GameObject SpawnLocation = (GameObject)Instantiate(myCube, spawnLocation, Quaternion.identity);
}
// Update is called once per frame
void Update () {
}
}
The above code is what I wrote to simply create Objects, one after the other. But when I run the game, it comes up empty -_- !
Can anyone please tell me where am going wrong, and from the look of it my code doesn't seem to do what I am hoping to achieve :(
I've attached the above script into an empty GameObject as I saw in a tutorial from the Unity Community forum, but that did not help either.
(I've looked around and it seems like no one has come across such an issue - I could bee wrong)

It would seem that your myCube variable is the root of your problems, having tested it in a scene of my own. By subsituting
(GameObject)Instantiate(myCube, ...
with
(GameObject)Instantiate(GameObject.CreatePrimitive(PrimitiveType.Cube), ...
I was able to produce a cube at (0,2,0) with no qualms. Perhaps your myCube GameObject is missing a Mesh Renderer, in which case it would appear in the Hierarchy during runtime even though it would not appear visible in Game View. Perhaps you are not assigning it in the Inspector before you run the game, in which case the myCube variable would refer to null and thus not be created. Additionally, though you may be assigning the GameObject you instantiate to SpawnLocation, it is an unused local variable (something MonoDevelop or your code editor should notify you of). Make sure you either provide a reference to a myCube GameObject in the Inspector before runtime or through script referencing to a loaded prefab/GameObject during runtime. It would also be helpful to provide a fallback (say, to PrimitiveType.Cube perhaps?), which will make your code more robust and able to handle errors in referencing should they arise.
Regardless, in order to achieve the functionality you have described, first make sure yo have properly prepared whatever you would desire myCube to be. Also, for future posterity, you may want to initialize your spawnLocation in the Start routine, and assign the variable coordinates by substituting Random.value * yourValueCeiling in for each random coordinate you would like myCube to spawn on. You could even go as far to make a helper method externalized and thus independent from the start routine, such that you will not have to have a hundred instances of a single script to create a hundred instances of what you need; rather, you can call the method through a single script, and save yourself trouble in this way. If you would so appreciate it, here is my implementation of your objective, hope this helps!
using UnityEngine;
using System.Collections;
public class randomObstacles : MonoBehaviour {
public Vector3 spawnLocation;
public GameObject myCube;
// Use this for initialization
void Start () {
if (myCube != true) {
Debug.Log("myCube not set");
myCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
}
if (myCube.renderer.enabled == false) {
Debug.Log("myCube not rendered");
myCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
}
CreateCube();
}
// Update is called once per frame
void Update () {
}
void CreateCube() {
spawnLocation = new Vector3(0, Random.value * 10, 0);
Instantiate(myCube, spawnLocation, Quaternion.identity);
}
}

It may be worth pointing out that you're creating your object in the Start method, which means your code will only run once: from the name of your class, I'd assume you want to create more than one object using this code.
If you move the code into Update you'll create one object per frame, which is most likely too many. My guess would be that you want something like a coroutine that will run on a random interval, and then spawn cubes repeatedly over time, something like this:
void Start () {
StartCoroutine("SpawnObjects");
}
IEnumerator SpawnObjects()
{
while (keepAddingObjects) // a boolean - could just be "true" or could be controlled elsewhere
{
GameObject SpawnLocation = (GameObject)Instantiate(myCube, spawnLocation, Quaternion.identity);
float delay = Random.Range(1f, 5f); // adjust this to set frequency of obstacles
yield return new WaitForSeconds(delay);
}
}

Taken from my own code in a game that auto generate mazes:
public class Cell
{
private GameObject instance;
public void CreateVisual()
{
// Load a GameObject that exist inside the "Resources" folder.
GameObject prefab = (GameObject)Resources.Load("Models/Walls/3W1");
// Create an instance of the prefab
instance = (GameObject)GameObject.Instantiate(prefab);
instance.transform.position = myPosition;
}
}
I think the part you are missing is the Resources.Load() method.

Related

How to control multiple GameObjects

I'm currently in the process of making my first bigger game in Unity 2D with C#, and I have a problem. I have already searched a lot for answers, but I haven't been successful.
My problem is, that I want to make multiple doors that the player can open individually (there will be around 40 doors). I think I will deactivate the closed door GameObject and activate the open door GameObject.
And I feel like there must be a better way of controlling multiple GameObjects than to drag and drop to separate variables and make an if statement for each door in the script.
E.g:
if (door4 gets opened) {
door4Closed.setActive(false);
door4Open.setActive(true);
}
It has been a problem for some time, and making the doors is just one of the times I've run into this type of problem.
I have played around with GameObjects arrays and .find, but I still feel like there must be af better way.
So I would like help to:
1: Deactivate and activate a specific GameObject among many.
2: Maybe even making a better door system.
Thanks in advance! :D
Edited after OP provided more information.
If all doors can be freely opened without any checks, the easiest way would be creating a script that inherits MonoBehaviour and assign it to a prefab that you use to create the doors.
If the doors are already there, you can select all of them at once on the hierarchy and add this door component to all of them at the same time.
To avoid the need of dragging and droping every single variable of the door script manually you can do it on the door script itself.
This is a script assumes that:
The Collider of the door is in the same gameobject as this.
The Colllider has the "IsTrigger" ticked.
The player's gameobject that contains the collider is tagged as "Player" (change the tag on the script if it's not that one).
The script contains two different ways of assigning those variables in the editor.
1: Using the Reset() event function, which is called whenever you add this script inside the editor or when you click "Reset" on the context menu.
2 (My preferred): Using the OnValidate() event function, which is called whenever you load the gameobject on the editor or when you edit any value on the inspector.
Futhermore, this is a suggestion of implementation, you should customize your own based on your specific needs.
public class DoorScript : MonoBehaviour
{
private bool _isOpen;
private bool IsOpenHandler
{
get => _isOpen;
set
{
if (_isOpen && !value)
ExecuteCloseAnimation();
else if (!_isOpen && value)
ExecuteOpenAnimation();
_isOpen = value;
}
}
private void OnTriggerStay2D(Collider2D other)
{
if (other.CompareTag("Player") && Input.GetKeyDown(KeyCode.E))
{
IsOpenHandler = !IsOpenHandler;
}
}
private void Reset()
{
animator = GetComponent<Animator>();
}
#if UNITY_EDITOR // we can't let this compile on game builds because the UnityEditor namespace does not exist on builds (which we are using bellow)
private void OnValidate()
{
if (animator != null) // if we already have the animator we don't need to do anything
return;
animator = GetComponent<Animator>();
if (animator == null) // if we fail to get the animator we log a warning to let you know
Debug.LogWarning($"Could not find animator on {gameobject.name}!", this);
else
UnityEditor.EditorUtility.SetDirty(this); //this sets this gameobject as dirty and is needed to force the Editor to save the changes we made when you click "Save"
}
#endif
}
What language are you using? The following is a general template you can use, as long as it is object-oriented.
Create a Door class. Make each door an object of class door. Define class door to have an attribute that keeps track of state such as opened = false. It's also good practice to make getter and setter methods:
function open() {
opened = true;
}
This way, you can easily change the state of doors.
Door d = new Door();
d.open();
You could keep track of the doors using an ArrayList if you wanted.
ArrayList<Door> doors = new ArrayList<Door>();
doors.add(d);
doors.get(0).close();

Awake Method not starting

Im trying to use the Awake method without attaching it to an object, but it never initializes the class. It was supposed to initialize the class so that it could run an OnEnable method that added a subscriber to an event, but it did nothing. The only thing that works is attaching the script to a game object, but I don't want to do that, I tried changing the acmes modifier from private to protected virtual and a bunch more...
This is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovementAnimationParameterControl : MonoBehaviour
{
private Animator animator;
private void Awake()
{
animator = GetComponent<Animator>();
}
private void OnEnable()
{
EventHandler.MovementEvent += SetAnimationParameters;
Debug.Log("Enable. Im running, its not me");
}
private void OnDisable()
{
EventHandler.MovementEvent -= SetAnimationParameters;
Debug.Log("Im running its not me");
}
private void SetAnimationParameters(float xInput, float yInput, bool isWalking)
{
animator.SetFloat(Settings.xInput, xInput);
animator.SetFloat(Settings.yInput, yInput);
animator.SetBool(Settings.isWalking, isWalking);
}
}
It was supposed to initialize the class so that it could run an OnEnable method that added a subscriber to an event, but it did nothing. The only thing that works is attaching the script to a game object
When you create a class that inherits from MonoBehaviour (Aka a 'Unity Script'. You're creating a script that allows the use of certain functions at runtime, such as OnEnable and OnAwake among others.
It might not seem too intuitive, but MonoBehaviour actual inherits indirectly from GameObject. The reason for this is because GameObject is the "target" of all of these methods like Update() for example.
Without an actual GameObject for your MonoBehavior to access it's properties at runtime, your script wont do anything.
The best example would be from your code.
Take the OnAwake() method for example
private void Awake()
{
animator = GetComponent<Animator>();
}
What this method does is set the field animator to the reference of Animator. That reference is found using the inherited method called GetComponent. Get component is an inherited method from the MonoBehaviour class, which like a russian nesting doll, is inherited from GameObject.
So what you're really doing is saying "hey GameObject that this script is attached to, please give me your Animator component, if you have one."
This is where we see the issue at hand, that without an actual GameObject to access none of the methods like OnAwake() or even the method calls contained within will work, since they all(or most) come from GameObject not actually from MonoBehavior.
There is also another major obstacle to overcome specifically with the events like OnEnable(), OnAwake(), Start() etc.. This issue comes from how Unity processes scripts during runtime. Only scripts with a actual GameObject is ever scheduled to have it's components scripts to run.
Think of it like a big pile of paper work on some body's desk. This big pile of paperwork consists of all of the scripts on all gameobjects in the scene. Unity has to pull a sheet of paperwork(a script) from the pile, run it's contents, and put it in the done pile on the opposite side of the desk. With this analogy, a script that doesn't have any attached GameObject, would be a sheet a paper with no name on it. Unity will look at this paper and say "This doesn't even have a name on it, I'm not even going to look at it" and then proceeds to throw it in the trash because it's not a script that any GameObject needs to run - and that's all Unity cares about. This is because it would be inefficient to look at any scripts that no GameObject needs.

Script working properly on one gameobject, but not on another

I have a very strange problem.
I created an AI using the navmesh tools and a custom script which keeps assigning a new destination to the navmesh agent once the old destination is reached.
I had to replace the model for this AI (was a capsule before), so I did that, copied almost all components, adjusted a few parameters like agent height etc., and ran it.
The capsule AI does its job properly, but the AI with the correct model clearly doesn't.
After debugging, I discovered that the List of destinations for the modelled AI consists if Vector3.zero's, while the capsule AI has the correct vector3s in its destination list.
Here's my AI destination script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AIMovement : MonoBehaviour
{
private NavMeshAgent _navMeshAgent; //reference to the navmesh agent
private MapController _mapControllerScript; //reference to the map controller script to access waypoints
private List<Vector3> _wayPoints = new List<Vector3>();
private Vector3 _destination;
// Use this for initialization
void Start()
{
_mapControllerScript = GameObject.Find("Map_01").GetComponent<MapController>();
SetWayPointLocations();
_navMeshAgent = this.GetComponent<NavMeshAgent>();
Debug.Log(_mapControllerScript.ToString());
Debug.Log(_mapControllerScript.HealthPickUpPositions.ToString());
Debug.Log(_navMeshAgent);
Debug.Log(_wayPoints);
foreach (Vector3 waypoint in _wayPoints)
{
Debug.Log(waypoint);
}
}
// Update is called once per frame
void Update()
{
MoveAroundMap();
}
void MoveAroundMap()
{
if (_navMeshAgent.transform.position.x == _destination.x &&
_navMeshAgent.transform.position.z == _destination.z
|| _destination == new Vector3(0, 0, 0)
)
{
Debug.Log("Acquiring new position");
_destination = _wayPoints[Random.Range(0, _wayPoints.Count-1)];
}
_navMeshAgent.SetDestination(_destination);
}
void SetWayPointLocations()
{
foreach (Vector3 waypoint in _mapControllerScript.HealthPickUpPositions)
{
_wayPoints.Add(waypoint);
}
foreach (Vector3 waypoint in _mapControllerScript.AmmoPickUpPositions)
{
_wayPoints.Add(waypoint);
}
}
}
Here's the console: you can clearly see the coordinates of the capsule AI being correct, while the coordinates of the broken AI being (0, 0, 0).
Here's the hierarchy window: Capsule is the working AI, Character_Piccolo the non-working.
Here's the inspector of the Capsule, the working AI.
Here's the inspector of the model, the broken AI.
Sorry for making this long, but I wanted to make sure you guys had all the needed information.
Thanks in advance!
The problem - and also the solution - are not in your AIMovement script, or in any edit you did while changing from the capsule to you new model, not even in any of the scripts and components you provided in the question. The problem is a subtlety in you MapController class that was already there all the time (even before your changes), and the fact that you did not experience this error before is a matter of casuality.
As you said in the comments, you're setting up HealthPickUpPositions and AmmoPickUpPositions properties of you MapController class in its Start() method. And you're reading the values of those properties on the Start() method of your component class. The thing is: the Start message runs once a gameObject is set on a loaded scene, but the order in that objects get started does not depend on your game logic (it depends on the scene hierarchy, prefabs, object instance number and other stuff, whatever...) and should be considered random.
In your specific case, the Start() of your AIMovement is being called before the Start() of MapController, i.e. before the proper values of the properties being set - hence, all you read are zero-vectors (default values).
Because of that, unity has a similar message named Awake() on gameObjects. You also don't know the order in which the objects will be awoken, but you can assure that any Start() message will be sent only after all the Awake() messages were already been sent. More on this here, here, here and here.
The solution: Put all internal initialization logic of your classes in the Awake method; and all the connections to other gameobjects (that assume they are initialized) on the Start method.

Accessing a script from another script at runtime in Unity C#

Like in here, but the difference is that it's supposed to be done from an instantiated prefab, so I can not drag the GameObject, that has the script with the variable I want to access, into this script.
This was working
public ScriptA script;
void Update() {
if (script.varX < 0) {
// . . .
}
}
But now I'm getting "Object reference not set to an instance of an object" error, which I think comes from the fact that the script trying to access ScriptA, is attached to an instantiated prefab.
How do I attach scripts and/or GameObjects at runtime?
Looks like you need to find your script type first, if it already exists in the scene:
public ScriptA script;
void Start()
{
script = GameObject.FindObjectOfType<ScriptA>();
}
void Update()
{
if(script.variable...)
}
You want to use AddComponent, like:
ScriptA script = gameObject.AddComponent<ScriptA>() as ScriptA;
See the docs here:
https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html
Best way to satify the links is fill the fields in the very next lines after you instantiate, that way you can avoid ugly and expenstive Find* calls (I am assuming the script that does the instancing can be made aware of what the target objects are, after all it knows what and where to instantiate)
Its worth noting that newly instantiated scripts' Awake() method will be called before Instantiate() returns, while its Start() will be called at the start of following frame, this is a major difference between the two calls, so if your instantiated script needs the refectences in Awake() you should either refactor (move stuff to Start()) or use Find* as suggested earlier.

Duplicates because of DontDestroyOnLoad()

I have a strange problem with DontDestroyOnLoad. I have a map as a starting scene. From there, the user can click on certain map-objects and a new level is loaded with Application.LoadLevel() When the level is finished the map is loaded again. But, the objects with DontDestroyOnLoad attached are duplicated on the second load.
Current script:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
I searched online and found this possible solution to remove the duplicates:
public class NoDestroy : MonoBehaviour {
public static NoDestroy instance;
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
return;
}
DontDestroyOnLoad(this.gameObject);
}
}
The above script simply does not work. How can I fix the problem?
Because the above script uses a static instance, it'll only work if a `single GameObject has it attached - worse yet, it'll delete other objects which try to use the same behavior.
The biggest problem is that whenever you load a scene, all objects in that scene get loaded. If they weren't unloaded (thanks to DontDestroyOnLoad) they'll be duplicated, since the scene doesn't know that they "already exist"
The above script might work better if you try to box your persistant objects under an umbrella, and only the umbrella object (usually called Toolbox) isn't destroyed. This is mostly appropriate for manager scripts, however.
If you know the objects that are meant to not be destroyed, consider loading them via a "Loading" scene. Since this moves the persistent objects out of your map scene, they won't get duplicated when reloading the map scene. Bonus to this pattern since it makes it easier to implement curtain drop.
If you want to implement this as a simple behavioural script, consider adding an ID like so
public class NoDestory : MonoBehaviour
{
private static Dictionary<string, GameObject> _instances = new Dictionary<string, GameObject>();
public string ID; // HACK: This ID can be pretty much anything, as long as you can set it from the inspector
void Awake()
{
if(_instances.ContainsKey(ID))
{
var existing = _instances[ID];
// A null result indicates the other object was destoryed for some reason
if(existing != null)
{
if(ReferenceEquals(gameObject, existing)
return;
Destroy(gameObject);
// Return to skip the following registration code
return;
}
}
// The following code registers this GameObject regardless of whether it's new or replacing
_instances[ID] = gameObject;
DontDestroyOnLoad(gameObject);
}
}
This will prevent the duplication of an object with the same ID value as one that already exists, as well as allowing recreation if said object has been Destory-ed elsewhere. This can be further refined by adding a special Editor script to hand over a new ID each time the script is implemented.
You could possibly make an "Initializer" scene that's the starting scene of the project, and all your "Don't Destroy On Load" objects get initialized in there. Then you immediately change to the real starting scene (your map screen) and all your objects exist and aren't duplicated. If you have a starting screen already, you might be able to use that instead of creating a whole new scene.
In case if anyone still needs the answer:
Answer which is available everywhere (WRONG ONE):
private static Sample sampleInstance;
void Awake()
{
DontDestroyOnLoad(this);
if (sampleInstance == null)
{
sampleInstance = this;
}
else
{
DestroyObject(gameObject);
}
}
Tl;dr Jump to solution.
What happens here is, when new scene is loaded, everything's fine. But as soon as you go back to previous scene, your original "Sample" game object is NOT destroyed and the new "Sample" game object which get created, is destroyed. So, what happens is, wherever you have referenced your Sample script (like in button onclick etc.) start referencing the duplicate script (which was destroyed of course), due to which they don't reference any script. Hence button.onClick no longer works.
So, the correct solution is to destroy the original Sample game object and not the duplicate, hence making the duplicate as the new original.
Here it is (THE CORRECT SOLUTION):
private static GameObject sampleInstance;
private void Awake()
{
if (sampleInstance != null)
Destroy(sampleInstance);
sampleInstance = gameObject;
DontDestroyOnLoad(this);
}
I have tested it. This works for me!!
if (Instance==null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
For use cases where you have some startup logic that only needs to be initialized once, consider creating a Startup scene that only loads once at the beginning of your game. That way, whatever scene switching you do from that point on won't create duplicates of the game objects created with the Startup scene.
In relation to networking, that's what Unity did in their Boss Room example:
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/ApplicationLifecycle/ApplicationController.cs#L94

Categories

Resources