How to create a MonoBehaviour fabric for non-MonoBehaviour classes? - c#

I am developing a small nuclear reactor simulator game. I have a bunch of reactor component classes: HeatVent, HeatExchanger, UraniumCell etc. They are not deriving from MonoBehaviour since they don't have any Unity logic, but they do implement a shared interface IReactorComponent. What I want to do is to be able to create prefabs of such components (simple heat vent, advanced heat vent, doubled uranium cell etc.) The prefabs would have different sprites and something like that, but the main issue is to define what reactor component class the prefab is related to, because I can't just drag'n'drop a non-MonoBehaviour script on inspector. Also, I want to be able to set settings in the inspector (for example, HeatVent has CoolAmount and HeatCapacity properties, UraniumCell has FuelAmount, HeatProduce and PowerProduce properties).
I have read about factory method pattern and as I understood, I have to create a fabric class that derives from MonoBehaviour for each reactor component class like HeatVentBehaviour, HeatExchangerBehaviour etc. Yes, that completely solves my issue with prefabs but is there any way to not create an additional MonoBehaviour wrap for each class? If I had 15 IReactorComponent classes, I would need to create 15 fabrics which feels like not the greatest solution.

Sounds like what you are looking for is ScriptableObject!
Instances of those are assets so they don't live in a scene but in the Assets folder and basically behave a little bit like prefabs except: They already exist and do not need to be instantiated anymore.
Mostly they are used as just configurable data containers. They have an Inspector so you can easily fill them with your desired data and references to other assets (e.g. the related prefab in your case).
But in addition you can as well let them implement behavior like your interface and thereby change the behavior of your scene objects by using different implementations of a method from different ScriptableObjects!
For the factory you then only need to figure out for which method to use which ScriptableObject instance e.g. either by having different methods or by having a Dictionary where you fill in your SO references.
Just as an example how this might look like (make sure each MonoBehaviour and ScriptableObject has its individual script file with matching name)
SpawnManager.cs
public class SpawnManager : MonoBehaviour
{
[SerializeField] private ReactorComponentBehaviour _behaviourPrefab;
[SerializeField] private BaseReactorComponent[] _components;
public bool TrySpawn<T>(out T component, out ReactorComponentBehaviour componentBehaviour) where T : IReactorComponent
{
component = default(T);
componentBehaviour = default;
var foundComponent = components.FirstOrDefault(c => c.GetType() == typeof(T));
if(foundComponent == null)
{
Debug.LogError($"No component found of type {T.GetType().Name}!");
return false;
}
// Here Instantiate doesn't spawn anything into the scene but
// rather creates a copy of the ScriptableObject asset
// This is just to avoid that any changes in the fields during the game
// would change the original ScriptableObject asset and thereby ALL related behavior instances
component = Instantiate ( (T) foundComponent);
// This now indeed spawns the related MonoBehaviour + GameOver
componentBehaviour = Instantiate (behaviourPrefab);
componentBehaviour.Init(component);
return true;
}
}
BaseReactorComponent.cs
public abstract class BaseReactorComponent : ScriptableObject, IReactorComponent
{
public abstract void WhateverIReactorComponentNeeds();
// Common fields and methods e.g.
public Sprite Icon;
}
HeatVent.cs
[CreateAssetMenu]
public class HeatVent : BaseReactorComponent
{
public int CoolAmount;
public int HeatCapacity;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
UraniumCell.cs
[CreateAssetMenu]
public class UraniumCell : BaseReactorComponent
{
public int FuelAmount;
public int HeatProduce;
public int PowerProduce;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
And finally you need only one base prefab with the
ReactorComponentBehavior.cs
public class ReactorComponentBehavior : MonoBehaviour
{
[SerializeField] private Image _image;
private IReactorComponent _component;
public void Init(IReactorComponent component)
{
_componemt = component;
// Do other stuff like e.g. adjust visuals according to the component etc
_image.sprite = component.Icon;
}
// And then use whatever this behavior should do with the assigned component
}
So in the end you would use that like e.g.
if(spawManagerReference.TrySpawn<HeatVent>(out var component, out var componentBehaviour)
{
// Do something with the behavior e.g. set its position, parent etc
}
else
{
Debug.LogError($"Failed to get a {nameof(HeatVent)}!");
}
If then at some point you still want different additional behaviours you could let them inherit from the common ReactorComponentBehavior and rather reference the prefabs inside the BaseReactorComponent itself .. then every component can bring its own prefab but still have a common core behaviour

Related

Unity c#: Code-structure or how to access a method/variable from another class/script?

I have two classes: Menu_Buttons, in which there are definitions for methods executed on clicking different buttons in the menu, and PauseMenu, which defines what happens when the Menu key is pressed during the game.
Menu_Buttons:
public class Menu_Buttons : MonoBehaviour
{
public void Menu_NewGameClick()
{
SceneManager.LoadScene(1);
}
public void Menu_ContinueClick()
{
Debug.Log("This will continue the game from the last save");
}
public void Menu_LoadGameClick()
{
SceneManager.LoadScene(1);
Debug.Log("Another menu will show to choose which save to load");
}
public void Menu_SaveGameClick()
{
SaveItem();
Debug.Log("This will save the game");
}
public void Menu_OptionsClick()
{
Debug.Log("This will show the game options");
}
public void Menu_QuitClick()
{
Application.Quit();
Debug.Log("The Game should quit now");
}
}
PauseMenu:
public class PauseMenu : MonoBehaviour
{
//private bool isPauseMenuOpened = false;
public GameObject pauseMenu;
void Update()
{
if (Input.GetKeyDown(KeyCode.B))
{
if (pauseMenu.activeSelf) { ClosePauseMenu(); }
else { OpenPauseMenu(); }
}
}
public void OpenPauseMenu()
{
pauseMenu.SetActive(true);
Cursor.visible = true;
Cursor.lockState = CursorLockMode.Confined;
//isPauseMenuOpened = true;
Time.timeScale = 0f;
}
public void ClosePauseMenu()
{
pauseMenu.SetActive(false);
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
//isPauseMenuOpened = false;
Time.timeScale = 1f;
}
}
I wanted to add another method called Menu_ResumeClick, which would resume the game from the Pause Menu. Of course, I could just create this method in the PauseMenu script and then everything is fine. It looks like this:
public void Menu_ResumeClick()
{
ClosePauseMenu();
}
But since I would like to keep things organised, I thought it would be better to put this method in the Menu_Buttons script along with all the other similar methods. So I tried this:
public void Menu_ResumeClick()
{
PauseMenu.ClosePauseMenu();
}
And then problems begin... I get an error: an object reference is required for the non-static field method or property. Then if I change any of those classes to static, I get errors saying: cannot declare instance members in a static class. Not to mention that static classes canot inherit from MonoBehaviour. Maybe I would be able to solve those problems somehow, but the thing is I don't want to change the whole code just because I would rather have a method in another class. It's just for keeping things organised, nothing more.
I have to admit that I'm a bit frustrated by how these things work. I can easily put the Menu_ResumeClick() method in the PauseMenu class, but in the future it may be difficult to keep track of things if I have various methods scattered around different scripts. The most reasonable solution is to put every menu button in the Menu_Buttons class and then access them from there, but it poses problems that I described. Actually this is not the first time when I'm having problems with accessing methods or variables from other classes - there always seem to be some difficulties. It seems the best way to write code would be to just have a single class for the whole game because then I would be able to access absolutely anything easily - but again the problem would be with keeping things organised.
So, the question is: can I easily use methods (or variables) from other classes, without changing the whole code for this purpose? In other words can I somehow just call a method from another class like this: className.MethodName(); or set a variable from another class like this: className.varName = 2; without making everything static, etc.?
And a bonus question: If it's not possible, then how should I structure my code? Should I try to squeeze as many things as possible into a single class to be able to access them easily, should I make classes static, whenever it's possible, etc.?
In PauseMenu, you can add a field for the Menu_Buttons, which Unity can serialize, so you can pull the GameObject (what contains the Menu_Buttons) in the inspector, and you can call its public methods (and access its public members) from PauseMenu.
public Menu_Buttons MenuButtons;
// or (I much more like this version, keeping things as encapsulated as possible)
[SerializeField] private Menu_Buttons _menuButtons;
private void Resume() => _menuButtons.Menu_ResumeClick();
Edit based on comments:
Both script can have references to each other. As both logically related, I wouldn't separate them, because with the references, we couple them anyway.
Example:
public class Menu_Buttons : MonoBehaviour
{
[SerializeField] private PauseMenu _pauseMenu;
public void Menu_ResumeClick() => _pauseMenu.ClosePauseMenu();
// ...
}
public class PauseMenu : MonoBehaviour
{
[SerializeField] private Menu_Buttons _menuButtons;
// ...
public void ClosePauseMenu()
{
// ...
}
}

How to set up Unity unit tests with *many* Script directories

I've been reading many articles online about how to set up a unit test, and most of all it seems pretty straight forward: you create a Test directory using Test Running in Unity. According to this post here, if you run into a namespace issue, then you create an assembly definition file in your scripts directly, reference it in your test.asmdef file, and boom you can start running tests successfully.
My problem is I've inherited a project with 34 of these Scripts directories, and the moment I add an assembly definition file to one, it creates a namespace issue with all other namespaces/objects. Logical conclusion is I create an .asmdef in each of these files and create references where they are needed. Unfortunately this program was designed in such a way that this creates a cyclical dependency among the assembly definition files. This circular dependency is not an issue in the general usage of the program. Without restructuring the code base, is there a way to make this code testable?
Simple solution would be to add the asmdef to the top folder of your 34 script folders.
If they are all across Assets folder then you can create that Script folder and move them all in there. That should not break your project as Unity will update all connections.
The long term solution you may have to go for is creating abstract/interface in assembly that current code would implement.
Say you have script Player in player.asmdef and you want to test it. But it has a dependency to Inventory which is not in any asmdef. You could move Inventory but it also has its set of dependencies and so on.
Instead of moving Inventory, you create a base inventory as abstract and interface in the manager.asmdef and add this one to player.asmdef. Assuming Player.cs uses
List<Item> Inventory.GetInventory();
void Inventory.SetItem(Item item);
Your IInventory.cs could look like so
public abstract class InventoryBase : MonoBehaviour, IInventory
{
// if those methods were self contained, meaning they don't use any outside code
// the implementation could be moved here
public abstract List<Item> GetInventory();
public abstract void SetItem(Item item);
}
public interface IInventory
{
List<Item> GetInventory();
void SetItem(Item item);
}
public class Item
{
public string id;
public int amount;
public string type;
}
Then the Inventory class
public class Inventory : InventoryBase
{
// Implementation is already there since it was used
// but requires the override on the methods
}
It may feel like adding extra useless layers but this adds a second advantage of major importance, you can mock the IInventory object in your player test:
[Test]
public void TestPlayer()
{
// Using Moq framework but NSubstitute does same with different syntax
Mock<IInventory> mockInventory = new Mock<IInventory>();
Mock<IPlayer> mockPlayer= new Mock<IPlayer>();
PlayerLogic player = new PlayerLogic(mockPlayer.Object, mockInventory.Object);
mock.Setup(m=> m.GetInventory).Returns(new List<Item>());
}
This assumes the Player class is decoupled between the MonoBehaviour and the logic:
public class Player : MonoBehaviour ,IPlayer
{
[SerializedField] private InventoryBase m_inventory;
PlayerLogic m_logic;
void Awake()
{
m_logic = new PlayerLogic(this, m_inventory);
}
}
public interface IPlayer{}
public class PlayerLogic
{
IPlayer m_player;
IInventory m_inventory
public PlayerLogic(IPlayer player, IInventory inventory)
{
m_player = player;
m_inventory = inventory;
}
// Do what you need with dependencies
// Test will use the mock objects as if they were real
}
Notice that Player uses InventoryBase since it cannot see Inventory not being in an assembly. But as you drop in the Inventory object, the compiler will use the code down there even if Player type is not aware of Inventory type.
If you were to use another method from Inventory into Player, then you'd need to add the abstract to the base class and the declaration in the interface for testing.
PlayerLogic uses the interface instead of the base type to make the testing possible.

C# shared property & field inheritance

I've been trying to perfectly structure this project I'm working on in different classes while maximizing the benefits of inheritance. So far however, it's given me more headaches than benefits.
Consider this:
public class SuperClass : MonoBehaviour
{
[SerializeField] protected Camera _camera;
}
and this
public class SubClass : SuperClass
{
}
Both scripts are attached to different game objects in the scene.
The Camera is to be assigned by dragging it in the inspector
I tried this, and unity seemed to tell me that I had to assign the camera to the SuperClass game object AND to the subclass game object, which makes no sense to me.
How can I assign a camera to SuperClass.cs, which is then used and shared by all of its subclasses?
Thanks in advance!
shared by all of its subclasses
Shared by classes could can only be achieved by using "static" (static variable or singleton).
A workaround could be
public class SubClass :SuperClass
{
[SerializeField] Camera camera;
void Awake()
{
if(camera!=null)
{
_camera=camera;
}
}
// Start is called before the first frame update
void Start()
{
camera=_camera;
}
}
To further extend the solution, you could write a editor script or just get the camera from the code.
You need to create public static Camera property somewhere and reference it in your code, using property:
public static class StaticValues
{
public static Camera Camera {get; set;}
}
public class SuperClass : MonoBehaviour
{
[SerializeField] protected Camera _camera
{
get
{
return StaticValues.Camera;
}
set
{
StaticValues.Camera = value;
}
}
}
public class SubClass : SuperClass
{
}

Unity - Using components as interface

The need :
To have the possibility to interact with Component instances of different type attached on GameObject instances through an interface.
For exemple, if I have a game with soldiers, and assuming that medics and snipers both are soldiers, I want to be able to get the Soldier component attached to a soldier GameObject, regardless of whether that soldier is actually a Sniper or a Medic. Then, I could do something as follows : soldier.GetComponent<Soldier>().Respawn(); which would end up calling either Medic.Respawn() or Sniper.Respawn(), depending on the actual type of the soldier.
Possible but dirty solution 1 :
A first naive approach would be to have the Sniper and Medic components implement a Soldier interface. However, this causes several problems.
For example, if you want to check whether a GameObject has a component implementing Soldier, you can't, because Soldier is only an interface, not an actual Unity Component. Thus, calling GetComponent<Soldier>() on a GameObject having, for exemple, a Medic component would not return that Medic component, even if Medic does implement Soldier.
(Actually you could check this by iterating over all the components and using the is operator, but that would be dirty and slow).
Possible but dirty solution 2 :
A second approach is to create a base Component class Soldier from which the Medic and Sniper classes would inherit.
But this also poses several problems.
First, the Unity events (Awake(), Start(), etc) are only going to be called on the leaf classes of the hierachy, forcing you to manually call the same functions on the parent class. Anyone who has tried that knows that it's easy to forget calling something, which results in improperly initialized objects, for example.
And second, the usual problems of inheritance are here too. For exemple, if I want my Medic and Sniper componenents to not only be Soldier, but also be Explodable or VehicleDriver or whatever, I can't, because C# does not support multiple inheritance.
The approach I'm thinking about :
I've thought about a way to design my code so that the issues listed above are solved.
The idea is to have a Component class that acts as the interface and have that interface component coexist with the acutal component on the same GameObject. In other words, let two game objects. One of them would have both a Soldier and a Medic component and the other one would have both a Soldier and a Sniper component. All three component classes, i.e Soldier, Medic and Sniper would be completely separate and all inherit from MonoBehaviour.
The other parts of the code would only interact with the Soldier component. In this case you would be able to do : soldier.GetComponent<Soldier>().Respawn();.
Then, it would be the resposibility of the "interface" component (i.e Soldier) to use the actual component (i.e Medic or Sniper) in order to perform the specific action.
However, since Soldier does not known anything about Medic, Sniper or whatever implementation might be added in the future, the Soldier component exposes an actual interface that the Medic and Soldier have to implement.
Since it is possible to implement multiple interfaces, using this solution, it would be possible to use more than one "interface" component. For exemple, a soldier game object could have the following "interface" components : Soldier and Explodable, and the following "actual" componenent : Medic which would implement both interfaces Soldier.ISolder and Explodable.IExplodable.
What do you think about this solution ? Thx !
EDIT :
I coded what I had in mind and it seems to work nicely. I've also created an editor script allowing to have the "interface" component reference the "actual" component without having public fields, but properties instead. I'll post the code, just in case someone wants it :
WaterComponent.cs - The "interface" component for water objects :
using System;
using UnityEngine;
public class WaterComponent : MonoBehaviour
{
#region Interface
public interface IWater
{
bool IsPointSubmerged(Vector3 worldPoint);
Vector3 GetNormalAtPoint(Vector3 worldPoint);
}
#endregion Interface
#region Properties
public IWater Water
{
get
{
return waterImplementation;
}
set
{
Component asComponent = value as Component;
if (null != value && null == waterComponent)
{
throw new ArgumentException($"The given {typeof(IWater).Name} is not a {typeof(Component).Name}.");
}
waterComponent = asComponent;
waterImplementation = value;
}
}
#endregion Properties
#region Fields
[SerializeField]
private Component waterComponent;
private IWater waterImplementation;
#endregion Fields
#region Public methods
public bool IsPointSubmerged(Vector3 worldPoint)
{
return waterImplementation.IsPointSubmerged(worldPoint);
}
public Vector3 GetNormalAtPoint(Vector3 worldPoint)
{
return waterImplementation.GetNormalAtPoint(worldPoint);
}
#endregion Public methods
#region Unity events
private void Awake()
{
waterImplementation = waterComponent as IWater;
}
#endregion Unity events
}
RealWater.cs - The "actual" component implementing the "interface" component :
using UnityEngine;
public class RealWater : MonoBehaviour, WaterComponent.IWater
{
#region WaterComponent.IWater implementation
public bool IsPointSubmerged(Vector3 worldPoint)
{
return SpecificIsPointSubmerged(worldPoint);
}
public Vector3 GetNormalAtPoint(Vector3 worldPoint)
{
return SpecificGetWaterAtPoint(worldPoint);
}
#endregion WaterComponent.IWater implementation
#region Non-public methods
private bool SpecificIsPointSubmerged(Vector3 worldPoint)
{
return true;
}
private Vector3 SpecificGetWaterAtPoint(Vector3 worldPoint)
{
return transform.up;
}
#endregion Non-public methods
}
WaterComponentEditor.cs - The custom editor allowing not to have naked fields exposed :
using UnityEditor;
[CustomEditor(typeof(WaterComponent))]
[CanEditMultipleObjects]
public class WaterComponentEditor : Editor
{
#region Serialized properties
private SerializedProperty waterProperty;
#endregion Serialized properties
#region Overridden methods
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(waterProperty);
if (EditorGUI.EndChangeCheck())
{
((WaterComponent) target).Water = waterProperty.exposedReferenceValue as WaterComponent.IWater;
}
serializedObject.ApplyModifiedProperties();
}
#endregion Overridden methods
#region Unity events
private void OnEnable()
{
waterProperty = serializedObject.FindProperty("waterComponent");
}
#endregion Unity events
}
Feel free to reuse, unless you see a flaw with this, in which case I'd would really like to know about it !!
EDIT : Well the problem with that custom editor is that you can have the "interface" component reference any Component even if the latter does not implement the real interface exposed by the "interface" component. It is still possible to do some run time checks in the custom editor script, but that's not so clean. However I think the advantages remain good enough in comparison to that issue.
Well...
GetComponent family of functions now supports interfaces as generic argument.
Unity 5.0 release notes : https://unity3d.com/fr/unity/whats-new/unity-5.0
Whatever...

NullReferenceException in Unity (C#)

I'm trying to add a Quest-object to a Person. It succeeds for one and gives a nullreferenceexception for the other, what am I doing wrong here?
P.S. The player and requestor are set in the Unity inspector.
public class GameCreator : MonoBehaviour {
private Quest quest;
public Player player;
public Requestor requestor;
void Start() {
quest = createQuest();
requestor.thisPerson.SetQuest(quest); //this is the problem
player.thisPerson.SetQuest(quest);
}
}
public class Player : MonoBehaviour {
public Person thisPerson;
void Start() {
thisPerson = new Person("Name");
}
}
public class Requestor: MonoBehaviour {
public Person thisPerson;
void Start() {
thisPerson = new Person("Name");
}
}
public class Person {
public Quest quest;
void SetQuest(Quest quest) {
this.quest = quest;
}
}
Any suggestions why this is going wrong?
Move your variable initialization in to Awake(), see the documentation for the following (paraphrased):
Awake is used to initialize any variables or game state before the
game starts.... and use Start to pass any information back and forth.
The way your GameCreator.Start() is written you are reliant on the arbitrary order in which Unity calls your scripts. GameCreator could be the first object called, in which case none of your other scripts have initialized their values.
Other possible errors:
You don't explicitly instantiate requestor, I'm going to assume this was done in Unity's Inspector.
You didn't include `createQuest()' which could be returning null.
As Jordak said, your Start methods can run in any possible order, so you can't rely on Start of some component in the other. You have several ways to address this issue:
You can move the basic initialization code to Awake(). However, this only allows you two levels of initialization, and can be insufficient in the future.
You can adjust script priority in the project settings. However, this is not really C# way, as this makes your code rely on logic that is not obvious from it.
Instead of initializing thisPerson field in the class initialization, create a public property to access it. (Public fields are bad practice in C# anyway). In this property, you can check if the field is null before returning it, and if it is, initialize it.

Categories