Wait for GetComponent() to load - c#

When calling GetComponent() just after my program starts up, I find that the method sometimes does not return a component quickly enough to prevent a null reference exception when code later tries to access the component member variable. My question is - is there a way to wait for GetComponent to finish finding what it needs to? I know can wait using coroutines, but is there another way to do this with some kind of lambda callback or event?
public class GameManager : MonoBehaviour
{
public bool AutosaveEnabled = true;
public static GameManager Instance;
[HideInInspector] public InputManager InputManager;
[HideInInspector] public UIManager UIManager;
...
private void Awake()
{
Setup();
}
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
UIManager = GetComponent<UIManager>();
...
UIManager.Setup();
...
}
public class UIManager : StateMachine, IUIManager
{
public static UIManager Instance;
public ITitleMenu TitleMenu;
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
TitleMenu = GetComponentInChildren<ITitleMenu>();
}
private void SetupScene()
{
UIManager.Instance.ChangeState<TitleMenuState>();
}
...
}
public interface ITitleMenu : IMenu
{
void ExitGame();
void LoadTitleMenuScene();
void OnNewGameClick();
}
public interface IMenu
{
public void Setup(IUIManager uiManager);
public void SetActive(bool toggle);
int selectedIndex { get; set; }
int previouslySelectedIndex { get; set; }
TextMeshProUGUI[] Options { get; set; }
void OnControllerMoveDown();
void OnControllerMoveUp();
void OnControllerConfirm();
}
public class TitleMenu : MenuBase, ITitleMenu
{
private enum MenuElements { Continue, NewGame, Controls, VideoSettings, AudioSettings, ExitGame };
public void Setup(IUIManager uiManager)
{
this.uiManager = uiManager;
DataManager.Instance.SaveFileMetadata = GameManager.Instance.SaveFileManager.GetSaveFileMetadata();
if (DataManager.Instance.SaveFileMetadata.Count > 0)
{
Options[(int)MenuElements.Continue].transform.parent.gameObject.SetActive(true);
selectedIndex = (int)MenuElements.Continue;
}
else
{
Options[(int)MenuElements.Continue].transform.parent.gameObject.SetActive(false);
selectedIndex = (int)MenuElements.NewGame;
}
previouslySelectedIndex = selectedIndex;
}
...
}
public class StateMachine : MonoBehaviour, IStateMachine
{
public virtual State CurrentState
{
get
{
return _currentState;
}
set
{
if (_currentState == value)
{
return;
}
if (_currentState != null)
{
_currentState.Exit();
}
_currentState = value;
if (_currentState != null)
{
_currentState.Enter();
}
}
}
protected State _currentState;
public virtual T GetState<T>() where T : State
{
T target = GetComponent<T>();
if (target == null)
{
target = gameObject.AddComponent<T>();
target.Initialize();
}
return target;
}
}
...
public class TitleMenuState : UIState
{
protected override void OnMove(object sender, InfoEventArgs<Vector2> e)
{
if (e.info.y == 1)
{
owner.TitleMenu.OnControllerMoveUp();
}
else if (e.info.y == -1)
{
owner.TitleMenu.OnControllerMoveDown();
}
}
protected override void OnInteract(object sender, EventArgs e)
{
owner.TitleMenu.OnControllerConfirm();
}
public override void Enter()
{
owner.TitleMenu.SetActive(true);
owner.TitleMenu.Setup(owner);
EventManager.UIMoveEvent += OnMove;
EventManager.UISubmitEvent += OnInteract;
}
public override void Exit()
{
UIManager.Instance.TitleMenu.SetActive(false);
EventManager.UIMoveEvent -= OnMove;
EventManager.UISubmitEvent -= OnInteract;
}
}
public abstract class State : MonoBehaviour
{
public virtual void Enter()
{
}
public virtual void Exit()
{
}
public virtual void Initialize()
{
}
}
public abstract class UIState : State
{
protected UIManager owner;
public override void Initialize()
{
owner = UIManager.Instance;
}
protected virtual void OnInteract(object sender, EventArgs e)
{
}
protected virtual void OnCancel(object sender, EventArgs args)
{
}
public override void Enter()
{
}
public override void Exit()
{
}
protected virtual void OnMove(object sender, InfoEventArgs<Vector2> e)
{
}
public virtual bool IsStateOfType(UIStates state)
{
return false;
}
}
Right now the game crashes in TitleMenuState.Enter() where I'm calling owner.TitleMenu.SetActive() because TitleMenu is null.
Hierarchy:

