Can't find objects with Find() in hierarchy - c#

When game starts, script will close the shop.(SetActive(false))
void Start()
{
_shopMenu = GameObject.Find("Shop");
_shopMenu.SetActive(false);
if (_shopSlotContainer == null || _shopItemSlotTemplate == null)
FindItemShopContainerAndItemTemplate();
}
And try to find two objects on scene
private void FindItemShopContainerAndItemTemplate()
{
_shopSlotContainer = transform.Find("itemShopContainer");
if (_shopSlotContainer == null)
Debug.LogError("Container doesn't found");
_shopItemSlotTemplate = transform.Find("shopItem_template");
if (_shopItemSlotTemplate == null)
Debug.LogError("Item template doesn't found");
}
Any other ways to find objects or fix this problem?
(Script attached to "Shop" object)

Transform.Find will only search the immediate children, it will not look further down in the hierarchy. It is also not very performant. You can assign the game objects you want to variables in your script
public class MyScript : MonoBehavior
{
[SerializeField]
private GameObject _itemShopContainer {get; set;}
}
After compile you should be able to see Item Shop Container property on MyScript game object in the editor and be able to drag the proper game object there.
In the code _itemShopContainer will now have your game object.
If you want to assign via code like you are doing switch to using a variant of Transform.GetComponentsInChildren<>(), rather than Transform.Find

Related

How to assign GameObject to child for serialization

I want to assign each item I create with a GameObject of some sort and then when I create a new object (called Item) it will have the assigned GameObject as a child.
For that I have a class of a scriptable object which holds a public GameObject called "gameObj" within it:
public abstract class ItemObject : ScriptableObject
{
public int id;
public GameObject gameObj;
}
Then, in another class I want to have something of this sort:
public class GroundItem : MonoBehaviour, ISerializationCallbackReceiver
{
public ItemObject item;
public void OnBeforeSerialize()
{
this.gameObject.transform.GetChild(0) = item.gameObj; //WRONG CODE, NEED HELP HERE
}
}
The purpose is to set the gameObj from the given ItemObject.item as the GameObject for the GroundItem.
The purpose is in the end to have lots of scriptable items of all sorts (like bread, sword, stone etc) and each one will have a GameObject assigned to it, and once I create a new GroundItem game object I will simply assign the scriptable object item and its child will have the game object itself (which includes all the visuals, special scripts etc).
For reference, in the following link the person is doing this from minute 4 to minute 6, but with a sprite instead of a game object.
Anyone knows how it should be written? Is it even possible?
You would probably mean
public class GroundItem : MonoBehaviour, ISerializationCallbackReceiver
{
public ItemObject item;
public void OnBeforeSerialize()
{
if(!item) return;
if(!item.gameObj) return;
// make according object a child of this object
item.gameObj.transform.parent = transform;
// make it the firs child
item.gameObj.transform.SetSiblingIndex(0);
}
}
However, in general ScriptableObjects are assets and GameObjects are instances in the scene hierarchy -> you can't simply reference scene objects in assets, doing so anyway might lead to unexpected behavior.
Is there a good reason why this has to be done in ISerializationCallbackReceiver?
I think what you actually rather want to achieve would be e.g.
public class GroundItem : MonoBehaviour
{
public ItemObject item;
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
private ItemObject _lastItem;
// This is called whenever something is changed in this objects Inspector
// like e.g. item ;)
private void OnValidate()
{
// Delay the call in order to not get warnings for SendMessage
UnityEditor.EditorApplication.delayCall += DelayedOnValidate;
}
private void DelayedOnValidate()
{
// remove the callback since we want to be sure it is always added only once
UnityEditor.EditorApplication.delayCall -= DelayedOnValidate;
// if item hasn't changed nothing to do
if (item == _lastItem) return;
// otherwise first destroy current child if any
if (_lastItem && transform.childCount > 0)
{
if (Application.isPlaying) Destroy(transform.GetChild(0).gameObject);
else DestroyImmediate(transform.GetChild(0).gameObject);
}
// is an item referenced and does it even have a gameObject ?
if (item && item.gameObj)
{
// instantiate the new one as child of this object
var obj = Instantiate(item.gameObj, transform);
// set as first child (if needed)
obj.transform.SetSiblingIndex(0);
if (!Application.isPlaying)
{
// if in edit mode mark this object as dirty so it needs to be saved
UnityEditor.EditorUtility.SetDirty(gameObject);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
}
}
_lastItem = item;
}
#endif
}
Which would now look like

How to access and disable a GameObject from a different script - Unity2D

