How to control multiple GameObjects - c#

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();

Related

How do I keep score between scenes in Unity dynamically?

I have a UI bug that I can not figure out. I wish to be able to carry my score over into each scene, while also having each scene independent of one another. If the player chooses to start the game in level 6 he should be able to do so with a clean scoreboard, same as starting at level 1. The score should proceed unitl to transfer until the player dies at which point the score should be set back to 0. To do this I've prefabbed my scoreboard and dropped it into every scene using the singleton pattern to make sure that there are no duplicate scoreboards in the scene. While trying to access my score text UI of type Text using UnityEngine.UI I receive a null reference error when the class is called after loading a new scene. Here are the C# classes defining the methods used in order to implement the functionality and screes snippets of my hierarchy in Unity 2019.4.1f1. I'm also attaching a short youtube video to show you that the score does work in the first scene, however it does not work in any other scene after. I do know that I'm destroying the Text UI at the beginning of every scene if there are multiple. What I do not understand is why the Text UI nested under the Scoreboard Canvas is not finding it's reference on Awake() for every instance created and why it's being called null. I truly appreciate anyone help!
Here are the screen snippets and videos in this order:
Youtube video of bug
Hierarchy of Scoreboard Canvas
Hierarchy of Score Text
Scoreboard singleton class (Attached to Scoreboard Canvas)/ ScoreText class (Attached to ScoreText UI)
Implementation of classes used to add score upon completion of level in CollisionHandler class
UI Bug Video
Hierarchy of Scoreboard Canvas
Hierarchy of ScoreText
Scoreboard Classes
CollisionHandler class used for implementation
If these don't help, here is the github link to see the project code:
Github Project Files
Store your score in a monobehavior that calls DontDestroyOnLoad(this.gameObject);
This will preserve that object between scene changes. https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
As for why it is not working, I'm not really sure.
But I can tell you some ways around it:
You could try to make a public static instance of your ScoreText inside the script of ScoreText.
public static ScoreText _instance;
public static ScoreText instance { get { return _instance; } }
private int score;
private Text scoreText;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(this.gameObject);
}
else
{
_instance = this;
}
}
void Start()
{
score = 0;
scoreText.text = score.ToString();
}
And access its functions with:
ScoreText.instance.AddToScore();
It would be cleaner and easier to use, in my opinion.
If you would like to simplify a bit more, you could update scoreText in the update function as well. But since you only add points upon landing in those green zones, your way works fine as well.
Hope I could help.

How to: Get instance of gameObject

Looking to get an instance of this game object so I can successfully use .enabled to hide it in a scene.
PlayPortalLoginButton loginButton = gameObject.GetComponent<PlayPortalLoginButton>();
Fairly new to C# and I believe I am close to achieving my goal with the line above. What needs changed? Want to understand how to correctly do this.
Here is one way you could find a component on a GameObject in the scene, where "PortalLoginButton" is the name of the GameObject as seen in the editor:
var loginButton = GameObject.Find("PortalLoginButton");
loginButton.enabled = false;
However, GameObject.Find("...") searches the name of every GameObject in the scene, and this is not usually the best way to reference a GameObject since it is not very efficient. So make sure not to use GameObject.Find("...") or similar function calls in the Update() function because it will execute every frame and slow your game down. If the GameObject is not instantiated while the game is running, it is usually better to make a global reference to any GameObject or Component that you use in your script and then drag-and-drop the GameObject with the Component you are looking for into the field in the editor. Alternatively, you can use GameObject.Find("...") in the Start() or Awake() functions to store a reference to the GameObject that you are looking for, so that the search only happens once at the start of your game.
Here is an example of how to store the reference in global field (it will show up in the editor and you can drag-and-drop the GameObject into it). The differences between using a public field vs a private field are explained in the comments (you can decide on using public or private):
// By default, private fields are not viewable in the editor,
// but the [SerializeField] attribute, placed before
// the field declaration, allows them to be visible.
[SerializeField]
private GameObject loginButtonPrivateReference;
// If you need to access loginButton from another script,
// you can make it a public field.
// Public fields are viewable in the editor by default.
public GameObject loginButtonPublicReference;
Here is an example of how you can use GameObject.Find("...") in the Awake() function:
private GameObject loginButton;
private void Awake() {
loginButton = GameObject.Find("PortalLoginButton");
}
If you need to search for GameObjects in your scene by type or by tag name, see the GameObject documentation here for more information. Searching by type is less efficient and searching by tag is more efficient than searching by name because type searches check each component on each GameObject, and tag searches search only an organized GameObject subset.
GameObject button;
void Start() {
button = GameObject.Find ("yourButtom");
}
void SomeEvent() {
button.SetActive(false);
}
I think you have to help to you
There are several ways to get access to a gameObject in a script depending on exactly which gameObject you are trying to get.
If you are trying to access a GameObject of a behavior in the behavior script then simply using gameObject should suffice since that is the gameObject that behavior is attached to.
Accessing a script that is attached to another GameObject is where it can be tricky. For scenes such as a MainMenu, there is nothing wrong with giving it a public reference as an example:
public PlayPortalLoginButton loginButton;
void setLoginState(bool activate) {
loginButton.enabled = activate;
}
With this example you would just drag and drop the gameObject with the PlayPortalLoginButton script on your manager script, the script that would control whether it is enabled or not.
If you need a more modular approach to it for example in a gameplay scene where you are populating objects at run time the approach you can take is:
PlayPortalLoginButton loginButton;
void setLoginState(bool activate) {
if(loginButton == null)
{
loginButton = FindObjectOfType<PlayPortalLoginButton>();
}
// Requires the correct version of .net otherwise you can reuse the check above...
loginButton?.enabled = activate;
}
In the above script if you needed to gameObject of loginButton, now you can access it through loginButton.gameObject.
There are some more examples you could use and other approaches however I feel the above should suffice if for a menu.
I will also mention I am avoiding example of GameObject.Find and GameObject.FindGameObjectsWithTag as those are more prone to error. They will only work if your name or tag are correctly marked, if you rename them for any reason then your scripts will fail to find the Object, and this can cause an issue with troubleshooting.
Using FindObjectOfType, you are less likely to have an error, and if you remove that script type these scripts will populate an error indicating the script no longer exists. When refactoring, if you right click on the behaviors name and use the rename option it will automatically update references to the script throughout your code.

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