At the time TitleMenu = GetComponentInChildren<ITitleMenu>(); is run in the UIManager component of the GameManager gameobject, the child gameobject TitleMenu is inactive, and that child is what has the ITitleMenu on it. And, from the documentation on GetComponentInChildren (emphasis mine):
Returns the component of Type type in the GameObject or any of its children using depth first search.
A component is returned only if it is found on an active GameObject.
So that will return null. This has nothing to do with failing to return "quickly enough".
A very simple workaround is to use GetComponentsInChildren, which has an optional includeInactive parameter that will allow for searching inactive objects. Using GetComponentsInChildren, with includeInactive as true should have the desired result, only needing to index the first element (since it returns an array):
public class UIManager : StateMachine, IUIManager
{
public static UIManager Instance;
public ITitleMenu TitleMenu;
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
TitleMenu = GetComponentsInChildren<ITitleMenu>(true)[0];
}

You should call GetComponent() before using the component. You could also check out Script Execution order menu (Edit - Project Settings - Script Execution order)

Related

Unity new Input system not being triggered

I'm having a weird issue where any input is not being triggered 'at all'.
Pulling my hairs out; can't find out where this is wrong.
Result is that nothing is being written; although all things are set.
In the input asset I have added the 'Interact' and 'Application Quit' entries.
Anyone could have a glimpse of what's wrong here ?
public class InputManager : MonoBehaviour
{
[SerializeField] private InputActionAsset _actions;
public InputActionAsset actions
{
get => _actions;
set => _actions = value;
}
private InputAction quitInputAction { get; set; }
private InputAction interactInputAction { get; set; }
private void OnEnable()
{
quitInputAction?.Enable();
interactInputAction?.Enable();
Setup();
}
private void Setup()
{
interactInputAction = actions.FindAction("Interact");
if (interactInputAction != null)
{
interactInputAction.started += OnInteract;
interactInputAction.performed += OnInteract;
interactInputAction.canceled += OnInteract;
}
else
{
Debug.LogError("Missing Interact Binding");
}
quitInputAction = actions.FindAction("Application Quit");
if (quitInputAction != null)
{
quitInputAction.started += OnAppQuit;
quitInputAction.performed += OnAppQuit;
quitInputAction.canceled += OnAppQuit;
}
else
{
Debug.LogError("Missing Application Quit Binding");
}
}
protected virtual void OnAppQuit(InputAction.CallbackContext context)
{
if (context.started || context.performed)
{
Debug.Log("Quit");
Application.Quit();
}
else if (context.canceled)
Debug.Log("Application Quit Cancelled");
}
protected virtual void OnInteract(InputAction.CallbackContext context)
{
if (context.started || context.performed)
{
Debug.Log("Interact");
}
else if (context.canceled)
Debug.Log("Application Quit Cancelled");
}
}
You need to run your setup before attempting to enable them:
private void OnEnable()
{
Setup();
quitInputAction?.Enable();
interactInputAction?.Enable();
}
Source: Disco Fever

The script needs to Derive from MonoBehaviour, can not take parameters

