Question

I'm currently in the process of architecting a small RPG-style dungeon crawl (in Unity), and am a little stuck on how to update various objects when things change, while not updating unrelated objects. For this question, I will be using my current project as an example, but the question itself can be applied to any situation where multiple different kinds of Observers each watch a single Subject.

The design I'm currently leaning towards for object interactions in my game implements the Observer Design Pattern, where I have a single Subject (let's call it GameEventManager), and each interactive object is an Observer of this Subject (this includes things like the player, enemies, interactive items on the ground, etc.).

When an event occurs (let's say a User hits Spacebar to shoot an arrow at a selected enemy), the current plan I have for the action's lifetime can be broken into steps:

  1. The Input system (not an Observer, but contains a reference to the Subject), sends an Event to the GameEventManager for broadcasting to the right place. I haven't designed the Event class yet, to keep the problem simpler and in case I need to redesign things.
  2. The GameEventManager broadcasts the Event to the Player with the relevant information.
  3. The Player, realizing an Event came in saying to shoot an arrow, does some internal math (like subtracting from the total ammo it has, maybe performing some sort of animation visually), and emits a new Event to the GameEventManager telling it that the Player shot an arrow at "enemy2".
  4. The GameEventManager broadcasts the Event to "enemy2".
  5. "Enemy2" receives the Event, realizes that it got shot, and dies in some fashion.

So the question is, how does GameEventManager know where to pass around all these different types of events (i.e. the movement event should only go to the player, and the arrow should only go to "enemy2"). Also, once the event gets to its designated target(s), how do these Observers know what to do with the event (i.e. how does the player know to shoot an arrow, and not take damage?).

I've done some research on Observer examples, but most are either obscenely complex and abstract, or are just simple "Subject emits event to ALL observers, and ALL observers interact with EVERY event they get", which is not what I want at all (an arrow shot at "enemy2" should not impact "enemy1" in any way).

Sorry if this is a little abstract, I like to have a plan before I start to actually build the code.

Also, as an aside, I am fully aware that in Unity I can have one GameObject directly interact with another, but the purpose of this entire project is to make everything as polymorphic and re-usable as possible, and most Unity tutorials simply don't touch on extensibility at all.

Était-ce utile?

La solution

To me this is maybe not the best use case for the observer pattern, since it would require the subject to require a boatload of knowledge about who is observing it and when a particular observer should be notified. Just thinking about it and how to separate which observer gets notified under what condition had me working my way towards wanting to put the bulk of the entire game logic into the subject or something pulling its strings and telling it exactly who to notify.

The main idea of the observer pattern is to decouple and abstract away the subject from the observers, so that you can do things like attach whoever/whatever to observe the subject as it goes through state changes so that whoever/whatever can know about it. The subject shouldn't have to know much about who is observing it, and definitely not any concrete information about who is observing it. It's a broadcast mentality like, "To whomever is listening out there, something happened", not, "the player shot an arrow so I should notify this exact enemy within its line of sight about getting hit." That's way TMI for the subject to know about its observers.

Sometimes an observer implementation might distinguish subjects by some abstract concept which doesn't require much knowledge about them, like an observer group: "notify this group of observers." But it's still very aimless. Aimless kind of broadcasting to observers who subscribe should be kind of the thinking pattern when you consider whether or not to use the observer pattern, and what you are tempted to do here is far from aimless (it has the aim of an expertly-shot arrow leading to a headshot -- sorry, lame joke).

Instead I think all you need are really abstractions for concepts like players and enemies (or maybe a "Creature" or something that unifies them -- endless ways to go here). And you might need one or more things to process your game logic. For example, input event for spacebar might trigger player to shoot an arrow doing all things you listed like subtracting ammo count. Then with the arrow projectile created (which might also be abstract as a "projectile" instead of, specifically, "arrow"), you might have something being invoked in the game loop like a physics system moving/processing all projectiles in the game. When it collides with an entity like a "creature" or something like that (something abstract so that you don't have to distinguish like an orc from a goblin), you might call an abstract function that causes it to be hit by the projectile.

This is a rather simplistic example, but something to this effect, yet this is done just by abstracting your game entities rather than using the observer pattern.

Autres conseils

Take a look to the chain of responsibility pattern and maybe you can chain both patterns.

Another possibility is to try a different approach altogether, such as "Functional Reactive Programming" (FRP). It claims to resolve several drawbacks of the observer pattern. Having attempted a javafx program with observers and currently doing a FRP-based application, I pretty much agree. There're lots of open source libraries: Sodium FRP, Reactive Banana, Elm, UniRx for Unity; and lots if information eg https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 or free intro chapters from Manning.

For your purpose you could consider a well known pattern for event programming (Normally for IO purposes), reactor pattern.

The idea is to decouple the application specific code from the reactor implementation.

Under this pattern you will define a set of functions (handlers) that will be called when an event happens. Its a neat and organize way to handle event oriented programming.

I know this is a relatively old question, however, I wanted to provide a more concrete solution for future readers as that is what I interpreted the OP was looking for. This should at least be thought-provoking and/or a "point in the 'right' direction". This pseudo-code solution is based off of the Publish-Subscribe Design Pattern, which sounds like what the OP was describing as the Observer Design Pattern (a common misconception).

/// INFRASTRUCTURE ///

public static class GameEventManager // Made static for brevity / explanation's sake (can actually be constructed in any preferred way)
{
    private static readonly Dictionary<Type, IEventHandler> event_handler_maps = new Dictionary<Type, IEventHandler>();

    public static void subscribe_to_event<TEvent>(IEventHandler event_handler)
    {
        // various guard clauses (null, invalid, duplicate, etc.)
        event_handler_maps.Add(typeof(TEvent), event_handler);
    }

    public static void publish_event<TEvent>(TEvent @event)
        where TEvent : IEvent
    {
        // various guard clauses (null, invalid, no entry in dictionary, etc.)
        event_handler_maps[typeof(TEvent)].your_callback_api(@event);
    }
}

public interface IEvent
{
    //... whatever properties, methods, etc. you may need (could be a marker interface that is cast to the correct type in the event handler ... up to you)
}

public interface IEventHandler
{
    void your_callback_api(IEvent @event); // We pass in the event object as it will contain any data needed to process the event, you could pass data a variety of different ways
}

/// USAGE ///

public class PlayerShotArrowEvent : IEvent
{
    public PlayerShotArrowEvent(Player player, Arrow arrow)
    {
        // ... assign to private fields
    }
}

public class PlayerShotArrowAtEnemyEventHandler : IEventHandler
{
    private Enemy enemy;

    public PlayerShotArrowAtEnemyEventHandler(Enemy enemy)
    {
        // various guard clauses (null, invalid, duplicate, etc.)
        this.enemy = enemy;
    }

    public void your_callback_api(IEvent @event)
    {
        // various guard clauses (null, invalid, duplicate, etc.)
        PlayerShotArrowEvent data = (PlayerShotArrowEvent)@event; // Not the cleanest, but certainly valid if you know that event is always cast-able to PlayerShotArrowEvent OR you can use whatever is in IEvent (depends on your needs)

        // various calculations / logic / check vectors or hit boxes
        if (this.enemy.is_dead())
        {
            GameEventManager.publish_event(new EnemyDiedEvent(...));
        }
        else
        {
            GameEventManager.publish_event(new EnemyAggroEvent(...));
        }
    }
}

public class Player
{
    public void shoot_arrow(Arrow arrow)
    {
        // ... whatever calculations are done here / other logic
        GameEventManager.publish_event(new PlayerShotArrowEvent(this, arrow));
    }
}

public class Enemy
{
    public Enemy()
    {
        GameEventManager.subscribe_to_event<PlayerShotArrowEvent>(new PlayerShotArrowAtEnemyEventHandler(this));
    }
}

You can actually flourish some C# syntax by using lambdas and the Func<...> and/or Action<...> API's if desired or available. I have not ever used Unity so I do not know what is and is not included in the specification / .NET Framework version. I leave that as an exercise to the reader.

You can have the event handlers call methods directly in the actual game objects to do all of the work so that the handlers stay lightweight and re-usable while the actual game objects do all of the heavy lifting. It depends on your architecture and preferences.

Licencié sous: CC-BY-SA avec attribution
scroll top