How to randomly generate GameObjects programmatically?

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.

How to be notified o a Component or child GameObject has been added to a GameObject

Is there anyway I can be notified (possibly through some method/event raised) when a Component is added to a GameObject (and even child GameObject)?
I'd like to be notified(possibly in some editors scripts) when some events occurs in the editor for example:
A Component has been attached to a GameObject instance
A Component has been attached to a Prefab
A GameObject has become child of another GameObject istance
Is this possible?If yes how?
EDIT
I found out a delegate for what concern parenting:EditorApplication.hierarchyWindowChanged
Accordingly to the doc it's called :
Called whenever the scene hierarchy has changed.
This is transform.parent changed, gameObject.name, creating a new game
object, etc.
I still don't understand if there's a convinient way to understood which Object in the hierarchy has been changed.
You can add a component to an object in your (editor-only) level-editing scene:
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public class LevelEditorHelper : MonoBehaviour
{
LevelEditorHelper()
{
EditorApplication.hierarchyWindowChanged -= MyHierarchyChangedCallback;
EditorApplication.hierarchyWindowChanged += MyHierarchyChangedCallback;
}
private static void MyHierarchyChangedCallback()
{
Debug.Log("Hierarchy has changed");
}
}
#endif
Then within the callback you can access the current selected object (the added object will be the active selection):
GameObject activeObj = Selection.activeGameObject;
if(activeObj != null) {
...
And now you can check its parent to see if it matches whatever other object, and perform other checks to ensure this is actually the event you're interested in (remember it will be called for various other hierarchy changes too), then you can call another function to do whatever action you want.
Note: This won't work as-is for in-game scenes, if you edit your in-game scenes directly with your level-editor scripts then you'll need to use more careful placement of #if UNITY_EDITOR and consider how to remove the unnecessary component (perhaps in Start or at build-time if possible), or just leave it there doing nothing if you're happy with that. In my case my level editor is an editor-only scene, and it saves out a specific sub-set of the editor scene as a scene for use in-game.
There is no "built-in" way of doing this conveniently. However, you could simulate it in a couple of ways.
Utilize the Awake() function in your Component to call it's parent gameObject (SendMessage works very cleanly for this) [note: this only works if you're adding a NEW component instance]
OR
Create a variable to track it's parent. In the Update() method, check current parent to trigger the SendMessage to the new parent.
Here's just 2 examples of what could work for situation--there's plenty more. At the end of the day, nothing built is (as of yet), but certainly do-able from a work-around standpoint.

Categories

Resources