I am currently working on a game, and while designing a Main Menu for the game, I need to be able to access different GameObjects and enable/disable them for the user to access different parts of the menu. The different parts are held under their respective parent GameObjects, for example, all the GameObjects related to the "Options Menu" will be under a single empty GameObject called "Options".
Here is the code pertaining to a script that holds the references to all the different parent objects and the methods to enable them:
public class MenuSwitch : MonoBehaviour
{
public GameObject Main;
public GameObject Options;
public GameObject About;
public GameObject Load;
public void MainMenu()
{
Main.SetActive(true);
Options.SetActive(false);
About.SetActive(false);
Load.SetActive(false);
}
public void OptionsMenu()
{
Main.SetActive(false);
Options.SetActive(true);
About.SetActive(false);
Load.SetActive(false);
}
However. the problem is the navigation of the Menu has to be done by the player by typing 'commands'. For example, they would type '/options' to go to the options menu. This functionality will remain throughout the game, so I made another script to handle player commands from the in-game console. Now how do I call the methods from MenuSwitch, because making a MenuSwitch object inside my console script does not seem to work, it gives an error: "Object reference not set to an instance of an object".
Here is the script handling the commands specified in the main menu:
// Commands
string[] MainMenuCommands = { "./start", "./load", "./about", "./options", "./end" };
private void Update()
{
if (Input.GetKeyDown(KeyCode.Return))
{
// Reset the console
playerInput = console.text;
console.text = "";
// Main Menu Input
if (CheckCommand(playerInput) == true)
{
RunCommand(playerInput, scene);
}
}
}
void RunCommand(string _command, int _scene)
{
// Main Menu commands
if (_scene == 0)
{
if (_command == MainMenuCommands[0]) { StartCoroutine(MM_Start()); }
else if (_command == MainMenuCommands[1]) { MM_Load(); }
else if (_command == MainMenuCommands[2]) { MM_About(); }
else if (_command == MainMenuCommands[3]) { MM_Options(); }
else if (_command == MainMenuCommands[4]) { MM_End(); }
}
}
The MM methods are empty currently, that is where the enabling/disabling will take place. How do I enable or disable the GameObjects inside the MenuSwitch script? The objects are referenced in that script via the Unity inspector.
You could create an empty object and call it MenuManager, attach the MenuScript to it. Then in a script you want to call MenuSwitch functions from declare a class member like so:
public MenuSwitch menuManager;
Drag and drop the MenuManager empty gameObject in the inspector and then you can easily use all the public functions from MenuSwitch script by just calling them from the menuManager variable like so:
menuManager.MainMenu();
menuManager.OptionsMenu();
I hope it was helpful :)

Change one attribute value of a script so it effects all gameObjects that this script is attached to

How do I change one or two attribute values of a script so that it effects all the gameObjects that this script is attached to?
For example making attributes SphereSmall and SphereBig global
public Vector3 SphereSmall = new Vector3 (0.001f, 0.001f, 0.001f);
public Vector3 SphereBig = new Vector3 (0.0015f, 0.0015f, 0.0015f);
Two gameObjects has this script attached to and I changed the attribute of public variables SphereSmall and SphereBig on one GameObject. I want this value to be changed in second GameObject as well.
I'm new to unity as well, but here are some solutions:
1) If you're talking about multiple instances of the same object, simply create a prefab out of that object and update the properties of the prefab.
https://docs.unity3d.com/Manual/CreatingPrefabs.html
2) If you're talking about instances of different objects, you can try using the multi-select functionality of the unity editor, this will let you edit all common properties. This is easy if you have lower number if instances and are grouped under a parent object in the Hierarchy pane.
3) If you're talking about instances of different objects and you don't mind seeing the effect of your updates only at run-time: you can try using the ScriptableObject class:
[CreateAssetMenu]
public class CommonObjectProperties : ScriptableObject
{
public Vector3 SphereSmall = new Vector3(1, 0.001f, 0.001f);
public Vector3 SphereBig = new Vector3(0.0015f, 0.0015f, 0.0015f);
}
After you create this script, go to Assets > Create > Common Object Properties:
Now you can use this ScriptableObject instance to add the common values to your objects and after you update them inside the ScriptableObject instance, they will update across all objects.
ObjectScript is the MonoBehaviour script to put on your objects:
public class ObjectScript : MonoBehaviour
{
public CommonObjectPropertis commonProps;
public Vector3 ObjectScriptSmallSphere;
private void Start()
{
ObjectScriptSmallSphere = commonProps.SphereSmall;
}
}
How it should look in the designer:
I'm sure there are plenty of other ways to do this, best of luck!
You could add [ExecuteInEditMode] to your script so it runs while the editor is running. From there you could check if the values are being changed and then update them. Something like this:
[ExecuteInEditMode]
public class MyScript : MonoBehaviour
{
public int myValue;
private int oldValue;
void Update ()
{
if (oldValue != myValue)
{
MyScript[] objects = FindObjectsOfType<MyScript>();
foreach(MyScript myObject in objects)
{
myObject.myValue = myValue
}
oldValue = myValue;
}
}
}
This isn't best practice though so I would recommend looking into using prefab variants instead.

Call object on other scene C#

