Delaying the Initiation of MonoBehaviours - c#

I need to initiate my GameManager with data, which is provieded by the LevelLoader. The problem is that some Behaviours need to access some of this data themself on Initation and there are already present, when the scene loads, meaning I'll get a NullReference because the GameManager isn't initiated yet.
class LevelLoader
{
void LoadLevel()
{
// ...
// Initiate Game Manager
GameManager.Instance.Initiate(data);
}
}
class GameManager : MonoBehaviour
{
// ...
public int someNum;
public void Initiate(data)
{
// ...
// This action may take a cuple of frames
// ...
someNum = data.someNum;
}
}
class SomeSceneObject : MonoBehaviour
{
void Awake()
{
// Initiate SomeSceneObject
DoSomething(GameManager.Instance.someNum);
}
}
Option 1:
I was thinking about adding an Interface for any script that requires the GameManager on Setup, which will replace the default Awake and Start methods:
interface IStartable
{
void Awake2();
void Start2();
}
But than I would also have to override the Update method, because it needs to be suppressed until Awake2 and Start2 are called. Which would make this solution even more ugly.
interface IStartable
{
void Awake2();
void Start2();
void Update2();
void LateUpdate();
void FixedUpdate();
}
Option 2:
I was also thinking about moving my initiation code to the Start method and disabling all scripts before there are called and reactivating them when ready.
But than I would have to keep track on all objects I need to disable on my GameManager/LevelLoader Script, which would be even more ugly and difficult to maintain.
Option 2b:
I could also add an Initiate() method to SomeSceneObject and call it when the GameManager is done instead of using Awake() but same problem: I need to keep track of all MonoBehaviours how need to be called on the GameManager which isn't ideal.
Question:
How do I delay the iniation and keep it maintainalbe?

Related

Why my event listener is not working in Unity using C#

So I am working on a game which has both single Player as well as multiplayer functionality in it. On one hand I have a bird script which should function differently on the basis as to whether it is a single player game or a mutli-player game. But I simply can't understand why my event listener is not working as such.
So for the Single Player game I have a script called GameController attached to a gameObject called GameController. In the Start function of it I am firing an event saying that it is a single player. Below is the relevant code:
public class GameController : MonoBehaviour
{
public static Action isSinglePlayer = delegate { };
void Start()
{
isSinglePlayer?.Invoke();
}
}
So in the Bird Pigeon class I have the listener and the relevant code is like this :
public class BirdPigeon : MonoBehaviour
{
public void Awake()
{
PV = GetComponent<PhotonView>();
BattleRandomController.BirdDirectionTransfer+=BirdDirectionMultiPlayer;
GameController.isSinglePlayer += SinglePlayer;
BattleRandomController.isMultiPlayer+=MultiPlayer;
}
void Start()
{
if (isMultiPlayerGame)
{
if (PV.IsMine)
IndicatorForMe.SetActive(true);
else
IndicatorForMe.SetActive(false);
}
}
public void SinglePlayer()
{
Debug.Log("isSinglePlayer function is executed................");
isMultiPlayerGame = false;
IndicatorForMe.SetActive(false);
}
private void MultiPlayer()
{
Debug.Log("MultiPlayer function is executed");
isMultiPlayerGame = true;
IndicatorForMe.SetActive(true);
}
}
But when I run the code the Debug.log statement "isSinglePlayer function is executed................" doesn't get executed at all.
Similarly for the multiplayer game scene I have a gameObject called Battle Random controller attached to game object of the same name and in the Start function of that I am firing an event to indicate it is a multiplayer and still the above listener for that which is Multiplayer, the debug.log statement doesnt get executed only i.e.,"MultiPlayer function is executed"
The Relevant code for the multiplayer scene is as follows:
public class BattleRandomController : MonoBehaviour
{
public static Action isMultiPlayer=delegate{};
void Start()
{
isMultiPlayer?.Invoke();
}
}
Earlier I had fired the event in the Awake and was listening in the Start of the bird pigeon class which somewhere I read was wrong. Now there is a listener ready in the awake function of the BirdPigeon class but I can't understand as to why the listener is not functioning .
The correct way to declare an event would be like
public event EventHandler MyEvent;
and raise it like
MyEvent?.Invoke(this, EventArgs.Empty)
You could use an arbitrary delegate in place of EventHandler or EventHandler<T>, but the most code I have seen uses EventHandler for events.
Note that you are using a static event, and there are several issues with such events. It may make thread safety more difficult, and can easily create memory leaks if you are not very through with unsubscribing. The singleton pattern might be an improvement, but I would recommend such singletons to only be global within some "context", and not for the entire application, that can make it easier to cleanly dispose of that context.
There is also the issue of timing, if you subscribe after raising the event you will obviously not get it. Easiest way to confirm this is by debugging. Add a breakpoint where you raise the event, and check that it is not null, that should also allow you to see if any breakpoints inside your event handler is hit.
Also, using events to determine the type of game seem fragile to me. You should ideally have some abstraction layer to isolate any multiplayer specific parts.
maybe you should try to replace your action by this :
public class GameController : MonoBehaviour
{
public event Action isSinglePlayer;
void Start()
{
isSinglePlayer?.Invoke();
}
}

