Hello and welcome back to the Blightmare dev blog! I’m in the middle of a series focused on why the game logic was rewritten twice before finally settling on the approach we have today. You can find Part 1 here and Part 2 here. Today I will discuss the net implementation and finish up with a simple hang target.
Continuing the theme of keeping art and logic separate, I started thinking about how to do the logical net in a way that would match the art but not be directly tied to it. The net has its own collision and collider layer – among other things – so it became a distinct object that was just referenced by the player. I built a quick ellipse movement component to approximate the shape that a net swing takes, and moved on to hooking up the input and animation events. I figured that it would be easy enough to come back later to match up the actual movement with the art so that the player could easily identify where the net swing was actually going. In order to make this easier, I setup a debug visualization of the net collider throughout an entire swing and then slowed down the animation a ton to really have a look at how similar the paths were. It turned out that the paths were not really similar at all, because the net animation didn’t really follow a closed form curve, and the “velocity” of the animation was not constant. In addition to this, the path taken by the animation net was different depending on if Blissa was swinging in the air or from the ground. I spent quite a bit of time starting at frame-by-frame video capture of the animation and my logical approximation, tuning it to be closer for some parts, but not quite there in others. Eventually I got tired of it and realized that if we ever needed to change the art, this whole process would have to be repeated. In the end it just came down to a play test. The net didn’t behave like it looked like it should. Another approach was required.
The obvious solution here is to map the position of the net in Blissa’s animation into world space and then do a collision query there for each frame of the swing. I’ve mentioned before that we use Spine as the animation tool and runtime for Blissa, so I set about trying to figure out how to attach a collider to a spine animation. I don’t recall – and I’m too lazy to go back and check – if Spine had something built-in to do this like they do now, but either way, I ended up building something myself. Spine is a skeletal animation toolkit which means the base animation is done with bones. You can then place slots that are bound to a certain local position on a bone. The slot can then be configured to contain an attachment such as a mesh or a sprite. For my purposes then, I built a custom attachment that worked out the world position of the slot and matched any scaling or rotation that might be involved. Adding this component to my net object and setting things up to target the proper slot resulted in pretty accurate tracking of the position, orientation, and size of the net art. But it didn’t Just Work.
The most significant problem that this approach had is that it had gaps in where it tested for collision. This is because the swing animation was so quick that it was just a handful of frames that moved across a large amount of world space. When this was combined with some of the odd scaling that the animation did to achieve the visual effects required, there were pretty obvious holes in collision sampling. To make matters even worse, the holes were not in the same place every swing. Clearly I had to do better. The solution I came up with is just to keep track of the last position that the net was in and essentially sweep the net collider along a line from that previous position to the current one. This didn’t track the trajectory of the net perfectly, but it was a close enough approximation for our purposes.
Clever readers may also have noticed that this way of doing net collision actually breaks the assertion made at the start of the series: logic is done in fixed time steps while animation is variable. This is true. What I did to deal with this problem is to buffer potential net collisions until they could be processed by the fixed step routines. This introduces some delays and can cause some visual artifacts in certain edge cases, but those can be smoothed out later when we come back to polish things.
Using the Net
At long last, the net is usable as a gameplay mechanic and it is finally time to start implementing things that can be caught. The first net target that I picked was a wall hook. This is a static object that Blissa can hang from and then jump off. I called the component that dealt with this logic HangTarget, and the basic idea is that it watched for any collisions by things that had the Hangable component (I didn’t try very hard on naming unfortunately). At this time in development, I was trying to keep the components as generic as possible because I didn’t know how we might want to reuse them, and the plan was to have the designers build the game in Unity directly. This meant that there was significant complexity walking the reference chain from the Net which was what actually collided with the wall hook and the player which is what should be hanging in the end. I also made the decision to keep the hanging logic in the hang target instead of centralizing this logic on the player. The idea was that we could build up new mechanics out of existing building blocks and the whole system could be flexible. Looking back now, I would have made a different choice in the early days and only tried to make things more generic or flexible when the need became apparent. However, that’s not what happened. In the end, this approach to HangTargets was reusable from wall hooks to wall cocoons which combined the bounce effect from before with the new hanging ability. The simplicity of putting these together seemed to show that the approach was a good one.
Next week I’ll talk about how this approach broke down and ended up resulting in our first major refactor. I’ll do my best to explain what the thought process was and what other factors contributed to making the decision. Unfortunately, we’ve also gone through multiple source control systems that are no longer available to us so I’m not able to exactly reproduce the problems. Instead, you’re left with my biased memory. Anyway, thanks for reading and I hope you’ll join me next week!