9
\$\begingroup\$

I've been reading a lot about design patterns, but in using these patterns there's one question that I keep coming back to. How should my entities access information about each other?

Let's say I'm using a simple pattern where each object updates and draws itself:

class Monster extends GameEntity {
    public void update(double delta) {}
    public void draw(Graphics g) {}
}

I'm writing my update loop, but I realize that for Monster requires knowledge about where the player is, where cities on the main map are, or some other random piece of data.

How does Monster get at this? Do I initialize it with a reference to every possible thing it might need?

class Monster extends GameEntity {
    public Monster(Player player, City[] cityList) {
        this.player = player;
        this.cityList = cityList;
    }
}

That looks pretty messy. Or do I have a main Manager of sorts, which has access to everything, and pass that to everything?

class Monster extends GameEntity {
    public Monster(GiantGlobalManager manager) {
        this.manager = manager;
    }

    public void update(double delta) {
        cities = this.manager.getCities();
    }
}

Or perhaps have the manager as singleton? We could also break these managers into their areas of concern.

class Monster extends GameEntity {
    public void update(double delta) {
        cities = CityManager.getInstance().getCities();
        player = PlayerManager.getInstance().getPlayer();

        guiManager.getInstance().createMenu();
    }
}

But this still doesn't feel quite right. Now I'm having to maintain all these managers, and it seems wrong for my objects to be making all these static calls. Second, many of these objects should not have access to everything provided by these classes. Letting everything access everything feels like a cop-out to me.

Whatever patterns I'm using - whether I'm breaking entities into components, or just moving my drawing and update loops to separate objects - I still need some way for them to communicate. What is a good way to go about this? What am I missing here?

Should this all be done through events? But getting a list of cities is not really an event, it's just data the monster needs.

\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

As you've noticed, there are pros and cons to each architecture scheme. Each pattern has its own implications for how objects will interact, and when.

First, I'd recommend a quick refresher on SOLID design.

Now, what we strive to accomplish with good architecture is enough abstraction that we aren't hampered by the system when we want to add a feature. If we can accomplish that with a couple of singletons or global methods in a way that scales to future development, great! It might even end up faster.

Now, the object oriented approach typically has one or more factories that produce either generic entities you add components to, or specific entities in a non ECS design. These factories are usually contained close to the core gameplay class. As an example of the non-ECS version:

class Monster extends GameEntity {
    // Called by factory
    public Monster(EntityService service) {
        // store a private reference to the service
    }
    public GameEntity Find( /* parameters depend on game's needs */ ) {
        // call methods on service to locate entities given these conditions
    }
    public void Destroy() {
        myService.Remove(this);
    }

}

class MonsterFactory extends GameEntityFactory {
    private EntityService myService;
    public MonsterFactory(EntityService service) { /* ... */ }
    public GameEntity Spawn()
    {
        GameEntity entity = new Monster(myService);
        myService.Add(entity);
        return entity;
    }
}

class Physics {
    private EntityService myService;
    public Physics(EntityService service) {
        /* retain service in order to provide updates to entities */
    }
    public void update(double delta) {
        // make changes to each applicable entity in
        // "myService," which the entity will refine
        // in its own update method
    }
}

The common thread here is that each object is constructed with a reference to a service that provides some core functionality where the specific behavior is abstracted according to how the object will always behave. Need to create a monster? Summon the monster-making-factory from the entity service. Need to find another entity? It's in the entity service. Need to send messages to all entities of a certain type (such as physical entities)? Just filter them out of the entity service. This way you can avoid hanging on to references to "dead" entities.

This is just one example, of course. In practice, your game's needs will dictate how this has to come together.

\$\endgroup\$
2
  • \$\begingroup\$ This helped a lot, thank you. One question though - in your example does service contain a reference to the factory and the factory also have a reference to the service? Isn't this sort of cyclical object hierarchy something to be avoided? \$\endgroup\$
    – Aelast
    Apr 8, 2015 at 21:50
  • \$\begingroup\$ Yes, mostly because any objects that are still referenced in the hierarchy will never get garbage collected, and a tree is easier to hold in your head than a graph. However, it's a necessary evil since we need to have some means of reaching out to look things up. But, since the service "owns" everything it's easy to ensure that there are no lingering references. Objects only need to take care of themselves, and avoid hanging on to each other. \$\endgroup\$
    – jzx
    Apr 9, 2015 at 0:21
3
\$\begingroup\$

A slight variant to jzx's implementation would be to use an update method similar to how you did your draw. It's not uncommon in architectural design to have some context object that often holds a plethora of state information.

 public interface GameEntity {
   void update(GameContext ctx);
   void draw(RenderContext ctx);
 }

Therefore, inside a particular GameEntity implementation class you might see your update method written as such:

 public void update(GameContext ctx) {
   GameObjectManager goManager = ctx.getGameObjectManager();
   if(!hasTarget()) {
     List<GameEntity> entities = goManager.getVisibleEntitiesWithinRadius(position, 30);
     if(!entities.isEmpty()) {
       setTarget(CollectionUtils.selectRandomFromList(entities));
     }
   }
   // do other stuffs
   SomeGameSystem sgSystem = ctx.getSomeGameSystem();
   sgSystem.process(this, ctx);
 }

You could consider inverting this approach where a system acts as both a factory and the place where your update logic resides. These systems are updated in a deterministic order and perform small sets of operations on a large bucket of entities based on criteria. This often yields faster and more cache friendly operations.

Essentially that update logic above could be pushed into a series of systems like this

 public AISystem implements System {

   public AISystem(SpacialQuerySystem spacialQuerySystem) {

   }

   public AIComponent createComponent(GameEntity entity) {
     AIComponent component = new AIComponent();
     component.setOwner(entity);
     components.put(entity, component);
     return component;
   }

   public AIComponent getComponent(GameEntity entity) {
     return components.get(entity);
   }

   @Override
   public void update(double deltaTime) {
     for(Entry<GameEntity, AIComponent> entry : components.entrySet()) {
       /* do whatever with the entity and use the injected systems */
     }
   }

 }

In the end, this approach becomes far easier to maintain long-term as you can often build foundation systems that you reuse in more complex systems that ultimately implement some game need. Additionally, the use of constructor injection explicitly identifies dependencies in your architecture without having to sift through code to determine what needs what.

All that is left is to have some map loader or such that understands how to read your level data and call the various systems to construct your entities. If you use archetypes and archetype factories, perhaps that can be abstracted into those factories that ultimately interact with these said systems.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .