Reputation: 35
imagine the following classes:
public abstract class State {}
public abstract class View : MonoBehaviour {
public State state;
}
Now I want to derive from both classes to create StateA
and ItemA
.
public class StateA { // some properties }
public class ViewA : View {
public StateA state;
}
ViewA
should only accept StateA
as its state. Setting a hypothetical StateB
should not be possible. When I am trying to set the state of ViewA
View view = someGameObject.GetComponent<View>;
view.state = new StateA ();
only the state of the base class will be set. Casting to ViewA
is not really possible in my situation because it would have to be cast dynamically.
Now I came up with two possible solutions.
Solution 1 - Type check and cast
All state classes remain unchanged. The base class is checking for the correct type of the state.
public abstract class View : MonoBehaviour {
public State state;
public Type stateType;
public void SetState (State state) {
if (state.GetType () == this.stateType) {
this.state = state;
}
}
}
To access the state in ViewA
I do this:
(StateA)this.state
Solution 2 - The unity way
State
will be changed to derive from MonoBehaviour.
public abstract class State : MonoBehaviour {}
To add a state to an item I would simply add a new StateA
component to the gameObject. The item then loads that state with GetComponent to access it.
viewA.gameObject.AddComponent<StateA> (); // set state with fixed type
viewA.gameObject.AddComponent (type); // set state dynamically
this.gameObject.GetComponent<StateA> (); // get state in item class
Conclusion
Both of these solutions are not ideal in my opinion. Do you guys know of a better way to do this kind of stuff? I hope you understand what I'm trying to achieve. Looking forward to see your ideas.
Edit
Seems I oversimplified my question. To make clear why I do it that way think of Item
as View
in a UI. The StateA
does not have any functionality. Instead it's just a number of properties to let the view know what it should do. For a table view for example the state would contain a collection of things to show.
public class StateA : State {
SomeObject[] tableViewData; // which data to load into the table view
}
Or for another view.
public class StateB : State {
bool hideButtonXY; //should I hide that button?
}
In another class I keep a history of the last showed views with their respective states. This way I am implementing a back and forth feature from a normal web browser. I hope this makes my intentions clear.
Upvotes: 2
Views: 2090
Reputation: 317
This is a fairly common problem caused by a combination of the odd way MonoBehaviours need to be used in Unity and a common misapplication of inheritance. It's not something you can solve as such, it's something you design in order to avoid.
This all depends on why you need subclasses of State, but I'm assuming it is something like: base State has some base logic, and derived StateA has some new/modified properties/logic. Assuming this is true:
Example written out in code:
abstract class State {
public int sharedField;
public sealed void DoThing() {
sharedField = 1;
DoThingInternal();
}
protected abstract void DoThingInternal();
}
abstract class Item {
State state;
public abstract void TestMethod();
}
class StateA : State {
public int customField = 10;
protected override void DoThingInternal() {
sharedField = 10;
}
}
class ItemA : Item {
public ItemA() {
//read from somewhere else, pass it in, etc.
state = new StateA();
}
public override void TestMethod() {
state.DoThing();
Console.WriteLine(state.sharedField); //will print 10
}
}
This is a very common pattern, especially in Unity, when you want to use inheritance like this. This pattern works also perfectly well outside of Unity and avoids generics, thus is useful to know in general.
Note: the point of the sealed attribute on the public method is to prevent other people/yourself later from trying to hide it (public new void DoThing() or just public void DoThing()) and then being confused when it doesn't work.
If you were to override it, then because you called it through a base class, it'd call the base version of the function instead of your new method. Having it defined as an abstract/virtual and called by the base class itself gets around this. This does mean that you've effectively defined:
interface IState {
public int sharedField;
public void DoThing();
}
So you can probably see that the same result can be achieved with an interface and paying attention to implementation.
Edited to conform example to question:
abstract class State {
public sealed Button[] GetHiddenButtons() {
GetHiddenButtonsInternal();
}
public sealed object[] GetObjects() {
GetObjectsInternal();
}
protected abstract Button[] GetHiddenButtonsInternal();
protected abstract object[] GetObjects();
}
abstract class Item {
State state;
public abstract void Render();
}
class StateA : State {
public Button[] _hiddenButtons;
public object[] _objects;
public StateA()
{
//get hidden buttons from somewhere/pass them in/create them.
_hiddenButtons = new Button[0];
//get objects from somewhere/pass them in/create them.
_objects = new object[0];
}
protected override Button[] GetHiddenButtons() {
return _hiddenButtons;
}
protected override object[] GetObjects() {
return _objects;
}
}
class ItemA : Item {
public ItemA() {
//read from somewhere else, pass it in, etc.
state = new StateA();
}
public override void Render() {
//get objects
var objects = state.GetObjects(); //returns array from StateA
//get hidden buttons to hide
var hiddenButtons = state.GetHiddenButtons();
}
}
Basically, what you're doing is creating an interface that is generic enough to serve whatever you need your state to do. The pattern doesn't dictate anything about how you need to return them, so your GetHiddenButtons() method could: return a list of IDs, return the objects, contact a GameObject to get the list, return a hardcoded list, etc.
This pattern allows you to do exactly what you want to do, you just have to make sure that the base State is designed generically enough to allow you to do it.
Upvotes: 3