5
\$\begingroup\$

I search a way to have Box2D joints in an entity component system, here is what i found :

1) Having the joints in Box2D/Body component as parameters, we have a joint array with an ID by joint and having in the other body component the same joint ID, like in this example :

Entity1
- Box2D/Body component { Body => (body parameters), Joints => { Joint1 => (joint parameters), others joints... } } // Joint ID = Joint1

Entity2
- Box2D/Body component { Body => (body parameters), Joints => { Joint1 => (joint parameters), others joints... } } // Same joint ID than in Entity1

There are 3 problems with this solution :

The first problem is the implementation of this solution, we must manage the joints ID to create joints and to know between which bodies they are connected.

The second problem is the parameters of joint, where are they got ? on the Entity1 or Entity2 ? If they are the same parameters for the joint, there is no problem but if they are differents ?

The third problem is that we can't limit number of bodies to 2 by joint (which is mandatory), a joint can only link 2 bodies, in this solution, nothing prevents to create more than 2 entities with for each a body component with the same joint ID, in this case, how we know the 2 bodies to joint and what to do with others bodies ?

2) Same solution than the first solution but by having entities ID instead of Joint ID, like in this example :

Entity1
- Box2D/Body component { Body => (body parameters), Joints => { Entity2 => (joint parameters), others joints... } }

Entity2
- Box2D/Body component { Body => (body parameters), Joints => { Entity1 => (joint parameters), others joints... } }

With this solution, we fix the first problem of the first solution but we have always the two others problems.

3) Having a Box2D/Joint component which is inserted in the entities which contains the bodies to joint (we share the same joint component between entities with bodies to joint), like in this example :

Entity1
- Box2D/Body component { Body => (body parameters) }
- Box2D/Joint component { Joint => (Joint parameters) } // Shared, same as in Entity2

Entity2
- Box2D/Body component { Body => (body parameters) }
- Box2D/Joint component { Joint => (joint parameters) } // Shared, same as in Entity1

There are 2 problems with this solution :

The first problem is the same problem than in solution 1 and 2 : We can't limit number of bodies to 2 by joint (which is mandatory), a joint can only link 2 bodies, in this solution, nothing prevents to create more than 2 entities with for each a body component and the shared joint component, in this case, how we know the 2 bodies to joint and what to do with others bodies ?

The second problem is that we can have only one joint by body because entity components system allows to have only one component of same type in an entity.

So we can't put two Joint components in the same entity.

4) Having a Box2D/Joint component which is inserted in the entity which contains the first body component to joint and which has an entity ID parameter (this entity contains the second body to joint), like in this example :

Entity1
- Box2D/Body component { Body => (body parameters) }
- Box2D/Joint component { Entity2 => (Joint parameters) } // Entity2 is the entity ID which contains the other body to joint, the first body being in this entity

Entity2
- Box2D/Body component { Body => (body parameters) }

There are exactly the same problems that in the third solution, the only difference is that we can have two differents joints by entity instead of one (by putting one joint component in an entity and another joint component in another entity, each joint referencing to the other entity).

5) Having a Box2D/Joint component which take in parameter the two entities ID which contains the bodies to joint, this component can be inserted in any entity, like in this example :

Entity1
- Box2D/Body component { Body => (body parameters) }

Entity2
- Box2D/Body component { Body => (body parameters) }

Entity3
- Box2D/Joint component { Joint => (Body1 => Entity1, Body2 => Entity2, others parameters of joint) } // Entity1 is the ID of the entity which have the first body to joint and Entity2 is the ID of the entity which have the second body to joint (This component can be in any entity, that doesn't matter)

With this solution, we fix the problem of the body limitation by joint, we can only have two bodies per joint, which is correct.

And we are not limited by number of joints per body, because we can create an another Box2D/Joint component, referencing to Entity1 and Entity2 and put this component in a new entity.

The problem of this solution is : What happens if we change the Body1 or Body2 parameter of Joint component at runtime ? We need to add code to sync the Body1/Body2 parameters changes with the real joint object.

6) Same as solution 3 but in a better way : Having a Box2D/Joint component Box2D/Joint which is inserted in the entities which contains the bodies to joint, we share the same joint component between these entities BUT the difference is that we create a new entity to link the body component with the joint component, like in this example :

Entity1
- Box2D/Body component { Body => (body parameters) }    // Shared, same as in Entity3

Entity2
- Box2D/Body component { Body => (body parameters) }    // Shared, same as in Entity4

