Unity - Struggling with Dependency Injection and Monobehaviour - c#

So I'm experimenting with DI and am trying to create a GameObject Generator.
That GameObject Generator generates GameObjects inside the scene based on some internal logic.
There is variation to what kinds of GameObjects are generated and the logic can vary.
I thought that I could create a Interface and be able to create a class per "unique logic" (i.e. for every generator behaviour i create a class and can switch between generating a lot of small objects and a couple of big objects without having to use if statements, but instead the power of polymorphism).
So I've got something like
GameObjectGenerator : Monobehaviour
IGeneratorType
SmallGenerator : Monobehaviour, IGeneratorType
BigGenerator : Monobehaviour, IGeneratorType
from a logical standpoint this seems to be making sense.
The issue arrises, when transitioning from those generators.
I want to have some sort of condition, where i call a method "TransitionGenerator" from IGeneratorType
that returns a new IGeneratorType. Logically this is working aswell.
However, i want to keep track of my generated Objects (in a list for instance), because they need to be Destroyed later.
When transitioning, the List of generated Objects need to be passed to the new IGeneratorType.
This is where I find myself struggling.
The classes that implement from IGeneratorType need to extend Monobehaviour aswell because i need to make calls to Instantiate and Destroy.
But because they extend from Monobehaviour, I can't seem to create a constructor.
After a bit of research i found a lot of people pointing to either Awake/Start or to creating a Init method.
The problem is, with Awake/Start i cant pass anything and with Init, i would need to put that into the interface aswell, which doesnt make a lot of to me sense from a design standpoint.
Example code:
public class GameObjectGenerator : Monobehaviour{
private IGeneratorType generator;
public void Start(){
generator = new SmallGenerator();
}
public void Update(){
generator.Generate();
if(somecondition){
generator = generator.Transition();
}
}
}
public interface IGeneratorType{
void Generate();
IGeneratorType Transition();
}
public class SmallGenerator : Monobehaviour, IGeneratorType{
private List<GameObject> generatedObjects;
public SmallGenerator(/*List<GameObject> previousObjects*/){
//generatedObjects = previousObjects;
}
public void Generate(){
//...
if(somespecificcond){
generatedObjects.Add(Instantiate(...));
}
if(somecondition){
Destroy(generatedObjects[0])
}
}
public IGeneratorType Transition(){
return new BigGenerator(/*generatedObjects*/);
}
}
public class BigGenerator : Monobehaviour, IGeneratorType{
private List<GameObject> generatedObjects;
public BigGenerator(/*List<GameObject> previousObjects*/){
//generatedObjects = previousObjects;
}
public void Generate(){
//...
if(somespecificothercond){
generatedObjects.Add(Instantiate(...));
}
if(somecondition){
Destroy(generatedObjects[0])
}
}
public IGeneratorType Transition(){
return new SmallGenerator(/*generatedObjects*/);
}
}

I just found the simplest workaround to this specific case:
public class BigGenerator : IGeneratorType{
private List<GameObject> generatedObjects;
public BigGenerator(/*List<GameObject> previousObjects*/){
//generatedObjects = previousObjects;
}
public void Generate(){
//...
if(somespecificothercond){
generatedObjects.Add(Object.Instantiate(...));
}
if(somecondition){
Object.Destroy(generatedObjects[0])
}
}
public IGeneratorType Transition(){
return new SmallGenerator(/*generatedObjects*/);
}
}
This works because Instantiate and Destroy are static methods from "Object", of which "GameObject" inherits.
However this doesn't solve the problem in case one really HAS TO inherit from monobehaviour

Related

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!)

Reference a class from a ScriptableObject

I have an EnemyData ScriptableObject that holds data about enemies. I'd like to have a field on EnemyData that references some logic about how this enemy behaves on its turn (in turn-based card game).
My current attempt at this is to structure that as a ScriptableObject too, basically like this:
public class EnemyData : ScriptableObject
{
public int health;
public EnemyAIBase enemyAI;
}
public abstract class EnemyAIBase : ScriptableObject
{
public abstract void PlayTurn(Enemy thisEnemy);
}
public class PirateShipAI : EnemyAIBase
{
public override void PlayTurn(Enemy thisEnemy)
{
thisEnemy.Heal();
AttackPlayer();
}
}
So as an example, I've got a "PirateShip" asset of type EnemyData, whose enemyAI field points to a "PirateShipAI" asset of type PirateShipAI.
But this feels wrong, every time I code up a new enemy's AI I have to also instantiate an asset just so it can be referenced by an EnemyData. I feel like EnemyAIBase shouldn't even be an SO, it's not like it has any variables that different assets will override. There will be a 1-to-1 mapping between EnemyData assets and custom AI for that enemy. So this SO is just a container for some logic, which doesn't feel right. But I don't know any other way to reference that logic in my EnemyData SO.
I guess I wish an SO could reference a C# class directly, but I don't think this is possible.
One option is that I could build an Editor that hides the EnemyAI asset as a sub-asset of the EnemyData asset, kinda like I did over here: Building an Editor for nested ScriptableObjects to compose abilities in a card game
But that feels really wrong here, because I don't intend to make any of this AI generic.
How can I attach behavior/logic to a ScriptableObject?
You can indeed simply make it not a ScriptableObject. To define different behaviors I would use a generic here:
public abstract class EnemyData<T> : ScriptableObject where T : EnemyAIBase
{
public int health;
public T enemyAI;
}
public abstract class EnemyAIBase
{
public abstract void PlayTurn(Enemy thisEnemy);
}
And then from these create your actual implementations
[CreateAssetMenu]
public class PirateShip : EnemyData<PirateShipAI>{ }
public class PirateShipAI : EnemyAIBase
{
public override void PlayTurn(Enemy thisEnemy)
{
thisEnemy.Heal();
AttackPlayer();
}
}

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;
}
}

