DEV Community

Cover image for An Object Composition System for Reactive Worlds: BOCS
Eric
Eric

Posted on • Edited on

An Object Composition System for Reactive Worlds: BOCS

BOCS: A Behaviour-based Game Architecture for Narrative Worlds

I started building my narrative-driven text RPG Ashborne a few months ago, and I noticed a problem pretty quickly: I needed more than a traditional entity architecture such as the Entity Component System (ECS). I needed a system that would allow characters, objects, and even the world itself, to react and evolve.

So that’s how I ended up building BOCS, the Behaviour-based Object Composition System, a design philosophy I'd love to introduce you to today. It helps you build reactive, reconfigurable game entities that can change mid-game - perfect for RPGs, simulations, or systemic worlds where things evolve dynamically.


The Core Idea

BOCS rethinks the “entity” model through a different lens, inspired by existing systems and philosophies (like ECS).

At its heart, a BOCSObject is composed of Behaviours, and each Behaviour implements one or more Modules (which are interfaces).

4 simple examples for how BOCS is structured

Each Module defines what the Behaviour that implements it can do, like a contract; this is very similar to how interfaces are used in other systems. They are also used by external systems to identify the Behaviour that implements it.

A BOCSObject is simply an abstract class with a list of Behaviours and methods for managing those Behaviours.

    /// <summary>
    /// A base class for all objects in the BOCS system, with support for adding and managing behaviours.
    /// </summary>
    public abstract class BOCSObjectBase
    {
        // ... (other config fields + properties)
        /// <summary>
        /// A dictionary mapping Module types to lists of Behaviours that implement those Modules.
        /// </summary>
        public Dictionary<Type, List<BehaviourBase>> Behaviours { get; private set; } = new Dictionary<Type, List<BehaviourBase>>();

        #region Behaviours
        public void AddBehaviour(BehaviourBase behaviour)
        {
            // ... (adds the given Behaviour to the Behaviours dictionary)
        }

        /// <summary>
        /// Remove all Behaviours in this object that implement the Module T.
        /// </summary>
        /// <typeparam name="T">The target Module, of which all implementations are to be removed in the Behaviours of this object.</typeparam>
        /// <returns></returns>
        public int RemoveAllBehavioursOfType<T>() where T : class
        {
            List<BehaviourBase> targetBehaviours = Behaviours[typeof(T)];

            // ... (removes all instances of the targetBehaviours from Behaviours and returns the number removed)
        }

        /// <typeparam name="T">The Behaviour to check for presence in this object.</typeparam>
        /// <returns>A bool representing whether this object contains the Behaviour specified.</returns>
        public bool HasBehaviourOfType<T>() where T : class
        {
            // ...
        }

        /// <typeparam name="T">The Module T to get all implementations of.</typeparam>
        /// <returns>An IEnumerable that contains all the Behaviours that implement the provided Module T.</returns>
        public IEnumerable<T> GetAllBehavioursOfType<T>() where T : class
        {
            // ...
        }

        #endregion Behaviours
    }
Enter fullscreen mode Exit fullscreen mode

Something super important here is that Behaviours are tracked using a mutable list. This means that Behaviours of an object can be modified at runtime, something that plain old static inheritance cannot provide. Like I said before, a flaming sword could have its OnUseApplyStatusEffectToTargetBehaviour (yes, it is a lengthy name!) that applies a flaming status effect to enemies when it is used removed if the sword is dropped into water.

It’s modular, reactive and dynamic, allowing objects to be reconfigured at runtime - something that many other architectures don't allow for.


Interaction with Systems

The big stuff - the "magic" - comes from how systems interact with Behaviours. Because each Behaviour implements one or more Modules (interfaces), a system can simply grab all Behaviours that implement a certain Module and call the corresponding methods within the Behaviour.

Let's tackle this with an example. Say your RPG has a helmet item that gives the player some extra defense when equipped. The BOCS structure would look something like this:

The BOCS structure of a helmet

Notice how all of the Behaviours are super general and versatile? Instead of having dozens of OnEquipChangePlayerStatBehaviour subclasses that each change a different player stat when equipped, we just have one, with an extra field determining the type of stat to change.

