Question on multiple Rigidbodys, singular movement, and fixed joints

I recently went back to one of my older projects, and it’s a re-make of the raft style games, where you start with one raft, and if you bump into another one it attaches and your raft gets bigger. Along with other added characteristics(speed, damage, etc…).

At first I was just using normal transform manipulation, and childed the new rafts to the given “locations” of snapping, but eventually got all twisted up on making my own physics. Not to mention needing a rigidbody anyway for proper collisions, I eventually scrapped making my own physics and chose to use the physics system.

But soon ran into many issues with the parent and children all having rigidbodys, as it prevented any movement. I’ve been scouring the web for any related situations and best found that FixedJoints were needed. And with small scale testing, this function seem to work:

void OnCollisionEnter(Collision col)
{
    Vector3 direction = col.transform.position - trans.position;
    if (Physics.Raycast(trans.position, direction, out RaycastHit hit, 10.0f))
    {
        if (hit.transform.TryGetComponent<Raft>(out Raft raft))
        {
            team = raft.team;
            raft.myRafts.Add(this);
            trans.position = raft.trans.position + hit.normal;
            FixedJoint fix = gameObject.AddComponent<FixedJoint>();
            fix.connectedBody = raft.rig;
        }
    }
}

Testing seemed to be perfect for my use case, until stress testing showed that certain angles or too many joints were breaking. Also each addition of a new raft, caused all other joints to move therefor re-calculating their connections.

So I added way more checks to prevent any additional hits, if a side was already taken or if the pieces in question were already on the main raft collection. But still bumping into other rafts sometimes caused the FixedJoints to break away in certain areas.

So I came to the conclusion that maybe I don’t want these collected rafts to move at all(from physics) once applied to the main raft, basically leaning me back into maybe childing them to the main raft. This is hoping that the FixedJoints won’t cause any issues with allowing the main to AddForce(or direction to move) since they’re technically jointed to the parent(main) rigidbody.

But then I also thought maybe just declare their transform position each frame, once they joint up, and move them(or keep them placed) while the main moves with physics. But I feel like manually setting their transform positions each frame(even if with a manager of sorts), would be too performance heavy. Or maybe even using a ton of joints themselves is too performance heavy. not sure…

So my question is, is there a better way to do this? Or does someone have an example, or know of a tutorial doing something similar? Or basically am I stuck to just childing the rafts and keeping them jointed? Or just make my own physics system like I had at the start?

Note: My older way would delete the rigidbodys on a raft that gets added/collected, preventing the multiple rigidbody setup. But I think there was an issue with rafts clipping into each other, and I felt that deleting and adding rigidbodys back(if owner died, or pieces broke off from combat) was a horrible way to go about it. Especially with needing their declared mass(heavy/slow raft pieces).

Quite frankly, the approach you put after your “Note” would be my first approach on the topic. From the game as i have it in my head, it seems the most reasonable. But if stuff constantly breaks apart you may want to go another route. Just dont discard the “single rigidbody approach” unless you really need to. Seems by far the most simple to me.

2 Likes

Yep - single Rigidbody is most definitely the way to go here. I don’t even think any other approach is worth considering after superficial consideration.

1 Like

The only nested Rigidbody(2D) support in Unity are Kinematic ones. You should never nest other types. Unless there’s a good reason to have nested bodies, you should avoid them entirely.

Make your decision based upon that.

Yes, Unity will sort the hierarchy and perform write-back from a Rigidbody(2D) to the Transform from root to leaf order to ensure there’s no horrible problems but it’s far, far from something I would say go ahead and do it.

Don’t mix what you logically want with how a system wants to work. You’ll only create problems for yourself.

1 Like

Update: I tried childing the Fixed Joints, which gave horrible results(rigs wouldn’t connect or stay connected if they did). So childing FixedJoints is far worse than just using FixedJoints.

Then I added back the logic of deleting a rigidbody once a new raft “connected” to main raft, then childing it to keep it in local space. Which am now reminded that I no longer can raycast to get the specific raft(hit.normal), to know who to connect to, since each connected(child) collider acts that as the main parent.

The collisions are still happening, but once they do, the returned collider shows as main parent, so the raycast shoots to it’s position, giving improper connections. So until I can figure a way to get a “collision.point” which I don’t think exists, that I can get a normal from, the ease of raycasting is out the window.

So then I’m kinda back to square one, which once a collision happens the Vectors of “newRaft” and “mainRaft” are calculated to equal local space(and snap there). However I’m reminded of the nightmare to not have the pieces snap diagonally.

So it’s dually noted if a parent has a rigidbody, and many child colliders, any collider hit returns that only of the parent. Which I don’t remember this being a thing with non-rigidbody logic, the proper collider hit was always the one returned.