Access a non-MonoBehaviour script from another script (Unity)

I'm working on a Unity project and i would like to access a non MonoBehaviour script from another script.
I can't use GetComponent cause the script isn't MonoBehabiour, is there a solution ?
Here is some code to help you understand :
public class SomeClass {
public static float coolVar = 1.0f;
private string someVar; // EDIT : I need to access this var too and AnotherClass.someVar won't work obviously
public class AnotherClass {
// i want to be able to access coolVar and change her value
// i know i can do SomeClass.coolVar but i was looking for another way close to a GetComponent approach
My SomeClass class is full of static var i need to edit, i didn't implemented those variables and i can't modify them (i know it's bad practices).
Maybe the reflection is the best way :
typeof(SomeClasse).GetField(name).SetValue(null, value);
you only need to include the script with the not MonoBehaviour class inside the Assets path of the project and you will be able to use it inside other behaviour class.
Take care of use only .NET 2.0 specifications in the external class and if u have used a namespace on it add the using in the behaviour script.
#External class
namespace DefNamespace
{
public class ModelList
{
private static List<GameObject> models;
private ModelList ()
{
}
public static List<GameObject> Models{
get{
if(models == null) models= new List<GameObject>();
return models;
}set{
models=value;
}
}
}
}
And then in a MonoBehaviour or another class:
#Behaviour
using DefNamespace;
public class DefBehaviour : MonoBehaviour
{
Start(){
GameObject go=ModelList.Models[0];
}
}

How to Clone a child of an abstract class in WP7 XNA

I'm working on a game for WP7 with XNA. Here is my structure:
public abstract class enemy
{}
Child elements:
public class genericEnemy : enemy{}
...
public class snake : enemy {}
etc...
In WP7, a lot of things have been moved around and/or removed (especially with Serialization) it seems. Despite much searching, I haven't been able to find a solution. I'm trying to duplicate the child elements.
For example: On loading a level, I pass an array of three different enemies into the loading phase. During loading, I need to duplicate each of those enemies so that 20 of each are flying around doing their own thing during gameplay.
All the solutions I've seen refer to things that are not present in the WP7 library.
There's no "library" way of doing this as far as I know. One solution would be:
1) Declare a Clone() method in enemy that returns a copy of that enemy.
abstract class Enemy {
public abstract Enemy Clone();
}
2) Implement it in every concrete type, so a Snake creates a new Snake, etc. Example:
class Snake : Enemy {
int speed;
public override void Enemy Clone() {
var clone = new Snake();
clone.speed = speed;
return clone;
}
}
3) Now any object of a concrete type knows how to clone itself, so if you have an array of Enemies, you can call Clone() on each and it will create the proper concrete type in the proper way.
Create an enemy factory that can create enemies from an id of some sorts. While loading your level, you can then call the factory when you need to create an enemy:
class EnemyFactory
{
Enemy CreateEnemy(int id)
{
if (id == 0)
return new Snake();
return new GenericEnemy();
}
}
void LoadLevel()
{
// bla bla
Level level = new Level();
int enemyId = LoadFromFile();
level.AddEnemy(EnemyFactory.CreateEnemy(enemyId));
}
This way you get rid of the nasty cloning code, and you can control all enemy instantiation in the factory class.
use an abstract method that calls a copy constructor:
public abstract class Enemy
{
private readonly int mEnemyData;
protected Enemy(Enemy pEnemy)
{
mEnemyData = pEnemy.mEnemyData;
}
public abstract Enemy Clone();
}
public sealed class GenericEnemy : Enemy
{
private readonly double mGenericEnemyData;
private GenericEnemy(GenericEnemy pGenericEnemy)
: base(pGenericEnemy)
{
mGenericEnemyData = pGenericEnemy.mGenericEnemyData;
}
public override Enemy Clone()
{
return new GenericEnemy(this);
}
}

Categories

Resources