Furthermore, any system or event can call the helmet's EquippableBehaviour's Equip() method, which would:

  • First "equip" the helmet onto the player's body (but no stats or effects occur beyond this, which is handled separately)
  • Then search the helmet's other Behaviours for ones that implement IActOnEquip using its GetAllBehavioursOfType(T) method
  • And finally run the interface methods OnEquip() or OnUnequip of those found Behaviours

Its modular, extensible and flexible, for any situation. Want the helmet to make the player FASTER instead of slower? Simply replace the status effect type in its OnEquipApplyStatusEffectBehaviour! Want to do it in the middle of the game? That's ALREADY supported! Want to create a random mix-and-match, absurdly stupid item that has so many functionalities that its completely unique from everything else in the way it can do EVERYTHING? Just attach every Behaviour onto it.

What's more exciting is that this system doesn't just apply to items - in fact, it can be used for settings, in-game object and NPCs.

  1. NPCs might have a DialogueBehaviour and an InventoryBehaviour to make it a trader that the player can't attack (no HealthBehaviour!)
  2. An object could have an OpenCloseBehaviour, an InventoryBehaviour and a HealthBehaviour to make it a destructible chest
  3. Or replace the InventoryBehaviour with an OnOpenTriggerExplosionBehaviour (that could be applied to anything that can be opened/closed) to make it a booby-trapped chest
  4. A throne room could have a EnterBehaviour, an OnEnterTriggerBattleBehaviour and an OnEnterApplyStatusEffectBehaviour to make it a boss battle throne room that gives the player Weakness upon entering

The possibilities are (almost) endless.


Why not use traditional architectures?

Traditional entity architectures, such as the Entity-component-system are great for performance and flexibility, especially in large-scale simulations of thousands of entities.
But when you’re designing a narrative-driven RPG, especially one that reacts to player emotion and choice, traditional ECS can feel too mechanical and restricting in how it separates data and behaviour.

Let’s break down what some of those limitations look like in practice.


1. Rigid Inheritance Hierarchies in OOP

In traditional OOP (e.g. Enemy : Character : GameObject), behaviours are inherited, not composed.
That means:

  • Adding new behaviour (say, giving an enemy the ability to fly) forces you to extend or rewrite existing classes.
  • The codebase becomes deep, brittle, and hard to reason about.
  • You can’t easily “swap” functionality at runtime.

In large-scale or systemic games, you need objects that can change dynamically e.g., an NPC turning into an ally, or a torch that becomes a weapon, all without breaking the codebase.

BOCS replaces inheritance with behaviour composition — objects are built from interchangeable Behaviours, which themselves implement interfaces for systems to call externally. This is very similar to other design philosophies such as the decorator pattern, or composition over inheritance.

A flaming arrow could lose its fire after being drenched simply by removing its OnUseApplyStatusEffectToTargetBehaviour at runtime.


2. Overly Granular or Opaque Systems

ECS is powerful, but also abstract to the point of inaccessibility.
It’s optimised for performance, not clarity. That makes it great for Unity DOTS or simulation-heavy systems... but poor for expressive design or small team development.

You end up with:

  • “Systems” that are hard to follow without a debugger.
  • Components that can’t easily express intent (“this NPC is cautious”).
  • Designers or writers locked out of the technical loop.

ECS tends to fragment logic so much that emergent storytelling and contextual systems become hard to express and debug.
BOCS borrows ECS’s composition and data separation, but instead wraps it in a behavioural context, so logic lives where it makes sense.


3. Tight Coupling Between Systems

In traditional OOP and even many ECS implementations, systems are highly dependent on one another.
For instance, a combat system might directly reference the player’s inventory or stats. And when one system changes, others break. It’s hard to isolate, test, or expand features independently.

To solve this, BOCS introduces a Behaviour-based Event Bus, a dependency injection system, and uses interfaces heavily so that Behaviours communicate through messages and indirect calls.

This dramatically improves:

  • Modularity
  • Testability
  • Team parallelisation (multiple people can work on different behaviours safely)

4. Poor Support for Systemic Design and Emergence