I don’t know, maybe I’m doing something wrong with my testing, but it would be nice to just raycast for the normal, but not have the complications that multiple rigidbodys give(since child rigidbodys give proper collisions.ray.hit.normal). :face_with_spiral_eyes:

Yes I forgot to test “isKinematic” for the children, but just tested it now, and I can’t move in the direction of the snapped child, but can move left(right,up,down) direction if no child is snapped onto the parent there. lol…

I now remember why I made my own physics system. :face_with_spiral_eyes:

I don’t follow at all. Not being able to move is an artifact of what you’re doing surely. I don’t see why the physics system is at any fault here?

1 Like

???

A collider callbacks happens on both the GameObject with the Collider(2D) and the GameObject with the Rigidbody(2D) if it’s on a different GameObject. Always works like that.

1 Like

All I’m doing is AddForce(), which I thought was the proper way to move a rigidbody(as I haven’t tested velocity=).

But if you have 2 cubes, and you move A to hit B, child B to A, and tell B’s rig to isKinematic, you can no longer AddForce(to A) in that same direction, it won’t move. But you can AddForce in either of the other 3 cardinal directions no problem.

I’m only getting the one with the rigidbody returned, are you saying there is a way to get the child with collider? If so, that would fix my issue.

As I’m saying H(single obj) collides with G(child of A), but when I print G(collider) in OnCollisionEnter it prints A(the parent).

And I guess you’ve not told it that it cannot collide with it; a behaviour which you don’t want!

In the end, it’ll only do what you ask (or don’t ask).

Change the “child” to be the same layer that cannot collide with itself. That way, it doesn’t collider and when the Dynamic/Kinematic parent has moved at the end of the simulation step, the children will be written at the new parents position.

There’s only three that stop collisions of Dynamic bodies:

  • Layer Collision Matrix
  • Specific Collider/Collider Ignore Instructions
  • Common Joints on their Rigidbody(2D) where the Joint has Collisions turned off

None of this is new though so I’m not sure why you’re not doing this; you don’t mention it.

1 Like

Interesting, I was under the impression that OnCollisionEnter only happened once, and since the object in question was now childed and didn’t move separately than the parent, that the collision matrix shouldn’t be involved anymore.

So thanks! I have a lot more testing to do, and find at a baser level what is actually happening. So I apologize for any confusion due to my confusion.

Well I’d need to double check that for 3D physics (it may have changed or I might be misremembering) but it’s always been like this for 2D. You can potentially get 4 callbacks if both things in contact have their RB (or no RB) on a different GameObject.

This allows you to target the Collider2D GameObject and/or the Rigidbody2D GameObject (if they’re different).

You’ll always get it at least twice though for both 2D/3D, for each component of the collision/trigger pair.

1 Like

Yes, I’ve found if I keep the (child)rigidbodys, and turn children kinematic, I have to set a global ignore:

Physics.IgnoreLayerCollision(layerPlayer, layerPlayer);

void ChildRigidNonFixed(Collision col)
{
    if (team != 0) { return; }
    if (team == 0)
    {
        Vector3 direction = col.transform.position - trans.position;
        if (Physics.Raycast(trans.position, direction, out RaycastHit hit, 10.0f))
        {
            if (hit.transform.TryGetComponent<Raft>(out Raft raft))
            {
                if (raft.team == 0) { return; }
                HandleConnectInfo(raft);

                trans.position = raft.trans.position + hit.normal;
                trans.SetParent(raft.trans);
                gameObject.layer = raft.gameObject.layer;
                rig.isKinematic = true;
            }
        }
    }
}

And this indeed works, as the direction of the children no longer prevent me from adding force in their relative directions. However I would be limited to only using layers per player/enemy team. So with other layers for needing things, seems to be around a max of 24 players.

However digging further into other ways, I found that collision points do in fact exist, and actually work without the need for raycasting:

void ChildFromHitNormal(Collision col)
    {
        if (team != 0) { return; }
        if (team == 0)
        {
            Collider other = col.contacts[0].otherCollider; // runs 4 times
            if (other.TryGetComponent<Raft>(out Raft raft))
            {
                if (raft.team == 0) { return; }
                HandleConnectInfo(raft);

                trans.position = raft.trans.position + col.contacts[0].normal;
                trans.SetParent(raft.trans);
                Destroy(rig);
            }
        }
    }

So with now having two separate ways of doing it, and with the conclusion of only keeping one rigidbody per parent setup, I think I’ll choose the second snippet as the way to go.

I was previously under the impression that only raycasts could give a “hit.normal”, which the raycast part gave many issues. Not to mention even more processes, for something that could in fact be less overall.

Thanks again everyone for your input, and especially you Melv for putting up with my stubborness. :slight_smile:

1 Like