Search Unity

MonoBehavior and Inheritance Headaches

Discussion in 'Scripting' started by Antistone, Feb 27, 2015.

  1. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I'm writing a Unity game that interacts with a web service I'm writing in C#; essentially, the client sends game actions to a central server, which verifies legality and processes their effects.

    Since both the client and the server are written in C#, and both need to understand the basic rules of the game, I thought it would make sense to write a lot of the abstract gameplay logic in separate classes that could then be included both in the Unity game and on the server, and thus avoid duplicate code.

    On the Unity side, I also have a user interface to worry about, so I find myself wanting to write subclasses that inherit from the gameplay classes and add user interface logic on top.

    However, I also find I generally want my scripts to be MonoBehaviors, so that I can edit their fields in the inspector and so that they will get Update() calls from the main game loop.

    Since C# does not support multiple inheritance, I cannot inherit from one of my gameplay classes and also simultaneously inherit from MonoBehavior. And since the base gameplay classes need to run on the server (which is not using Unity), I can't make them inherit from MonoBehavior.

    So far, I have mostly worked around this problem by declaring an interface with the most primitive gameplay functionality possible, adding higher-level gameplay functions as extension methods of this interface, and then implementing that interface separately for the client and server. This is a decent solution, but still involves a nontrivial amount of duplicate code and limits the amount of encapsulation I can enforce.

    I find myself wishing that I could give one of my classes MonoBehavior functionality through an attribute, or possibly an interface, rather than by inheriting from a class. Any chance of that?

    Any other suggestions?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    How would these server side classes receive messages such as 'Update' and the sort? The server isn't receiving these.
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    They don't. It's an asynchronous turn-based game; the server isn't running a real-time simulation.

    For example, imagine an online version of checkers. Both the client and the server need to understand the concept of players taking turns, know how checkers move, when pieces get captured or kinged, etc. But only the client needs to animate the pieces moving across the board; as far as the server is concerned, each move is instantaneous.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    So then why would the code that runs on both server and client need methods for Update and what not?

    Decouple this game logic that is specific to a MonoBehviour, from game rules that must exist on server and client.
     
  5. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    The code that runs on both server and client doesn't need an Update method.

    My problem is that the client version (only) wants to be a MonoBehavior, the server version cannot be, but they have large amounts of overlapping functionality that I would prefer not to duplicate.

    For example, suppose I've got a "Checker" class that represents a single checker piece, and that class has a Move() method. On the server, that method simply changes the coordinates of the piece; in the client version, that method has to do everything the server method does, plus animate the movement so that it looks pretty. The standard way of doing that would be to create a subclass that inherits all of the server functionality but adds in new behavior for animating the movement.

    But the client version can't inherit from the server version and from MonoBehavior, because C# doesn't support multiple inheritance.

    I could create a MonoBehavior that contains a Checker, of course; but I don't just have one class, I have a whole bunch of related gameplay classes that call each other, many of which want to have a UI representation (on the client side only), and I don't want to redo all the connections between them, either.
     
    Fyko-chan likes this.
  6. OldUnityFool

    OldUnityFool

    Joined:
    Feb 27, 2015
    Posts:
    25
    I´m not shure, whether it is usefull in this case. But you can try to use an Interface class. So your client can inherit from MonoBehavior and the interface class and the server can although inherit from your interface class.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    1) interfaces are classes

    2) OP already said they're using interface



    @OP - as I said, decouple these game rules from the MonoBeahaviours.

    This code that is on the server must not be code that actually moves GameObjects around or directly effects anything in the scene (since it'd be code that runs on the server, and the server can't do this period).

    So thusly it must be game rule code. Stuff like "is this action valid", "update player health", that sort of thing.

    Well, don't put that kind of code in a MonoBehaviour. It has no reason to be in a MonoBehaviour.

    Your MonoBehaviour scripts will then just call methods on the objects that has this code is in. It just needs a reference to it (static class, singleton, the script creates its own object for it, the script receives a reference to it from somewhere, whatever...)
     
  8. OldUnityFool

    OldUnityFool

    Joined:
    Feb 27, 2015
    Posts:
    25
    Sorry, I don´t read that carefully!
     
  9. cmcpasserby

    cmcpasserby

    Joined:
    Jul 18, 2014
    Posts:
    315
    So the code that runs on both the server and the client, why can't it just be a rules object that get referenced by your monobehaviour in the client. If you wanted to display the fields of the rules object in the inspector you cold also just mark it as Serializable and make a property drawer for it in unity.
     
  10. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Apparently I'm not explaining this very clearly.

    Imagine that I have a whole bunch of interrelated classes that process gameplay functionality. Say I've got a Wizard class, and when you order your wizard to cast a spell, it talks to the Battlefield class and tells it to instantiate a new Storm object at a certain location, and then the Storm asks the Battlefield for a list of all Animals within a radius and tells all those Animals to hide or something.

    And I've got all of that code already written and working on the server side.

    On the client side, when you cast a spell, the same set of events get set off, except that each of those events also have to be displayed to the user. So the Wizard needs to play a casting animation before actually instantiating the Storm, and then the Storm needs to appear at the appropriate location on the screen and so forth.

    I could create a new UnityWizard class that contains a Wizard class, and when the user chooses to cast the spell, I could send a message to the UnityWizard, which could play the casting animation, and also pass along a message to the Wizard so that it can deduct the mana cost and instantiate the Storm and so forth.

    But the Wizard class doesn't know how to talk to a UnityStorm object, only a regular Storm. And that Storm isn't going to send messages to the UnityAnimals, because it only knows about Animals. So how am I going to make sure that the UnityStorm and the UnityAnimals know what they're supposed to be doing?

    I could make it so that the UnityWizard talks to the UnityStorm directly (bypassing the Wizard), and the UnityStorm talks to UnityAnimals, but now I'm creating a parallel execution path for every interaction in my game, which leads to a bunch of duplicate code. Plus, I need to somehow make sure that the command that the UnityWizard sends to the UnityStorm is synchronized with the command that the Wizard sends to the Storm, which probably isn't practical (since the Wizard and Storm are designed to do everything instantaneously, while the UnityWizard and UnityStorm are designed to have delays for animations), so I've got to prevent the Wizard from talking to the Storm directly at all (but have the UnityWizard tell the UnityStorm everything the Wizard would have told the Storm). Pretty soon the Wizard isn't doing anything and I've re-implemented all the gameplay from scratch.

    My preference would be to have UnityStorm be a subclass of Storm, so that the Wizard and the Animals and so forth can all talk to it using the usual interface, without even knowing that it's actually a UnityStorm (the UnityBattlefield will instantiate a UnityStorm but cast the handle to Storm before handing it back to the Wizard), but the UnityStorm will override the Storm's methods to do the additional things that it has to do only on the client side. If I factor my code appropriately, the subclass can probably defer to the base class's implementation for all the common functionality and I get almost zero duplication.

    Except that UnityStorm can't be a subclass of Storm and also a subclass of MonoBehavior.
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    The Wizard class shouldn't send a message to UnityWizard. It should just dispatch an event that can be listened for... no matter WHO listens for it.
     
    Kiwasi likes this.
  12. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I never proposed that the Wizard class would send a message to UnityWizard. In this example, the Wizard class sends a message to the Battlefield class telling it to create and return a Storm, then send a message to the Storm telling it parameters like how big it is and how long it's going to last.

    I could hypothetically have a public event on the Wizard that the Battlefield listens for, and write code in the Battlefield class so that it knows when that event fires that it has to create a Storm with such-and-such parameters.

    But imagine that the Wizard can cast a large number of different spells with widely varying effects on different objects in the game. Am I going to define dozens of different events on the Wizard class and have dozens of different game objects listening for various ones? And then dozens more events on the Geomancer class that other game objects all over the world have to listen for? That seems like poor encapsulation; I don't want my Rock class to have to listen for and implement the Wizard's levitation spell.
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    That's how I read this.

    Sorry... who ever it is you're sending to. You don't need to know who, they should just register for the events.

    Decouple the necessity to know one anothers types directly.

    I can't be more specific if I don't know your design specifically.

    And no, that is not me asking for more specifics about your program... I don't have the time for that. You're thinking about things in a too couple manner... separate your game rules from your gameplay. Keep the game rules in classes that are NOT MonoBehaviour dependent.

    That's it.

    If you don't know how to separate your game rules from your gameplay... well, I can't explain that. That's a very abstract thing dependant on YOUR game.
     
    Dameon_ likes this.
  14. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    In general, Unity uses a component-oriented design instead of a more classic object-oriented design. Component-oriented design avoids deep inheritance and instead breaks things up into separate components. So rather than a UnityWizard that inherits from Wizard and then adds things on top of it, you would instead have a game object with multiple components: at the simplest, this would be "Wizard" and "WizardStuffThatOnlyMattersInUnity". Or better, you would try to break it into several small classes, like "Spellcaster", "ParticleEffectShooter", "BattleMessageSender", "CharacterGUI", etc. Then, when you want to make a Fighter-Wizard, instead of lamenting the lack of being able to inherit from both Fighter and Wizard, you would just mix and match the different components you need without having to write much new code at all.

    I should add that "components" don't necessarily have to be MonoBehaviours; you could also have a UnityWizard class that has a member variable that is a regular Wizard. The idea is still the same; you have a class that "has" a wizard component, rather than a class that "is" a new type of wizard.
     
  15. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    The secret is decoupling. This has been said a few times already. There are multiple ways to do decoupling.

    One is to trigger Events. As you have noted this requires an event for every interaction.

    Another is to use interfaces. Both the UnityStorm and the ServerStorm implement the same IStorm interface. The sending class does not know which one its using.

    Next up is something like the observer pattern. Messages are sent through a central class that distributes them out to listeners as required.

    As a general rule your game logic and rules should always be decoupled from your graphical representation. How to do this is up to you, but I suggest reading up on design patterns.