In modern games and simulations, we want emergence, when simple systems interact in complex ways.
But ECS and OOP tend to discourage that naturally:

  • ECS can isolate logic into distant systems.
  • OOP hides state behind class hierarchies.

Interactivity and reactivity (e.g., an NPC responding to weather, or their mood affecting dialogue) is awkward and requires hardcoded exceptions.

As a solution, BOCS defines a shared world state with conditional evaluators and behaviour subscriptions - Behaviours can listen to any world or local event and react accordingly, without needing bespoke code. So, when it starts raining, a WeatherBehaviour in the current setting would broadcast a OnWeatherChanged event that contains additional info about the new type of weather (rain). All Behaviours implementing the IReactToWeather can then choose to modify themselves; maybe the villager walks home, or the merchant closes shop.

BOCS makes emergence easy and simple.


5. Slow Prototyping and Designer Bottleneck

Even in indie teams or educational contexts, the slowest part of development is iteration, especially for non-programmers.
Writers, designers, and testers often need code-level access to tweak dialogue triggers or object logic.

The key limitation here is that designers can’t iterate fast because game logic is trapped in compiled code.

Because BOCS separates logic into modular behaviours, it’s easy to expose them to external configuration: JSON, Ink, YAML, etc.
This means designers can define how an object behaves without touching the engine core at all - not a single drop of programming needed. But of course, unless a proper interface between a user-friendly layer and the data is created, designers will have to directly add behaviour through the chosen data structure, such as JSON or YAML.


TL;DR Design Benefits

  1. Runtime Flexibility: Behaviours can be added, removed, or swapped mid-game.
  2. Modularity: Almost any object can be composed with the right pool of Behaviours through mix-and-match.
  3. Isolation: Each Behaviour has its own lifecycle, reducing coupling.
  4. Emergent Systems: Interactions arise naturally from independent Behaviours listening to shared events.
  5. Scalability: Perfect for games that evolve with player choice or modular content.

Why It Matters

Games like Ashborne thrive on reactivity, the feeling that the world remembers and responds.

With BOCS, I’ve found a way to make that technically feasible without sacrificing modularity or elegance.

I believe systems like this can redefine how narrative-driven games are built, especially those that need to intertwine world logic with player psychology.


What’s Next

I’m currently using BOCS to power Ashborne’s NPCs, objects and reactive dialogue systems.
In future versions, I plan to open-source a full version of BOCS as an example for devs who want to build reactive narrative systems too. However, because this is more of a design philosophy than a strict architecture, you and any others can adapt it to your specific needs and modify it in any way you want.

Also, if anything in this post sounds interesting, feel free to ask me any questions about it. Would you use this system in your own future projects? Why? And is there any other way I could improve it?
I’ll also be sharing progress as I refine the model and test it across multiple genres (one of which will be simulations, something very interesting as ECS already dominates here).


Closing Thoughts

Building BOCS taught me that architecture is storytelling.
Just like Shakespeare (who inspired Ashborne!) built worlds out of language, we, as developers, are to build them out of logic and structure.

If you’re building a game that needs to feel alive, maybe it’s time to look beyond data and think in terms of behaviour.

Thanks for reading! This is my first post on dev.to, please give me any feedback on the actual content, formatting and style.

I'd also like to emphasise this: BOCS isn’t a replacement for ECS. It’s a reinterpretation. Where ECS optimises for performance, BOCS optimises for expression and reactivity. It’s not new in conceptual ancestry, but unique in design philosophy, so there are situations in which BOCS is not the way to go.

Top comments (2)

Collapse
 
bancheng_ni_c21ce7982760c profile image
Bancheng Ni

Very insightful, friendly & clear writeup. I love how BOCS focuses on behaviour & in-game reactivity to user input instead of inheritance, which often creates unnecessary methods and attributes. The runtime flexibility especially sounds perfect for RPGs like Ashborne (which is great btw). Can’t wait to see how BOCS evolves or integrates with other engines. Keep it up!

Collapse
 
halfcomplete profile image
Eric

Thanks a lot for the feedback! I plan to extend BOCS beyond just narrative systems with further improvements, for example into the simulation sector. However, it will be challenging to find a part of simulation where BOCS is better suited than other architectures such as ECS.