I have this problem that I want to call an object from my first scene then call that object on my second scene . I tried doing this
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
and put it on the object I don't want to destroy then changed my scene on the
void Start(){
SceneManagement.LoadScene("Menu",LoadSceneMode.Single);
}
But it's not there on the heirarchy
Could someone help me out
EDIT:
Now when the next scene is loaded
The object I wanted is not there anymore. It is being destroyed
Create a persistent object
Create a preloader scene here you can place a splash screen or whatever you prefer but the important thing is loading things that should be persistent(maybe such as a network or gamemanager)
Create a script PersistentObject.cs and put the following code in it
private void Awake(){
DontDestroyOnLoad(this.gameObject);
}
Put this script on any object you initialize in the preloader
Access object from anywhere
If you want to access an object in another scene there are several ways but I will assume you do not have any specific reference to the object
So if we have a GameManager.cs and we created a Persistent cube in our preloader called Cube we can get a reference to the gameobject by saying GameObject cube = GameObject.FindGameobjectWithName("Cube");
Now you are able to do whatever you want by using cube
Write less, Do more with singletons
Creating a singleton will also be very useful as well
public static class Singleton<T>: MonoBehavior where T: MonoBehavior{
private static T instance;
//Notice the lower and upper case difference here
public static T Instance{
get{
if(instance == null){
instance = GameObject.FindGameObjectOfType<T>();
}
return instance;
}
}
}
You can then add this to your script make accessing properties easier and reduces the amount of code you have to write
public class Cube: Singleton<Cube>{
private string cubeName = "Jeff";
public void ChangeCubeName(string newName){
cubeName = newName;
}
}
To access this methods of this class you could now call the singleton from anywhere in your code
Example
public class GameManager: MonoBehavior{
private void Start(){
cube.Instance.ChangeCubeName("Joe");
}
}

Detect collision from different objects?

I couldn't come up with a suitable title for this question but I'll explain.
I have a collision box, which holds a script. This script has an if statement that detects collision from object "Cube001" and sends a Debug.Log to console.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cubeDetect : MonoBehaviour {
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.name == "Cube001")
{
Debug.Log("Cube001 hit!");
}
}
}
With this method, the box collider knows what cube has touched it, as I have instructed so with
collision.gameObject.name == "Cube001"
But say if I have 10 cubes colliding with the collision box, how can I change the if statement so instead of writing another 9 if statements that check if it touches the collision box, I can just have 1 if statement that just first detects a collision from another cube, knows what cube hit the box, and with this knowledge, is able to do a Debug.Log to display the name of the cube that hit the box.
I've tried going through the documentation for OnCollisionEnter but couldn't find anything to help with this.
One option would be to tag all of your similar objects you want to collide with, with the same name. Say we give them the tag "Cubicle". Then we can do the following:
Gameobject myCube;
void OnCollisionEnter(Collision collision) {
if (collision.collider.tag == "Cubicle")
{
Debug.Log(collision.gameObject.name + " hit!");
myCube = collision.gameObject; // store colliding gameobject info for use elsewhere
}
}
You need to use Dictionary. This eliminates the need for all the if statements.
This is what the Dictionary should look like:
public static Dictionary<GameObject, System.Action> objToAction = new Dictionary<GameObject, Action>();
Then a function to add objects to the dictionary when they are instantiated or in the Start function if they already exist
public void registerObject(GameObject obj, System.Action action)
{
objToAction.Add(obj, action);
}
The key in the Dictionary is the GameObject(Cube), you can also use string(name of the GameObject) but using the GameObject is better. The value in the Dictionary stores what you want to to do when OnCollisionEnter is called. So the code that should've been inside that if statement should be placed here. This is done with Action and delegate. You can add as many GameObjects (Cubes) as you wish.
You must add those Cube Objects to the Dictionary with the function above:
public GameObject prefab;
void Start()
{
//Create new Cube
GameObject obj = Instantiate(prefab);
obj.name = "Cube001";
//Add it to the Dictionary
registerObject(obj, delegate { Debug.Log("Hit: " + obj.name); });
}
Your new OnCollisionEnter code is as below. No if statement required. If the Object exist in the Dictionary, execute that code we stored in the value of that Dictionary for each key GameObject.
void OnCollisionEnter(Collision collision)
{
Action action;
if (objToAction.TryGetValue(collision.gameObject, out action))
{
//Execute the approprite code
action();
}
}
Note that the objToAction variabel should either be made static or placed in another script attached to an empty GameObject so that you can access it. There should only be one instance of it.
What was working best for me was to use interfaces and components and check for that. This is working great if you have certain logic on collision but when you don't you can just use tag and set it to something like "collidable".
interfaces solution:
public interface ICollidableObject
{
void CollidedWith(ICollidableObject other);
}
public class CollidableBlock : MonoBehaviour, ICollidableObject
{
public void CollidedWith(ICollidableObject other)
{
Debug.Log((other as MonoBehaviour).gameObject.name);
}
}
public class CollidableSphere : MonoBehaviour, ICollidableObject
{
public void CollidedWith(ICollidableObject other)
{
Debug.Log("sphere001");
}
}
And just use it like such :
void OnCollisionEnter(Collision collision)
{
ICollidableObject collidedWith = collision.gameObject.GetComponent<ICollidableObject>();
if ( collidedWith != null )
collidedWith.CollidedWith(this);
}

Categories

Resources