#Modular Items

1 messages · Page 1 of 1 (latest)

brave edge
olive kelp
#

Interfaces are great for this. One class can implement multiple, and each interface can declare methods and properties that you must implement. Then, simple checks such as if (item is IEquippable) can help you filter out which class implements which interface.
And have a base Item class, that is abstract. All the child classes derive from it, and implements (or not) your interfaces.

#

But the problem is the scalability. You definitely don't want a separate class for each item.
So you could do composition instead. A simple Item class that isn't abstract, and you can (or not) add other scripts aside it on the same object, that would modify the behavior of Item. Then other scripts could fetch the Item, and see if it implements the other scripts.

brave edge
#

Right, so if I end up with 10+ interfaces, I wouldn't want every item to implement all 10 of them, only the ones that it uses. Would I be able to attach these at runtime?

olive kelp
#

No, interfaces are implemented before compiling, you can't modify a type at runtime.

brave edge
#

So how do I avoid making 20+ subclasses to satisfy all interface combinations?

olive kelp
#

You use composition.

#

One general script, Item, and n other scripts you can put aside that represents "the interfaces". These could get the Item instance at start, and "register" themselves to Item

brave edge
#

This makes sense up until "register themselves", I don't think I've ever used interfaces like that before

Is this different from "class Item : IEquippable, ISellable, . . ."?

olive kelp
#

They are not interfaces

#

They are scripts that you attach to your object. Classes that derive from MonoBehaviour

#

By "registering", I meant that the script GetComponent<Item>() in Start for example, and calls a method on Item that tells Item "hey I'm here, you should consider me as sellable"

brave edge
#

Ahhh I gotcha, I misread a few messages up.

This makes sense. The items themselves are not monobehaviours though, just.. data driven classes

Would it work if I stored a list of attached ItemComponents? (That each psuedo-interface-class-thing is a child of, and have a method to see if X component exists in a list, and call on it from there?)

#

so you'd have "class ICEquippable : ItemComponent" and whatever else, and you kinda slap those into the base item class's component list

olive kelp
#

Something like that yep, i'll make an example

brave edge
#

awesome! thank you very much

olive kelp
#

Base class for the "modules"

public abstract class Module : MonoBehaviour {
  public abstract ModuleType Type { get; } // An enum that can uniquely identify a module. Must override.
}

Item class (the one you attach modules to)

public class Item : MonoBehaviour {
  // Relevant properties here like the item's name...

  // The collection that will hold references to the registered modules.
  // Dictionary because lookup is ~fast~ with those.
  public Dictionary<ModuleType, Module> Modules { get; } // Initialized in Awake

  // Other methods...

  // Registers a module
  public void RegisterModule(Module module) {
    Modules.Add(module.Type, module);
  }
}

Example module

[RequireComponent(typeof(Item))] // Important, can't attach a module without an Item on the same object.
public class Sellable : Module {
  public override ModuleType Type => ModuleType.Sellable; // Override enum value

  void Start() {
    GetComponent<Item>().RegisterModule(this); // Register yourself to the Item
  }
}

That should get you started with the structure of it.
Phew, it's been some time since I last typed a textwall like this one

#

Whoops, almost forgot the usage

#

Another script...

Item item = whatever.GetComponent<Item>();
if (item.Modules.TryGetValue(ModuleType.Sellable, out Module mod)) // Could improve this to avoid casting, using a generic method for example.
{
  // It's a sellable item.
  Debug.Log(((Sellable)module).PriceOrWhatever); // Access properties defined in Sellable here.
}
brave edge
#

Alright cool! Thank you

And yeah i've been tinkering with the generic method for getting a module but I'm pretty brain dead XD
using a dictionary sounds good enough for now

olive kelp
#

Something like the following would avoid the casting.

public bool TryGetModule<T>(ModuleType type, out T module) where T : Module
{
  if (dict.TryGetValue(type, out Module mod)) {
    module = (T)mod; // This should work
    return true;
  } else {
    module  = default;
    return false;
  }
}
#

What's left is to try it out!

brave edge
#

oh duh
I was forgetting the "where T : Module" thing so it was getting mad at me for casting 🤣
thanks again!!! this should work great

olive kelp
#

Alright, it's getting late here, so I'll log out for the night. The thread will archive in 24 hours, so we have plenty of time if you have any questions

brave edge
#

alrighty :)

final sun
#

Why not just modules structs/classes and contain them in a list on the Item script?

#

Seems like it'd be easier designer-wise, an editor script that gets all of these and puts them in a new dropdown to add to the list would go well

#

and is it worth using an Enum for ModuleType over just typeof(), since your going to have to modify said enum every time you make a new class that inherits from Module

brave edge
#

something like this?:

public Dictionary<Type, ItemComponent> components;

public bool HasComponent<T>(out T component) where T : ItemComponent
    {
        if (components.TryGetValue(typeof(T), out ItemComponent itemComponent))
        {
            component = (T)itemComponent;
            return true;
        }
        else
        {
            component = null;
            return false;
        }
    }