6
\$\begingroup\$

I'm trying to learn how to use Unity for my class composition, and I was wondering if you could help me.

I'm trying to create a simple game where the player is trying to pop a bunch of balloons. However, any given balloon can only be popped if all of its conditions are met. Example conditions are:

  • The player color must match the balloon color
  • The balloon must be popped in sequence (balloon A must be popped before balloon C)
  • The balloon is only pop-able during a certain time window

And I'm sure you could think of more.

Balloons can have 0 to many conditions associated with them, but they follow the 80/20 principle where 80% will fall into a specific category (e.g. color-match-only, or color-and-sequence-only).

My Initial Solution

I created a "Balloon" class that contains a list of "ICondition" interfaces. When a collision occurs, the balloon loops through each condition to determine whether the balloon is pop-able or not. I populate this list within a "BalloonFactory" class. For 80% scenarios I can just pass in the name of the balloon type I want. For 20% scenarios I can pass a list of conditions. This solution works, except for each new combination I need to change the factory code to include the new condition.

What I'd Like to Do

Instead of storing the balloon definitions in code, I'd prefer to create prefabs in Unity and use the editor to assign condition implementations to each balloon. My problem so far is that the Unity editor doesn't seem to like displaying a list of interfaces or abstract classes.

Is there some way to do what I'm trying to do here? Or is there a third option which you would recommend?

\$\endgroup\$
  • \$\begingroup\$ Seems there are a few common "solutions" to this problem. One, is to adopt one of the libraries that extends some of the Unity Editor functionality to actually display your Interfaces (Or do it yourself). Another I personally have found myself doing to work around this issue, is throw those ICondition interfaces on GameObject prefabs, make your List<ICondition> into List<GameObject>, then do the extra legwork in code to retrieve and manipulate the objects you Actually WANT to use. bit of a PITA but it lets you drag and drop Interface Prefabs for sure, just be careful with your types. \$\endgroup\$ – Joe May 26 '15 at 4:37
3
\$\begingroup\$

Your balloons are gameobjects right? I find that in general, adapting your ideas to how Unity works yields better results than hacking around the limitations. I use this component-based approach to do pretty much everything involving composition, and it's especially handy to change the behavior of objects on the fly or in the editor, without ever touching the code, by just manipulating components.

abstract class PoppingCondition : MonoBehaviour
{
       public abstract bool IsMet();
}

class ColorMatchCondition : PoppingCondition
{
       //Can edit in the editor.
       public Color Color;

       public bool IsMet()
       {
           //Logic goes here. Can use GetComponent<Balloon> to get its owner
           //and compare its color to the one set in the editor.
           //Your condition also has access to the usual Start, Awake, Update etc.
       }
}

class PoppingSequenceCondition : PoppingCondition
{
       //Can edit in the editor.
       public List<Balloon> BallonsThatHaveToBePopped;

       public bool IsMet()
       {
           //logic goes here
       }
}

Then you attach how many conditions you want to some balloon, via Add Component, and edit their properties normally in the editor.

Inside your balloons:

//Balloon.cs
bool CanBePopped()
{
     //True if all conditions are met, otherwise false. Your logic goes here.
     PoppingCondition[] conditions = GetComponents<PoppingCondition>();
     foreach (var condition in conditions)
     {
          if (!condition.IsMet())
          {
               return false;
          }
     }
     return true;
}

Of course, you can store your balloons together with their conditions inside prefabs. Or do pretty much anything else that you're able to do with gameobjects and their components.

Sample editor: enter image description here

\$\endgroup\$
  • \$\begingroup\$ I didn't know GetComponents<>() worked with polymorphism- that's awesome! \$\endgroup\$ – John May 27 '15 at 0:48
3
\$\begingroup\$

Some facts:

  • unity supports serialization of polymorphic types only when derived from MonoBehaviour or ScriptableObject
  • reference to Interfaces won't be took in consideration by the serializer, even if the actual instance is of a serializable type

Now, I often prefer programming against interfaces, so I faced similar problem several times (unfortunately not found a particularly elegant solution).

What I would do:

First of all I'll use a ScriptableObject asset to describe your ICondition. This allow several nice usage that, as far as I understood your use case, would be usefule, such as:

  • despite a MonoBehaviour , they don't need a GameObject instance to be attached to
  • you can serialize reference to them
  • they support polymorphism
  • you can share the same ScriptableObject condition across several classes

    For how to store reference you have 2 possibilities:

Inheritance

Make your concrete Condition classes extend a common base class derived from ScriptableObject.

You can serialize the reference to the required assets. (I'd go this way in your case)

Interfaces

You can implement the ICondition interface from your ScriptableObject derived class, but to show them in the inspector, and allow a proper serialization and display you need to do some work:

  • Somewhere in the referencing class you need a reference to ScriptableObject (or a serializable derived base class reference)
  • You eventually need a custom inspector for checking assigned reference actually implements the required interface

I do something similar in my code, it looks like:

[SerializeField]
[IFace("IMyInterface", typeof(MonoBehaviour), typeof(IMyInterface))]
private MonoBehaviour m_refTo;

The attribute allow me to:

  • writing a custom PropertyDrawer which enforce some costraints (es. when a drag&drop operation is performed it checks if the referenced component actually implements the interface)
  • object is correctly serialized because serializer will take into consideration m_refTo (and supports polymorphic type, so every derived class will be displayed correctly)

You still need to cast m_refTo at some point (either on Start or during a SerializationCallback).

Hope this help

\$\endgroup\$
  • \$\begingroup\$ Another design consideration that I was asked to look into was to find a way to separate my game logic from the Unity-specific code as much as possible (more of an academic exercise for a game this small, but possibly useful for a larger game or unit testing). From what you're saying, it doesn't look like there's a great way to do that and use the Unity editor (although I suppose that is to be expected). \$\endgroup\$ – John May 27 '15 at 0:52
  • \$\begingroup\$ Well, of course gamelogic can be separated from unity specific code. And often is a good practice if you have a subsystem that can be independent from the engine itself. If you want to rely on unity serialization system, what you can do is, just after deserialization, build your own independent classes with data serialized by unity. So you let unity handle the serialization but once deserialized the data, logic classes are independent. Eventually consider to ask a specific question on this topic. \$\endgroup\$ – Heisenbug Jun 15 '15 at 10:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.