• Runtimes
  • Question about merging constraint lists between two rigs

Hello,

Our studio, All Out Games, has something of a unique use-case. There's gonna be a lot of backstory for context so please bear with me 🙂 Some of the team is working on a 2D, multiplayer-first, custom engine; and we have other sub-teams making games using that engine. One of our games is shown here:

The engine is specifically made to be able to produce these kinds of games quickly, so there is specific support for this player character built in to the engine and the game teams can do whatever they want with this common base.

For each new game the animators will clone the base Engine Rig and then add whatever animations and such that the game is calling for, for various abilities and effects, this produces the Game Rig. On our website we sell cosmetics and unique emotes. The goal is that when we release a new emote or cosmetic we don't want to have to do through our back-catalog of games and add that emote/cosmetic to all the previous Game Rigs.

For this I have implemented a rig merging algorithm that reads the JSONs of the Engine Rig and the Game Rig and merges the Engine Rig into the Game Rig. Now when we want to release a new emote or cosmetic the animators only have to add it to the Engine Rig and then it will automatically get added to all the Game Rigs. Basically how it works is we traverse the ASTs produced by reading the JSON files and appending anything that exists in only the Engine Rig into the AST of the Game Rig. This AST is then reserialized to JSON and that's what we give the Spine runtime to use. There are many concerns here to do with attempting to preserve draw order so when merging Slots it tries its best to put it in the right spot, as opposed to naively appending it to the end.

At this point most things work perfectly and I'm quite happy with it. However, there are some things about Constraints that I'm not totally sure on. First off, constraint order matters. So I can't just naively append to the end when merging. I could do the same "smart" insertion that the Slot merging does where it tries to put it in the right index, but constraints also have this "order" field on them and I'm not sure exactly how that affects things. I'm looking for some guidance on how I could merge constraints properly, taking into account whatever this "order" field is meant to encode. I know this is quite an esoteric (😉) use-case, but hopefully somebody will be able to help me out here. I'd be more than happy to hop in a call and go over this in more detail.

Thank you,
Joshua

Related Discussions
...

Hello Joshua,

Your use-case for merging rigs in Spine is indeed quite unique and interesting. When it comes to merging constraints between two rigs, understanding the "order" field is crucial. In Spine, the order of constraints is significant because it determines the sequence in which constraints are applied. This can affect the final pose of the skeleton, as constraints can influence each other based on their order.

To merge constraints properly, you should consider the following:

  1. Constraint Order: The "order" field specifies the order in which constraints are applied. When merging, you should ensure that the constraints from both the Engine Rig and the Game Rig are ordered correctly. This might involve reordering constraints after merging to maintain the intended behavior.

  2. Smart Insertion: Similar to your approach with slots, you can attempt a "smart" insertion for constraints. This involves analyzing the dependencies between constraints and inserting them in a way that respects these dependencies. For example, if a constraint in the Engine Rig depends on a constraint in the Game Rig, it should be inserted after the Game Rig's constraint.

  3. Testing and Validation: After merging, it's essential to test the resulting rig to ensure that the constraints behave as expected. This might involve running animations and checking for any unexpected behavior.

By carefully considering the order and dependencies of constraints, you can achieve a successful merge that maintains the intended functionality of both rigs.

My question is more "how does the 'order' field fit into things?" I can do the "smart" insertion just fine. But should I base this insertion on the constraint's order in the constraint list or based on it's "order" value? Is the "order" value different per-animation? I talked with our animators and they said that constraint ordering is not something that they are able to animate manually, so where is the "order" field coming from? Does it always map to the order of that constraint in the rig's constraint list? Or is it a per-animation re-ordering based on what constraints are used in that animation or something? I think I need to talk to one of the implementers of a Spine runtime (we're using spine-cpp but this should be agnostic), or otherwise somebody super familiar with the runtime and JSON spec.

Okay I think I have figured out the purpose of the "order" field. In the Spine editor we have all these constraints, group array-of-struct style where IK and transform constraints are intermixed.

In the JSON however they are packed struct-of-arrays style where all the IK constraints are listed, then all the transform constraints. So we need some way to encoded the order of IK constraints with respect to transform constraints, and vice versa in order to construct the AOS list at runtime. The "order" field is the constraints position in that AOS list. Okay I think this is enough for me to get to work with merging. I will update when I get it working 🙂

5 дней спустя

Yup that was the ticket! What I do is read all the constraints into a single array using that "order" field to determine the constraint's index in that array (the order of this array should exactly reflect the order shown in the Spine editor), do that for both the Game and Engine rigs, then for each constraint in the Engine rig that isn't in the Game rig (remember we are merging the Engine rig into the Game rig, not the other way around), look for a "nearby" constraint that is in the Game rig and insert it near that one. For example:

// game rig constraints
A
B
C

// engine rig constraints
A
B
D
C

The algorithm will see that D is not present in the Game rig and so it will try to see if the constraint above it (B) or the constraint below it (C) is present in the Game rig. Right now I think it will prefer B in the case of a tie but I'm not sure that matters too much (we'll see in the future I guess). So it sees that B is in the Game rig so it inserts D after B in the final list, so you end up with

// merged rig constraints
A
B
D
C

If it didn't find B in the game rig then it would find C and insert before C (since it was before C in the Engine rig).

If it didn't find B or C in the Game rig then it just continues climbing in both directions until there is a hit. This should preserve relative ordering during merging.

This solved the constraint issues we were having, and I just had to apply the same logic when merging slots. Same basic principles but this was a bit more tricky since you also have to touch up offsets in drawOrder timelines.