Better way to have static class / method access a gameObject variable from several scripts away?

TLDR: How can I have a script that inherits from a public abstract class have access to an often changing Enemy gameObject variable (so it can't be static) without passing it through several other scripts first?
In my game, I have a battle system where a different "Battle Event" gets loaded for each battle. Each "Battle Event" gets its own script, and each of those events inherits from the same BattleEvent parent (which is public abstract).
The code structure basically goes:
BattleSystem (main brain of battles which holds the Enemy
gameObject) ->
BattleEventsManager (handles both which BattleEvent to load, and which methods to run on that BattleEvent) ->
a random BattleEvent (BattleEventOne or BattleEventTwo etc)
public class BattleSystem : MonoBehaviour
{
BattleEventsManager battleEventsManager;
public Enemy currentEnemy;
// the Enemy data is passed when the battle starts
public void Start(Enemy enemyToLoad)
{
battleEventsManager = GetComponent<BattleEventsManager>();
currentEnemy = enemyToLoad;
}
public void BeginPlayerTurn()
{
battleEventsManager.SetupEvent(currentEnemy);
}
}
public class BattleEventsManager : MonoBehaviour
{
BattleEvent currentBattleEvent;
private void Awake()
{
// define this battleEvent
currentBattleEvent = GetComponent<BattleEventOne>();
}
public void SetupEvent(Enemy currentEnemy)
{
// start the battleEvent with its Setup function
currentBattleEvent.Setup(currentEnemy);
}
}
// inherits from `BattleEvent` parent class, shown below
public class BattleEventOne : BattleEvent
{
// override the method from the parent
public override void Setup(Enemy currentEnemy) {
// we can now use the data we need in `currentEnemy`
// all I wanted was to get access to `BattleSystem.currentEnemy`
// but i had to pass it down all the way here. Is there a better way?
}
}
// parent of all `BattleEvents`
public abstract class BattleEvent : MonoBehaviour
{
public abstract void Setup(Enemy currentEnemy);
} // end BattleEvent class
As you can see, the the currentEnemy variable needs to be passed down through 2 classes in order to get to where it needs to be: BattleEventOne.Setup().
Furthermore, I needed to add the Enemy currentEnemy param to the parent BattleEvent, which is problematic because not all BattleEvents will need this information.
I originally wanted to just call BattleSystem.currentEnemy from BattleEventOne (using a property or something), but because the BattleSystem is abstract/static, it can't access it. And because currentEnemy contains new data each battle, I can't make that a static variable.
So, how can I have BattleEventOne here access BattleSystem.currentEnemy without having to pass it down as I've done above?
(I still struggle a lot with passing information between scripts, so any help here is really appreciated!)

Errors that i don't know how to solve with standard component for unity

The unity standard component comes pre-packaged with errors on visual studio and im trying to learn so I tried playing around with the auto-correct system and got it down to the fewest errors I could.
I made sure I have the most up to date unity and VisualStudio with all relevant plugins.
https://gist.github.com/EgoSphere001/288818beb7ad0d2db2bdda028508c76e
Please look at your script again
public class PlayerMovement : MonoBehaviour
{
public PlayerMovement()
{
}
}
public Rigidbody rb;
// Start is called before the first frame update
private void Start() => Debug.Log("Hello, World!");
// Update is called once per frame
private void Update()
{
}
You are closing the PlayerMovement class ... so where does rb and Update belong to?! You expression body for Start is ok-ish but I would suggest to stick with a normal method declaration.
Also MonoBehaviours can not be generated using a constructor so the public PlayerMovement(){ makes no sense either.
Your script should probably look like this
public class PlayerMovement : MonoBehaviour
{
public Rigidbody rb;
// Start is called before the first frame update
private void Start()
{
Debug.Log("Hello, World!");
}
// Update is called once per frame
private void Update()
{
}
}
Also note: You should remove any empty Unity messages like Update, Start etc .. because Unity calls them anyway causing unnecessary overhead.

Don't use Interfaces in Unity?

in Unity I make use of interfaces. I set a logic for components which are totally different to each other.
Examples:
A car, a dog and a aircraft would implement IMovable. I can call Move() from each component but these components execute different code.
Same for ISavable, each component, that has to save data to the database could save the stuff when looping through all savables.
The problem:
Some people in forums say that interfaces are bad for Unity.
When destroying a gameobject and call its interface method this still gets executed.
No error would come up because Destroy() does not destroy objects. Unity as a C++ driven Engine would setup a C# wrapper for the objects. These objects just get a flag destroyed which is a bool.
Destroyed gameobjects will not get destroyed immediately, they will be destroyed later on at the end of the frame.
Until this end of the frame is not reached the method can still get called from the destroyed object.
The best way would be using abstract classes only and never use interfaces because of the bad behaviour coming up when destroying objects.
I tested this with a small example, I created the following scripts:
public interface IIntfacable
{
void DoSomething();
void DestroyComponent();
}
public class bar : MonoBehaviour
{
private IIntfacable i;
private void Start()
{
i = FindObjectOfType<foo>().GetComponent<IIntfacable>();
}
private void Update()
{
i.DoSomething();
i.DestroyComponent();
i.DoSomething();
}
}
public class foo : MonoBehaviour, IIntfacable
{
public void DoSomething()
{
Debug.Log("=> DoSomething");
}
public void DestroyComponent()
{
Debug.Log("=> DestroyComponent");
Destroy(gameObject);
}
}
When executing this code I get the following result
Workaround:
I could create an abstract base class and choose between
public abstract void Foo();
and
public virtual void Bar()
{
return;
}
but this might lead to overengineering. Because all Scripts would need this base class whether they need this method or not.
Conclusion:
Should I prevent using interfaces?
I am confident in saying there is no harm in using interfaces.
The underlying fear is about keeping track of unmanaged references, a problem which will still be there weather you are using interfaces, abstract classes or whatever. You simply have to make sure that your game code will not try to access any objects which have been Destroy()ed.
Basically, I just construct a collection of objects that I know are not destroyed in my scene, and remove them after destruction.
With risk of answering an xy-problem, If you are scared to miss out on your reference count anyway or there is something in particular which wont allow creating such a list, there is not really any magic wand here, but there are a few precedent patterns in the .net framework with the IDisposable interface/pattern that may lead the way.
Many implementations of these patterns checks a flag in a few public-facing methods of the object. IDisposable.Dispose() would set the flag to true and throw an ObjectDisposedException on some public method if this is set to true, analog to MissingReferenceException in this case. Some patterns will then expose the flag IsDisposed, so that other objects that use the implementation can check instead of doing a try-catch on any access to the object. Your analog could be IsDestroyed, and you should set it in the override of OnDestroy.
You could change your method update like this (well it's not really a use case, why would you try to use it after destroying it, but to show my point):
private void Update()
{
i.DoSomething();
i.DestroyComponent();
if (!i.IsDestroyed) {
// This will not be called
i.DoSomething();
}
}
and implementation could be
public interface IIntfacable : IDestroyable
{
void DoSomething();
}
public interface IDestroyable
{
void DestroyComponent();
bool IsDestroyed { get; }
}
public class foo : MonoBehaviour, IIntfacable
{
bool IsDestroyed { get; private set; }
public void DoSomething()
{
Debug.Log("=> DoSomething");
}
public void DestroyComponent()
{
Debug.Log("=> DestroyComponent");
Destroy(gameObject);
}
public override OnDestroy() {
base.OnDestroy();
IsDestroyed = true;
}
}

How to send integer value to another script?

I am making a game where the player first has to choose the type of control to use before playing. The three options being: Keyboard, Controller, Touch
The player must click the button corresponding to his choice. Each button runs this script when clicked on:
public class KeyboardButton : MonoBehaviour {
public static int controller;
public void buttonClick () {
controller = 1;
}
}
In reality, each button as its own script, where the value of controller is different depending on the script ran. The idea is that the value of this integer would be sent over to the script responsible of controlling the player so it will make use of the demanded input type. ie: if the keyboard button is selected, it will run the corresponding script, setting the integer value to 1. After the PlayerController script receives this value, it will know to only accept input from the keyboard.
I have consulted a lot of documentation, but a lot of it contains context-specific C# things that I don't understand and are irrelevant to what I want to do.
Also, I would not like an answer around the lines of: "You don't have to make the player choose a control type, here's how you can make your game accept all types of control at once." I already know all this stuff and there is a reason I want the player to make a choice. Furthermore, I would still like to know a way to transfer integers to be able to be more organized, rather than having a single script that does 90% of the things in the game.
There are three way you can pass value to another script.
GetComponent
You can use GetComponent method to get another script.
public class KeyboardButton : MonoBehaviour {
public int controller;
//this is anotherScript instance
public AnotherScript anotherScript;
Start()
{
anotherScript = GameObject.Find("Name of Object").GetComponent<AnotherScript>();
}
public void buttonClick () {
controller = 1;
anotherScript.sendValue(controller); //send your value to another script
}
}
Singleton
Let AnotherScript be a static Singleton,You can get the instance on other side.
public class AnotherScript : MonoBehaviour
{
//need to be static
public static AnotherScript Current;
Start()
{
if(Current == null)
{
Current = new AnotherScript();
}
}
public void sendValue(int val)
{
//todo
}
}
public class KeyboardButton : MonoBehaviour
{
public int controller;
public void buttonClick () {
controller = 1;
AnotherScript.Current.sendValue(controller);//send your value to another script
}
}
SendMessage
If you want to send a value to otherscript,SendMessage is a simple way you can choose.
ps:SendMessage method can just send a parameter.
public class KeyboardButton : MonoBehaviour
{
public void buttonClick ()
{
controller = 1;
GameObject.Find("name of object").SendMessage("sendValue",controller);
}
}
As pointed out in one of the comments, you already exposed that value, you can refer to is via
Debug.Log(KeyboardButton.controller);
without providing an instance. There's multiple other ways of doing it, as this way is only good to a certain level of complexity, after which it starts to get more muddy, but depending on what you need right know it might get you through. It is one of the valid ways, and probably the simplest one.
You may also want to know when the value has changed, for example you could use UntiyEvent and trigger it when value is changed
public class KeyboardButton : MonoBehaviour {
public UnityEvent OnValueChanged;
public static int controller;
public void buttonClick () {
controller = 1;
OnValueChanged.Invoke();
}
}
this is if you like to wire events in the editor. You could also do:
public class KeyboardButton : MonoBehaviour {
public static UnityEvent OnValueChanged;
public static int controller;
public void buttonClick () {
controller = 1;
OnValueChanged.Invoke();
}
}
the downside is that the event won't show up in the editor,but the upside is that you can set up a trigger without having to have a reference to the KeyboardButton instance that just got clicked.
public class ControllerChangeReactor : MonoBehaviour {
void Start()
{
KeyboardButton.OnValueChanged.AddListener(React); // add event listener
}
void React() // will get called when keyboard is clicked
{
Debug.Log(KeyboardButton.controller);
}
}
This approach can become limiting after you've written a dozen or so of those scripts, but a step up involves tailoring a custom system which is probably not worth it on your level (just yet). You can finish a simple game using the above approach, even if its not the most elegant.
You could also parametrize your script (expose 'WHAT DOES IT CHANGE' in editor), to avoid unnecessary multiplication of code

Categories

Resources