R-Object

Intent

Provide a clean, robust, mechanism for controlling the relationship between two objects.

Motivation

Connection style relationships between pairs of objects are common in games: A player flying a plane, an enemy npc equipping a weapon, or even particles generating from a smoke stack, and so on. By introducing a "relationship object" that exists for the lifetime of the relationship you can provide any methods needed for control over the relationship all in one easy to find place. The relationship object in the first example above might expose an interface for controlling the plane's physical inputs: ailerons, etc. in a simplified manner: perhaps, for instance, just a: SetDesiredRoll()

Benefits

Helps to reduce complexity of object code by keeping relationship management out of the main object hierarchies. Specifically it provides:
  • Clear separation between each object's encapsulated interface and the interface that *only* exists when the two objects are connected to each other;
  • Clear separation between different kinds of relationships between the same objects;
  • Clear separation between different kinds of relationships for different kinds of objects.
This pattern becomes particularly useful in multiplayer games. The creation and destruction messages for the object provide clear communication to all players about the existence and nature of the relationship. Member methods provide good event style notifications; member variables provide well isolated data replication blocks.

Related Work

  • Facade: Overlaps this pattern somewhat in that they both can provide a simplified interface -- facade implies a permanent, architectural, relationship; whereas the R-Object provides an explicitly temporary interface that changes based on the particular objects involved.
  • Adapter: Mechanism that takes user input ( via a game controller, mouse, keyboard, etc. ) and maps it onto the capabilities of the relationship object's interface.
  • State: Lifts control over the relationship into classes based on behavior; helps sustain the relationship object by keeping the control over the relationship object itself out of the two classes involved in the relationship to a third-party.

Liabilities

You pay a tax in the form of additional classes and more distributed ownership of responsibility. Its also worth noting that the relationship object itself requires its own memory allocations. The additional class complexity ( again: in exchanged for reduced interface complexity ) may not be necessary in some cases, especially where:
  • the control mechanism remains invariant through the course of the development cycle
  • the r-object only handles concrete or "leaf" classes of the objects involved in the relationship
  • one of the objects in the relationship only exists as long as the relationship exists
  • the interface for the relationship is small and easily understood.
For instance: the particle effect relation described above can work with this pattern, but the additional class may not be worth the work. Particle effects are typically either attached to something or not ( so: their relation is fairly invariant during development ). Additionally the relationship control between the two objects is generally fairly simple: perhaps just its creation ( attach ) and its destruction ( detach ). In this case its probably clearest to simply put attach / detach ( and update ) control directly into the particle effect itself.

Alternatives

In most cases, since games tend to deal in exceedingly concrete terms, its possible to think of one primary object as controlling the other, secondary, object. The primary objects in the above examples would be: the player, the npc, and the smoke stack; the secondary objects: the plane, the weapon, the smoke. In my own experience I've seen both primary and secondary objects take over the interface for the relationship. Generally speaking, if there's any amount of polymorphism in the secondary object class, then the relationship will reside there. If, the secondary object class tends to be of uniform type, with little if any derived behavior, then the relationship may live in the primary object. Weapons, for instance, might have a lot of behavior differences, but still can posses a relatively standard set of inputs (ex. begin/end firing ) and so may get control over the relationship built into their class. Player characters, however, may have several different appearances, but not a wide variety of control mechanisms, and so the control over the player character itself may be built directly into the primary: player or actor class. Reuse pressures may also help decide where the code for a particular relationship gets placed. For instance: if npcs and player are able to use the same weapon interfaces, then the code for the relationship may live primarily in the weapon; if not the code for managing the player-weapon control may move into the player class, and npc-weapon control in to a separate npc class.

Known Uses

I implemented a variant of this relationship for a prototype game to good success but haven't ever used it in a production environment. Looking through games with open (mod) source: Half Life, Unreal, Quake ( though quake isnt quite applicable as its written in C ), and yes even MechCommander2, I see secondary-object oriented implementations in almost all cases. I think the primary motivation for that architecture remains: if its relatively trivial to create class types, its straight forward enough to simply bake control and behavior ( and even networking manipulations ) straight into the secondary object. Wherever differentiation between player and npc object behaviors are needed its relatively easy to simply create player ( and npc specific ) weapons. The main expense ultimately being large interfaces on both actors and weapon classes to handle variations in control mechanisms. The closest implementation I've found is perhaps the controller classes in UT, which does pull out control over character ( and vehicle? ) into a third party class. It seems primarily specialized to handle different kinds of NPC control over different kinds of actors ( vehicles, turrets, etc. ), though there is also a PlayerController.uc. The differences between their controller class and this pattern is two fold: 1) theirs handles every type of control mechanism in a single class rather than splitting different control types into different classes; 2) theirs also does the job of translating input into action, whereas that would be the role of a separate adapter class with this pattern.

0 comments: