I am controlling a text GameObject. I am performing operations such as SetActive() and changing the text of the gameObject. Now I am duplicating the same object and I want the duplicated object to follow the behaviors of its main gameObject. That is ObjA is parent and ObjB is a clone. If I change the text UI through code of objA, I want objB to automatically change its component. How do I achieve this behavior?
There are multiple ways to do that, but it will always be by code, there is no "authomatism" to do that.
So by code you can make a relation child to parent or parent to child.
One way could be that one:
public class Parent : MonoBehaviour
{
public Child child = null;
public void DoSomething()
{
this.gameObject.SetActive(true);
child.DoSomething();
}
}
public class Child : MonoBehaviour
{
public void DoSomething()
{
this.gameObject.SetActive(true);
}
}
Another fancy way to do that is using delegates, or Actions:
public class Parent : MonoBehaviour
{
public Action OnDoSomething = null;
public Action OnDoSomethingElse = null;
public void DoSomething()
{
this.gameObject.SetActive(false);
OnDoSomething();
}
public void DoSomethingElse()
{
this.gameObject.SetActive(true);
OnDoSomethingElse();
}
}
public class Child : MonoBehaviour
{
public Parent parent = null;
public void Awake()
{
parent.OnDoSomething += ChildDoSometing;
parent.OnDoSomethingElse += ChildDoSometingElse;
}
public void OnDestroy()
{
parent.OnDoSomething -= ChildDoSometing;
parent.OnDoSomethingElse -= ChildDoSometingElse;
}
public void ChildDoSometing()
{
this.gameObject.SetActive(false);
}
public void ChildDoSometingElse()
{
this.gameObject.SetActive(false);
}
}
You can be tempted to pass your own method as Action parameter like Action<Action> but remember that child will call parent method, won't operate on his own. So in this case if you do something like:
public class Parent : MonoBehaviour
{
public Action<Action> OnDoSomething = null;
[ContextMenu("A")]
public void DoSomething()
{
this.gameObject.SetActive(true);
OnDoSomething(this.DoSomething);
}
public void DoSomethingElse()
{
print("Hello");
}
}
public class Child : MonoBehaviour
{
public Parent parent = null;
public void Awake()
{
parent.OnDoSomething += RepeatedAction;
}
public void OnDestroy()
{
parent.OnDoSomething -= RepeatedAction;
}
public void RepeatedAction(Action actionToRepeat)
{
actionToRepeat?.Invoke();
}
}
Will result on StackOverflow exception, cause child will call parent, who calls child, who calls again parent...you can see the problem.
Anyway would be nice to declare an abstract base class that have all the methods, and let both classes inherit from that class, and implement those methods.
Related
when I load a new scene I get this error: NullReferenceException: Object reference not set to an instance of an object GameController+d__16.MoveNext () (at Assets/Scrips/GameController.cs:76) UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at :0)
this is my script to save:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Scene_Manager : MonoBehaviour
{
int Saved_scene;
int Scene_index;
public void Load_Saved_Scene()
{
Saved_scene = PlayerPrefs.GetInt("Saved");
if (Saved_scene != 2)
SceneManager.LoadSceneAsync(Saved_scene);
else
return;
}
public void Save_and_Exit()
{
Scene_index = SceneManager.GetActiveScene().buildIndex;
PlayerPrefs.SetInt("Saved", Scene_index);
PlayerPrefs.Save();
SceneManager.LoadSceneAsync(0);
}
public void Next_Scene()
{
Scene_index = SceneManager.GetActiveScene().buildIndex + 1;
SceneManager.LoadSceneAsync(Scene_index);
}
}
and this is my script for the whole game where I call save:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
[RequireComponent(typeof(GameUI))]
public class GameController : MonoBehaviour
{
public static GameController Instance { get; private set; }
[SerializeField]
private int knifeCount;
[Header("Knife Spawning")]
[SerializeField]
private Vector2 knifeSpawnPosition;
[SerializeField]
private GameObject knifeObject;
public GameUI GameUI { get; private set; }
private void Awake()
{
Instance = this;
GameUI = GetComponent<GameUI>();
}
private void Start()
{
GameUI.SetInitialDisplayedKnifeCount(knifeCount);
SpawnKnife();
}
public void OnSuccessfulKnifeHit()
{
if (knifeCount > 0)
{
SpawnKnife();
}
else
{
StartGameOverSequence(true);
}
}
private void SpawnKnife()
{
knifeCount--;
Instantiate(knifeObject, knifeSpawnPosition, Quaternion.identity);
}
public void StartGameOverSequence(bool win)
{
StartCoroutine("GameOverSequenceCoroutine", win);
}
private IEnumerator GameOverSequenceCoroutine(bool win)
{
if (win)
{
yield return new WaitForSecondsRealtime(0.3f);
FindObjectOfType<LevelLoader>().LoadNextLevel();
FindObjectOfType<Scene_Manager>().Save_and_Exit();
}
else
{
GameUI.ShowRestartButton();
}
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex, LoadSceneMode.Single);
}
}
Now first of all take a look at this post to understand NullReferenceExceptions.
After that take a closer look at your error message:
NullReferenceException: Object reference not set to an instance of an object GameController+d__16.MoveNext () (at Assets/Scrips/GameController.cs:76)
The (at Assets/Scrips/GameController.cs:76) part tells you exactly where you error is thrown (which basically means where it occured). It's in your Assets/Scrips/GameController.cs script at line 76.
private IEnumerator GameOverSequenceCoroutine(bool win)
{
if (win)
{
yield return new WaitForSecondsRealtime(0.3f);
FindObjectOfType<LevelLoader>().LoadNextLevel();
FindObjectOfType<Scene_Manager>().Save_and_Exit(); // <--- HERE
}
...
}
In your specific implementation my best guess would be that you shouldn't load your new scene before you try to save & quit. You're starting to load your new scene asynchronously and then call another function which loads another scene (?) while not actually preventing the first scene from activating until your other functions actually executed properly. There's a plethora of cases where this can and will break.
I think you'll need to rethink what it is your actually trying to accomplish because these function calls don't make sense like that.
Suppose I have 2 (or more) types of objects that the user can control:
public class Runner : MonoBehaviour
{
//...
public void Run() { //... }
}
and
public class Jumper : Runner
{
//...
public void Jump() { //... }
}
Furthermore, we have the following InputActionMaps in our InputActionAsset:
runner action map
jumper action map
My current solution defines the control-to-action mappings using separate "Controls" classes.
Controls.cs:
public abstract class Controls<T>
{
protected T controlled;
public Controls(InputActionMap actionMap, T controlled)
{
SetupInputCallbacks(actionMap);
this.controlled = controlled;
}
protected abstract void SetupInputCallbacks(InputActionMap actionMap);
}
RunnerControls.cs
public class RunnerControls : Controls<Runner>
{
public RunnerControls(InputActionMap actionMap, Runner controlled)
: base(actionMap, controlled) { }
protected override void SetupInputCallbacks(InputActionMap actionMap)
{
InputAction runAction = actionMap.FindAction("Run");
runAction.perform += controlled.Run();
}
}
Jumper.cs
public class JumperControls : Controls<Jumper>
{
public JumperControls(InputActionMap actionMap, Runner controlled)
: base(actionMap, controlled) { }
protected override void SetupInputCallbacks(InputActionMap actionMap)
{
InputAction runAction = actionMap.FindAction("Run");
runAction.perform += controlled.Run();
InputAction jumpAction = actionMap.FindAction("Jump");
jumpAction.perform += controlled.Jump();
}
}
The code for the "run action" is duplicated, and although this may seem minor here, my actual code does this to a much greater extent. I tried something like public class JumperControls : RunnerControls, but then the controlled.Jump() would fail since the T in the Controls class evaluates to Runner. If anyone can help me come up with a solution to this that'd be a huge help, even if that means changing the architecture. Thanks!
You can make Runner and Jumper separate interfaces with their respective Run and Jump methods. The actual monobehaviours will be implementing the interfaces and having their own Run and Jump logic. So in the Controls<T> you can check whether the T implements the respective interface:
public class Controls<T>
{
protected T controlled;
public Controls(InputActionMap actionMap, T controlled)
{
SetupInputCallbacks(actionMap);
this.controlled = controlled;
}
protected virtual void SetupInputCallbacks(InputActionMap actionMap)
{
if (T is Runner) ....
if (T is Jumper) ....
}
}
I'm getting an error that I don't understand. The simplified version of my code:
using UnityEngine;
public class RunLater : MonoBehaviour
{
public static void Do()
{
Invoke("RunThisLater", 2.0f);
}
public void RunThisLater()
{
Debug.Log("This will run later");
}
}
You can pass it in as a parameter like this:
public class RunLater : MonoBehaviour
{
public static void Do(RunLater instance)
{
instance.Invoke("RunThisLater", 2.0f);
}
public void RunThisLater()
{
Debug.Log("This will run later");
}
}
One approach is to have the static parts of the class store a MonoBehaviour reference to itself. Like so:
public class RunLater : MonoBehaviour
{
public static RunLater selfReference = null;
public static void Do()
{
InitSelfReference();
selfReference.DoInstanced();
}
static void InitSelfReference()
{
if (selfReference == null)
{
// We're presuming you only have RunLater once in the entire hierarchy.
selfReference = Object.FindObjectOfType<RunLater>();
}
}
public void DoInstanced()
{
Invoke("RunThisLater", 2f);
}
void RunThisLater()
{
Debug.Log("This will run later");
}
}
You would now be able to call RunLater.Do() from anywhere in your code of other gameObjects. Good luck!
I have a class in Unity 2017 that only shows some of the public methods in the inspector.
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
List<ShipPart> _inventory;
int currentInvPosition = 0;
bool invExists = false;
// Use this for initialization
void Start () {
CreateInventory(0, 0);
}
// Show all inventory parts as gameobjects
public void CreateInventory(int quality, int part)
{
...
}
void DestroyInventory()
{
...
}
public void ScrollInvLeft()
{
...
}
public void ScrollInvRight()
{
...
}
void UpdateInv(float offset)
{
...
}
public void AddInventoryItem(ShipPart newShipPart)
{
...
}
public void RemoveInventoryItem(ShipPart oldShipPart)
{
...
}
public void Test1(){}
public void Test2(int i){}
}
I thought it might be because the invisible methods have parameters, so I added the last two methods. However they are visible in the inspector!
I am trying to call the methods from a dropdown UI element, but have also tested from a button and that cant see them either.
What am I doing wrong?
As indicated in the official Unity tutorials, if you want to provide a function to an event in the inspector, the function must meet the following requirements:
The function must be public
The function must have a return type of void
The function must take no or one parameter
If the function takes one parameter, the latter must be one of the following types:
int
float
string
bool
UnityEngine.Object, or any type inheriting from UnityEngine.Object (such as GameObject, MonoBehaviour, ScriptableObject, ...)
Here's what I'm dealing with. I have a Tile class, a TileController, and a TileControllerEditor.
public class Tile
{
public enum TileType {
Blank, Portal
}
public TileType type;
}
public class TileController : MonoBehaviour
{
// The View component of a Tile
public GameObject tileObject;
// The Model component of a Tile
public Tile tile;
public Tile.TileType tileType {
get {
return tile.type;
}
set {
tile.type = value;
}
}
void Awake()
{
tile = new Tile();
}
}
[CustomEditor(typeof(TileController))]
public class TileControllerEditor : Editor
{
TileController tc;
public override void OnInspectorGUI()
{
tc = (TileController)target;
DrawDefaultInspector();
// Provide a dropdown for tileType
tc.tileType = (Tile.TileType)EditorGUILayout.EnumPopup("Tile Type", tc.tile.type);
}
}
I want to make the tileType attribute of the TileController class available in the inspector as a dropdown. The issue I'm having is that in my custom inspector, when the tileType attribute is first accessed, Awake() has not been called yet, so tile is null and I get a NullReferenceException.
How do I make sure that my class members are fully instantiated before they are accessed by the inspector?
You could initialize the Tile object at declaration, avoiding Awake altogether.
public class TileController : MonoBehaviour
{
public GameObject tileObject;
public Tile tile = new Tile();
public Tile.TileType tileType {
get {
return tile.type;
}
set {
tile.type = value;
}
}
}
I came up with a solution that works for me. Because I created the TileControllers in another editor script, I was able to simply add an Init() method to TileController that functions as a constructor, which I call manually every time I create one.
public class TileController : MonoBehaviour
{
// The View component of a Tile
public GameObject tileObject;
// The Model component of a Tile
public Tile tile;
public Tile.TileType tileType {
get {
return tile.type;
}
set {
tile.type = value;
}
}
public void Init()
{
tile = new Tile();
}
}
And then when I create the TileController (They are attached to a prefab, which I instantiate and call GetComponent<TileController>() on):
void GenerateTiles()
{
...
GameObject tileObject = Instantiate(boardController.tilePrefab);
TileController tileController = tileObject.GetComponent<TileController>();
tileController.Init();
...
}