I have project in Unity and also new to C#. Problem is that I got error The script needs to Derive from MonoBehaviour. I understand what does that mean but when I use MonoBehaviour I'm getting tons of Errors like this:
Errors in console
Will be glad if anyone can explain what am i doing wrong - thank you good people of stackoverflow!
Every script is connected to BaseWindow
namespace BlGame.View
{
public abstract class BaseWindow
{
protected Transform mRoot;
protected EScenesType mScenesType;
protected string mResName;
protected bool mResident;
protected bool mVisible = false;
public abstract void Init();
public abstract void Realse();
protected abstract void InitWidget();
protected abstract void RealseWidget();
protected abstract void OnAddListener();
protected abstract void OnRemoveListener();
public abstract void OnEnable();
public abstract void OnDisable();
public virtual void Update(float deltaTime) { }
public EScenesType GetScenseType()
{
return mScenesType;
}
public bool IsVisible() { return mVisible; }
public bool IsResident() { return mResident; }
public void Show()
{
if (mRoot == null)
{
if (Create())
{
InitWidget();
}
}
if (mRoot && mRoot.gameObject.activeSelf == false)
{
mRoot.gameObject.SetActive(true);
mVisible = true;
OnEnable();
OnAddListener();
}
}
public void Hide()
{
if (mRoot && mRoot.gameObject.activeSelf == true)
{
OnRemoveListener();
OnDisable();
if (mResident)
{
mRoot.gameObject.SetActive(false);
}
else
{
RealseWidget();
Destroy();
}
}
mVisible = false;
}
//预加载
public void PreLoad()
{
if (mRoot == null)
{
if (Create())
{
InitWidget();
}
}
}
//延时删除
public void DelayDestory()
{
if (mRoot)
{
RealseWidget();
Destroy();
}
}
private bool Create()
{
if (mRoot)
{
Debug.LogError("Window Create Error Exist!");
return false;
}
if (mResName == null || mResName == "")
{
Debug.LogError("Window Create Error ResName is empty!");
return false;
}
if (GameMethod.GetUiCamera.transform== null)
{
Debug.LogError("Window Create Error GetUiCamera is empty! WindowName = " + mResName);
return false;
}
GameObject obj = LoadUiResource.LoadRes(GameMethod.GetUiCamera.transform, mResName);
if (obj == null)
{
Debug.LogError("Window Create Error LoadRes WindowName = " + mResName);
return false;
}
mRoot = obj.transform;
mRoot.gameObject.SetActive(false);
return true;
}
//销毁窗体
protected void Destroy()
{
if (mRoot)
{
LoadUiResource.DestroyLoad(mRoot.gameObject);
mRoot = null;
}
}
public Transform GetRoot()
{
return mRoot;
}
}
}
Something like This:
public class UIGuideWindow : BaseWindow
{
public UIGuideWindow()
{
//mScenesType = EScenesType.EST_Login;
//mResName = GameConstDefine.UIGuideRestPath;
//mResident = false;
}
Unity Update Function cannot have any paramaters like this : Update(float deltaTime), this is unity not unreal engine:))
To fix this, Remove float deltaTime , and instead use Time.deltaTime inside the function implementation itself

Returning a function if specific conditions are not followed

Is it possible to not execute functionC if conditions are not followed in functionA?
public void functionA()
{
if(!specificCondition)
{
return;
}
}
public void functionB()
{
functionA();/*Conditions did not meet so i no longer want the next
function to execute anymore */
functionC();
}
public void functionC()
{
Console.WriteLine("OK");
}
Sure, check the condition before you call them:
public void functionB()
{
if(specificCondition)
functionA();
if(specificCondition)
functionC();
}
Another option is to return a bool which you could check before you call the next method.
Just return a bool from functionA
public bool functionA()
{
if(!specificCondition)
return true;
return false;
}
public void functionB()
{
if(!functionA())
functionC();
}
public void functionC()
{
Console.WriteLine("OK");
}
I should make clear that returning a boolean condition from the functions is the correct way to proceed. If, however, you find yourself in a situation where you can't change either the parameters or the return type of the other methods, you can use a class-level variable.
class myClass
{
var specificCondition = false;
public void functionA()
{
var resultOfThisMethod = specificCondition;
/*do stuff
*
*
*/
if(resultOfThisMethod != testedcondition)
specificCondition = false;
}
public void functionB()
{
functionA();/*Conditions did not meet so i no longer want the next
function to execute anymore */
specificCondition ? functionC() : return;
}
public void functionC()
{
Console.WriteLine("OK");
}
}
I hope this Code will help you... In this we can get
specificCondition from functionA as ref and used in functionB to
take decision to functionC.
Using Ref
public void functionA(ref bool cond)
{
cond = specificCondition;
if (!specificCondition)
{
return;
}
}
public void functionB()
{
bool cond = false;
functionA(ref cond);
if (cond)
{
functionC();
}
}
public void functionC()
{
Console.WriteLine("OK");
}
Using Out.
public static void functionA(out bool cond)
{
bool specificCondition = true;
cond = specificCondition;
if (!specificCondition)
{
return;
}
}
public static void functionB()
{
bool cond;
functionA(out cond);
if (cond)
{
functionC();
}
}
public static void functionC()
{
Console.WriteLine("OK");
}

Inheritance, need advices

