0
\$\begingroup\$

I'm trying to create a "sims-like" context menu when an item / game object is clicked.

This comes as an add-on question from this question: How to design context menus based on whatever the object is?

Specifically:

Each object of class Item would have a list of components like Equipable, Edible, Sellable, Drinkable, etc. An item can have one or none of each component (for example, a helmet made of chocolate would be both Equipable and Edible, and when it is not a plot-critical quest item also Sellable). The programming logic which is specific to the component is implemented in that component. When the user right-clicks on an item, the components of the item are iterated and context-menu entries are added for each component which exists. When the user selects one of these entries, the component which added that entry processes the option.

You could represent this in your XML-file by having a sub-node for each component. Example:

 <item>
      <name>Chocolate Helmet</name>
      <sprite>helmet-chocolate.png</sprite>
      <description>Protects you from enemies and from starving</description>
      <edible>
          <taste>sweet</taste>
          <calories>2560</calories>
      </edible>
      <equipable>
          <slot>head</slot>
          <def>20</def>
      </equipable>
      <sellable>
          <value>120</value>
      </sellable>
   </item>

When I read this, it reminds me of an interface in programming. My question is, how do you implement this? Perhaps a better question is "is the answer still valid" but it does seem relevant to me.

What have I tried? Nothing at this point, because I'm not sure where to start. Given the example above, I'd guess that Item would have a list of... interfaces? that it could implement? This is why I am a bit confused.

I want to add, this isn't just a UI issue. I found a few radial menus in the store, my issue is how to populate the menu depending on what game object is clicked.

\$\endgroup\$
  • 1
    \$\begingroup\$ This looks like what you get out of the box with the built-in Unity component system. What specific part do you need help implementing? \$\endgroup\$ – DMGregory Jun 18 at 1:00
  • \$\begingroup\$ I may be falling into the trap that newer developers fall into, but I'm trying to understand a generic, D.R.Y., reusable way of doing this rather than creating a menu for every object in the game. Is that clearer? I know it's probably hard to answer as it's incredibly difficult for me to figure out how to ask. \$\endgroup\$ – Jeff Jun 19 at 12:58
  • \$\begingroup\$ What I mean is Unity already has components that you can iterate through with something like GetComponents<IContextMenuExposed>() — did you run into any specific obstacle making this component system work for you? \$\endgroup\$ – DMGregory Jun 19 at 13:01
  • \$\begingroup\$ I didn't know about "Components" tbh. Also, no specifics yet, I think it's just wrapping my head around the basic concept of it. Like, in the above example, if I deserialized that into an object, of type Helmet, Helmet would have to have Eat, Equip, and Sell defined in it, or would it have "edible" in the class somewhere? Then, I'd have to have the verb form of "edible" in a context menu as an action that can be performed. \$\endgroup\$ – Jeff Jun 19 at 13:06
  • 1
    \$\begingroup\$ Just about everything in Unity is a component. The Camera, Lights, Colliders, Renderers, any script you write as a MonoBehaviour, etc. I'd strongly recommend working through a few tutorials with a new engine when getting started to familiarize yourself with its core concepts. That will help you form more focused questions and get answers that are more useful to you. \$\endgroup\$ – DMGregory Jun 19 at 13:15
1
\$\begingroup\$

Yes, using interfaces is one way to do this.

You can use GetComponents (note the additional s) to get all components on a gameObject which implement the same interface or inherit from the same class. So you can create an interface which marks any MonoBehaviour which adds one or more context menu options to the gameObject it is on.

Let's call this interface IContextOptionsProvider and give it a method IEnumerable<ContextOption> GetContextOptions() (assuming ContextOption is a plain old C# class you created to represent a context option). Then, when you create a MonoBehaviour which is supposed to add one or more context menu options to the gameObject it is on, you have it implement that interface and implement that method to return the context options. This could look something like this:

public class Potion : MonoBehaviour, IContextOptionProvider
{  
    public IEnumerable<ContextOption> GetContextOptions() {
        return new ContextMenuOption[] {
            this.Drink,
            this.Sniff,
            this.PourOut
        }
    }    
    /* definition of Drink, Sniff and PourOut */
}

Now if you want to get all the context options of all the components of a GameObject which implement IContextOptionsProvider, you can do it like this:

    foreach(IContextOptionProvider provider in gameObject.GetComponents<IContextOptionProvider>()) {
        foreach(ContextOption contextOption in provider.GetContextOptions()) {
            // do what needs to be done with each contextOption
        }
    }
\$\endgroup\$
  • \$\begingroup\$ Okay, this makes some sense to me. And with this, I could make Potion another interface and have "HealthPotion" and "ManaPotion" with specific implementations of Drink, Sniff, etc. \$\endgroup\$ – Jeff Jun 19 at 13:03
  • 1
    \$\begingroup\$ @Jeff That's one option. Just try to not create different classes which have the exact same logic but just differ by their values. For example, if you have "Small Health Potion", "Medium Health Potion" and "Large Health Potion", those should all have the component "HealthRestoreConsumable" with different HP values. \$\endgroup\$ – Philipp Jun 19 at 13:12

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.