Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Discussion Creating passive items that assign effects and subscribe to events

Discussion in 'Scripting' started by PeapodGod, Aug 23, 2023.

  1. PeapodGod

    PeapodGod

    Joined:
    Sep 26, 2020
    Posts:
    4
    Hello! I'm currently working on a modular system for passive items, the likes of which you may see in roguelike games like The Binding of Isaac (mostly just for the sake of experimentation and the fun of it). The way I've thought about the issue so far is as follows:

    1. The Item class derives from ScriptableObject.
    2. Each Item has a reference to an abstract MonoBehaviour Effect class, which it adds to the player as a component when they acquire said Item.
    3. The Effect then subscribes to any relevant events in order to carry out their functionality at the appropriate time.

    For example, let's say we have an Item that gives the player a speed boost whenever they take damage. Under this model, the Effect would subscribe to the player's DamageReceived event, then hook up to their movement component to apply the boost.

    Where I feel this approach falters is that I have no way to preserve serialized values when adding an Effect as a component since I'd just be creating it from a type. For instance, maybe I want this SpeedBoost Effect to be reusable, so I serialize the intensity and duration of it as floats. When I add this Effect to the player, I can't configure these values. I suppose I could save the script with its serialized values as a prefab, add it as a child GameObject of the player, and then have it reference its parent, but then I can't enforce dependencies with RequireComponent (the movement component comes to mind).

    I understand that this approach is flawed, but I'm struggling with what direction to take. Any insight or nudges in the right direction would be greatly appreciated!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    As long as you keep straight in your mind the difference between authored values and overridden values and transiently-boosted values in your game, it doesn't really matter how you implement it. For instance:

    - a base sword does 1d6 damage. This would be specified in a ScriptableObject
    - if you enchant it with a scroll, it does 1d6+1 damage (the base sword remains unchanged)
    - if you quaff a potion of strength, now the total is 1d6+2

    The enchant would be stored perhaps as a buffing attached to the base sword.

    The strength effect would be stored as a transient boost to the player.

    Beyond that, all the normal Load/Save steps just work (see below).

    Here's more on ScriptableObject usage in RPGs:

    https://forum.unity.com/threads/scr...tiple-units-in-your-team.925409/#post-6055289

    https://forum.unity.com/threads/cre...ssigned-in-the-inspector.946240/#post-6174205

    Usage as a shared common data container:



    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    Loading/Saving ScriptableObjects by a proxy identifier such as name:

    https://forum.unity.com/threads/use...lds-in-editor-and-build.1327059/#post-8394573

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
  3. PeapodGod

    PeapodGod

    Joined:
    Sep 26, 2020
    Posts:
    4
    This is great! I definitely want to tackle modifiers as part of this system, so I really appreciate the insight here. One lingering question I have pertains to this bit:

    "The enchant would be stored perhaps as a buffing attached to the base sword."

    The phrasing here suggests, to me, a MonoBehaviour component. With that said, since creating effect scripts that listen for events at runtime is important to me, I'd like to make sure I'm not misconstruing what you meant here. Would you say my thought on adding these "on [x] event" effect scripts to the player and having the responsible ScriptableObject initialize them is advisable?
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    No!!

    The only purpose of making anything a MonoBehaviour is to interoperate directly with Unity.
     
  5. PeapodGod

    PeapodGod

    Joined:
    Sep 26, 2020
    Posts:
    4
    That makes sense to me, though I'd like to emphasize that the event-driven behavior is my primary concern here. Put otherwise, I'm most interested in interactions like "create an explosion upon taking damage" and "receive a damage boost upon picking up a collectible". If not via components on the player, where, in your opinion, would be a good place to handle these, since A) there could be many of them, B) they could apply to different players individually, and C) the subscriptions should take place only when the item is acquired?
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    I have no idea what that means.

    If you have a sword with a value in it... don't read that value directly.

    Instead, make a sword composite object that combines all the attached parts: the base sword, a stack of modifiers, etc. and use that whenever you need to know "how much damage?"

    and then if you have a combat handler, it should know that participant X has a bunch of additional buffs that may be attached to any attack done by that participant.

    Don't overcomplicate this... lay out some basic framework and get moving. As you go and discover you need to refactor, do so and move on.

    Like this:

    Imphenzia: How Did I Learn To Make Games:

     
  7. PeapodGod

    PeapodGod

    Joined:
    Sep 26, 2020
    Posts:
    4
    I think we may have simply had a communication breakdown. In any case, you've helped me reframe the issue at hand. Thanks for your help!