Entity3
- Box2D/Body component { Body => (body parameters) }    // Shared, same as in Entity1
- Box2D/Joint component { Joint => (joint parameters) } // Shared, same as in Entity4

Entity4
- Box2D/Body component { Body => (body parameters) }    // Shared, same as in Entity2
- Box2D/Joint component { Joint => (joint parameters) } // Shared, same as in Entity3

With this solution, we fix the second problem of the solution 3, because we can create an Entity5 which will have the shared body component of Entity1 and an another joint component so we are no longer limited in the joint number per body.

But the first problem of solution 3 remains, because we can't limit the number of entities which have the shared joint component.

To resolve this problem, we can add a way to limit the number of share of a component, so for the Joint component, we limit the number of share to 2, because we can only joint 2 bodies per joint.

This solution would be perfect because there is no need to add code to sync changes like in the solution 5 because we are notified by the entity components system when components / entities are added to/removed from the system.

But there is a conception problem : How to know easily and quickly between which bodies the joint operates ? Because, there is no way to find easily an entity with a component instance.

My question is : Which solution is the best ? Is there any other better solutions ?

Sorry for the long text and my bad english.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Note: This has turned out to be a long answer. If you just want to see the main idea, scroll to the Main Answer heading

I have recently come across almost the same issue. My home-baked framework is more traditional in the sense that components have a few methods, but I think I should still be able to help.