Let's say I have an ai or player, I want him to be able to use different weapons.
My design with weapons:
public class Weapon()
{
public virtual void FireWeapon(){} // this is useless for melee weapons
public virtual void SwingMelee(){} // this is useless for guns
public virtual void Reload(){} // this is also useless for melee weapons
}
Then in the ai controller class I simply call the function I want him to do.
This is where the ugly part is (I think)...
Controller class have a list containing some different weapons of ai and a weapon which is being used.
public class WeaponController
{
private List<Weapon> someWeapons;
private Weapon aWeapon;
public void Main()
{
if(/*"Some action or a button click" &&*/ aWeapon.CanSwingMelee() )
aWeapon.SwingMelee();
if(/*"Some action or a button click" &&*/ aWeapon.CanReload() )
aWeapon.Reload();
}
}
What is the better way to implement this? do you have any advices?
Seems that for every different action in a new weapon, I need to implement a function in the most parent Weapon class and I don't think it's a good idea...
The capability of an in-game object can be represented by an interface; you can check if a capability is present by attempting to cast to the interface. What's more, these interfaces can overlap, e.g. both melee and ranged weapons might both have an Attack method.
So for example:
public interface IWeapon
{
void Attack();
}
public interface IRangedWeapon
{
bool IsInRange(ITargetable target);
}
public interface IRequiresAmmunition
{
void Reload();
int AmmoRemaining { get; set; }
}
public class Sword : IWeapon
{
public virtual void Attack() { //code }
}
public class Rifle : IWeapon, IRequiresAmmunition, IRangedWeapon
{
public virtual void Attack() { //code }
public virtual void Reload() { //code }
public virtual int AmmoRemaining { get { } set { } }
public virtual bool IsInrange (ITargetable target) { //code }
}
public class LaserGun: IWeapon, IRangedWeapon
{
public virtual void Attack() { //code }
public virtual bool IsInrange (ITargetable target) { //code }
}
public class WeaponController
{
private List<IWeapon> someWeapons;
private IWeapon aWeapon;
private ITargetable currentTarget;
public void Weapon_OnUse()
{
if (!currentTarget.IsHostile) return;
if (this.IsInMeleeRange(currentTarget))
{
aWeapon.Attack();
return;
}
var w = aWeapon as IRangedWeapon;
if (w != null && w.IsInRange(currentTarget)
{
aWeapon.Attack();
return;
}
context.HUD.Warn("Out of range");
}
public void Weapon_OnReload()
{
var w = aWeapon as IRequiresAmmunition;
if (w != null)
{
w.Reload();
context.HUD.DisplayAmmo(w.AmmoRemaining);
}
}
}
This seems like what abstract classes and inheritance is for:
public abstract class Weapon {
public abstract void Attack();
public abstract void Reload();
}
public class MeleeWeapon : Weapon {
public override void Attack() {
// swing sword
}
public override void Reload() {
// ignore reload
}
}
public class GunWeapon : Weapon {
public override void Attack() {
// fire gun
}
public override void Reload() {
// load weapon from inventory
}
}
public class WeaponController {
private List<Weapon> someWeapons;
private Weapon aWeapon;
public void Main() {
if (/*"Some action or a button click" */)
aWeapon.Attack();
else if (/* some other button click */)
aWeapon.Reload();
}
}
I don't recommend an approach that requires you to create new interfaces for every new behavior and check the type of the weapon. What about something like this:
(This is a very rough draft.)
public abstract class Weapon
{
protected Weapon(WeaponCommandStrategy[] commandStrategies)
{
CommandStrategies = commandStrategies;
}
protected IEnumerable<WeaponCommandStrategy> CommandStrategies { get; }
public void Use(WeaponCommand command)
{
var strategy = CommandStrategies.FirstOrDefault(c => c.Command == command);
strategy?.Execute();
}
}
public enum WeaponCommand
{
Fire,
Swing,
Reload
}
public abstract class WeaponCommandStrategy
{
public WeaponCommand Command { get; private set; }
protected WeaponCommandStrategy(WeaponCommand command)
{
Command = command;
}
public abstract void Execute();
}
Now you can give a weapon whatever behaviors you want it to have in the form of various instances of WeaponCommandStrategy. If a command is sent to a weapon, it executes it. If it doesn't support a command it ignores it. You could add a property to a weapon exposing the available commands so that you could display a list of available commands.
public class Sword : Weapon
{
// Perhaps use dependency injection here
public Sword()
: base(new WeaponCommandStrategy[] { new SwordSwingStrategy() })
{
}
}
public class SwordSwingStrategy : WeaponCommandStrategy
{
public SwordSwingStrategy() : base(WeaponCommand.Swing) { }
public override void Execute()
{
// Do whatever it does
}
}
This essentially makes a Weapon a composition of various things that a weapon can do. If several weapons behave similarly they can share strategies vs. having code duplicated between various weapons.

How to pass a List<Interface> and an implemented method from another class

I have a Game Manager, which is used to manage the execution order of specific Unity callbacks (FixedUpdate, Update and LateUpdate) in all the other scripts.
Specifically, I wrote these 3 interfaces:
public interface IFixedAt {
bool IsActive { get; }
void FixedAt();
}
public interface IUpdateAt {
bool IsActive { get; }
void UpdateAt();
}
public interface ILateUpdateAt {
bool IsActive { get; }
void LateUpdateAt();
}
These interfaces are implemented in game objects' scripts, where needed, like this for example:
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour, IUpdateAt, ILateUpdateAt {
[SerializeField]
bool isActive = true;
public bool IsActive {
get {
return (isActive && gameObject.activeInHierarchy);
}
}
public void UpdateAt() {
// Do stuff in Update
}
public void LateUpdateAt() {
// Do stuff in Late Update
}
}
The Game Manager script gets at Awake the reference to all scripts which implement the interfaces, creating a List<Interface> and then uses the list at runtime to execute the callbacks only where needed:
using UnityEngine;
using System.Collections.Generic;
public class GameManager : MonoBehaviour {
public List<GameObject> GameObjectsWithScripts;
List<IFixedAt> fixedAtList { get; set; }
List<IUpdateAt> updateAtList { get; set; }
List<ILateUpdateAt> lateUpdateAtList { get; set; }
private void Awake() {
PopulateAllLists();
}
private void FixedUpdate() {
if (fixedAtList != null) {
for (int i = 0; i < fixedAtList.Count; i++) {
if (fixedAtList[i].IsActive)
fixedAtList[i].FixedAt();
}
}
}
private void Update() {
if (updateAtList != null) {
for (int i = 0; i < updateAtList.Count; i++) {
if (updateAtList[i].IsActive)
updateAtList[i].UpdateAt();
}
}
}
private void LateUpdate() {
if (lateUpdateAtList != null) {
for (int i = 0; i < lateUpdateAtList.Count; i++) {
if (lateUpdateAtList[i].IsActive)
lateUpdateAtList[i].LateUpdateAt();
}
}
}
void PopulateAllLists() {
fixedAtList = PopulateList<IFixedAt>(GameObjectsWithScripts);
updateAtList = PopulateList<IUpdateAt>(GameObjectsWithScripts);
lateUpdateAtList = PopulateList<ILateUpdateAt>(GameObjectsWithScripts);
}
List<T> PopulateList<T> (List<GameObject> goScripts) {
//Scans the GOs list and adds existent interface elements to the list
var list = new List<T>();
for (int i = 0; i < goScripts.Count; i++) {
if (goScripts[i].GetComponent<T>() != null) {
list.Add(goScripts[i].GetComponent<T>());
}
}
//Returns list (null if list is empty)
if (list.Count > 0) {
return list;
}
else {
return null;
}
}
}
Now, the question for which I'm having trouble in understanding if it's possible to do, and if yes, how.
As you can see, the code inside FixedUpdate, Update and LateUpdate is basically the same: it iterates on the specific List, checks if the current element is active, and if true it executes the proprietary callback.
What I want to know is if it's possible to create a generic method that can be called from inside the three callbacks, and passing to it the List to iterate on and the specific method to call for that List, something like this in pseudo-code:
private void FixedUpdate() {
Method (fixedAtList, FixedAt() );
}
private void Update() {
Method (updateAtList, UpdateAt() );
}
private void LateUpdate() {
Method (lateUpdateAtList, LateUpdateAt() );
}
private void Method<T> (List<T> list, Action method) {
if (list != null) {
for (int i = 0; i < list.Count; i++) {
if (list[i].IsActive)
list[i].method();
}
}
}
I've tried different things, to no success, and atm I'm clueless about how to do that. Any help will be very appreciated.
First you need an interface that covers the IsActive method.
public interface IActive {
bool IsActive { get; }
}
public interface IFixedAt : IActive {
void FixedAt();
}
public interface IUpdateAt : IActive {
void UpdateAt();
}
public interface ILateUpdateAt : IActive {
void LateUpdateAt();
}
Then you need to use the generic Action<T> and then you can pass in lambdas
private void FixedUpdate() {
Method (fixedAtList, f => f.FixedAt() );
}
private void Update() {
Method (updateAtList, u => u.UpdateAt() );
}
private void LateUpdate() {
Method (lateUpdateAtList, l => l.LateUpdateAt() );
}
private void Method<T> (List<T> list, Action<T> method) where T : IActive {
if (list != null) {
for (int i = 0; i < list.Count; i++) {
if (list[i].IsActive)
method(list[i]);
}
}
}

Categories

Resources