The real issue you seem to be dancing around can be summed up in the question: "Who owns the joint?" My answer to this question was inspired by how CAD programs manage assemblies. (I'm going to use an analogue here so bear with me.)

Analogue with CAD software

In SolidWorks (for example) parts contain a feature tree:

Solidworks part with a feature tree down the left hand side Image courtesy of cadjunkie

There are a variable numbers of features in the tree, some of the same type (the one above has many fillets), and some are dependent on eachother. Generally, though (there are a few ways to get around this) each part (.sldprt file) has only one solid body. This is how the software is intended to be used. The important part here is that the part owns the body and all its features.

But the problem is, in most cases we design parts to interface and work with other parts. And you might be thinking 'Do machine designers just make all these parts and hope they fit together when they are fabricated?' The answer is a resounding 'No way Jose'. Most designers will create an assembly file such as the one below:

SolidWorks assembly with a feature tree on the left Image courtesy of MathWorks

In assembly mode, solidworks displays a feature tree on the left, but it is not the same as the feature tree in part mode. Notice the entries are parts instead of features. Also notice that there is one extra entry at the end of the list: MateGroup1. A mate group just expands to reveal a list of mates, where each mates specifies a constraint of one part relative to another. Lets take a look at another assembly to see what a mate group might look like:

Another assembly with many mates between the parts Image courtesy of CAPUniversity

The feature here shows many different types of mates (concentric mates ensure cylindrical faces are concentric, and coincident, parallel, and distance mates constrain planar faces). The mates between the parts are specified in a completely separate file (the assembly or .sldasm file) for several reasons: (these are just the ones I can come up with)

  1. Part feature trees do not become enormous
  2. Parts can be moved relative to eachother as they would in real life (by leaving some parts partially unconstrained or specifying multiple configurations for the assembly)
  3. Parts can be reused in other assemblies (if parts contained mates and references to other parts then we would have the problem, who owns the mate? What happens if one part is deleted?)
  4. Distinct parts in the model map to physically distinct parts (This may seem obvious, but without assemblies this separation between parts would be hard to make! (if you tried to model an assembly as one part))

You might have guessed it already, but here is what I'm getting at: the idea in SolidWorks is that individual parts do not dictate (directly) how they link to other components, you can only specify the properties of a feature, and that feature may or may not be used to mate to another part in an assembly. IMO this should hold for box2d as well. If you have two bodies you should only indicate where the anchor points are on those two bodies, and its the responsibility of something else to get those two anchor points, along with the bodies, and create the joint.

Main Answer

This is most like solution 5 of your post. Following your format, my solution goes like this:

Entity1
- Box2D/Body component { Body => (body parameters) }
- AnchorPoint component {LocVec}

Entity2
- Box2D/Body component { Body => (body parameters) }
- AnchorPoint component {LocVec}

Entity3
- Box2D/Joint component { Joint => (Body1,AnchorPoint1 => Entity1, 
                                    Body2,AnchorPoint2 => Entity2,
                                    other parameters of joint) }

Where LocVec is the local position of the anchor point relative to the body origin.

You stated one major issue with this approach:

The problem of this solution is : What happens if we change the Body1 or Body2 parameter of Joint component at runtime ? We need to add code to sync the Body1/Body2 parameters changes with the real joint object.

I think you are referring to moving the anchor point or changing the joint type after the joint has been created. Unfortunately, that's a box2d limitation; you can't change either of those things after the joint has been created. In that case you should destroy the joint, edit the relevant components (e.g. change the anchorpoint component) and recreate it in one fell swoop.

Other Notes and Example

Really if your are joining two bodies then they likely have some relationship between eachother. And often it would make sense for them to share some common properties. For example, you might have an enemy pair of scissors in the game, where each blade is an entity, and the ScissorEnemy holds the joint component. The enemy scissors might have a health meter, represented by a Health component. Now that you have a third entity that owns the joint, why not have this entity hold the Health component, too? This makes sense, because the health is not really a property of either scissor blade, or even both of them; it belongs to the scissors as a unit. The system would look like this:

Blade1
- Box2D/Body component { Body => (body parameters) }
- AnchorPoint component {LocVec}
- Parent component (ScissorEnemy) // A parent component just holds the parent Entity's ID
- ... other components

Blade2
- Box2D/Body component { Body => (body parameters) }
- AnchorPoint component {LocVec}
- Parent component (ScissorEnemy)
- ... other components

ScissorEnemy
- Child component (Blade 1) // An child component just holds the child Entity's ID
- Child component (Blade 2)
- Health component
- ... other components
- Box2D/Joint component { Joint => (other parameters of joint) } //There's no need for the joint component to hold onto references to bodies or anchor points; you can get all that info from the joint or accessing the bodies' of the child components.

So here I've introduced a Parent component and an Entity component. The idea is that if one of the blades is involved in a collision or is otherwise damaged, the collision system (or something equivalent) will look for a Health component on the blade. Upon not finding one, The blade (or the system) should look for a parent component, and then check the parent for a health component and subtract health from that. If a scissorEnemy dies it is easy to destroy the scissor enemy, and leave the two blades in play. The health and any other components (maybe a Pathing component) that were only important when the scissorEnemy was alive are destroyed. As a side note, destroying the scissorEnemy does not necessarily destroy the joint, because box2d (and the two bodies) hold onto references to the joint, So you could choose whether or not to destroy the pin.

So far I haven't had any real issues in using 'entity components' like parent component and child component, and I can't see any real issues for an Artemis-like framework. (Though experience has taught me that the devil is in the details, and I have not been through those details yet. So let me know of any roadblocks.)

Assuming you've made good use of recursion, a system like this would have no trouble working with entities who parent entities who parent other entities (as long as there are no circular chains). You could create an entity with a very deep hierarchy to get a lot of sophistication.

I hope this helps!

\$\endgroup\$
7
  • \$\begingroup\$ here is the link to the last image \$\endgroup\$ Sep 5, 2013 at 13:29
  • \$\begingroup\$ Thank you for this very useful answer. I think also that solution 5 is better than the others. But you put an AnchorPoint component in the Entity which has a body component, the problem is that in an entity component system, an entity can have only one component of same class, so if we want a body with more than one joint, we can't because we need to put more than one AnchorPoint component in the entity. I prefer to have the anchor point as a parameter of the joint (Anchor point body A and Anchor point body B). \$\endgroup\$
    – Johnmph
    Sep 5, 2013 at 14:41
  • \$\begingroup\$ And the same problem happens for your child components. \$\endgroup\$
    – Johnmph
    Sep 5, 2013 at 14:41
  • \$\begingroup\$ In my system I solved the multiple joint problem by making it an AnchorPoints component that has a map of anchor points each keyed with a unique name. That being said there are no major issues with storing the anchor points with the joint component, in fact that will be a little more memory efficient. \$\endgroup\$ Sep 5, 2013 at 15:40
  • \$\begingroup\$ However, if you store the anchorpoints on the child entity you could implement a system where the player or enemies in your game can dynamically join themselves to different objects. Imagine an enemy in your game parents a hand entity that has anchor point on the hand. That enemy identifies and wants to join to an un-parented knife entity to the hand. In your system the enemy has to have a joint component that knows where the anchor point on the knife is. In my system, the enemy just has to know it's a weapon, and it will use the knife's anchorpoint(s) component to decide where to joint it. \$\endgroup\$ Sep 5, 2013 at 15:57

You must log in to answer this question.

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