Yesterday I did some design work and really WIP art for A Proper Boss.
Most of its behavior I get well enough to implement now. The one problem: its tail.
This guy's got a tail composed of like 8 separate sprites. Segmented. You've seen this before, I'm sure.
The tail is swept around the playing field, getting up in your shit to
complicate the space you have to dodge the boss's projectiles in. It's
also used for a thrusting attack that punishes you for putting too much
space between yourself and the boss to try and cheese it from a safe
(The tail is also destructible and regenerates on a somewhat irregular
schedule, but that's not the issue here. I know how to do that!)
The boss moves around the screen freely. The tail follows, being
attached as it is, you know, a tail. Tricky part: the boss can rotate in
eight directions, (though it only has four "facing" directions - the
diagonals are just used as midpoints for animations) and while it can
only move in four, it does need to have the tail keep a consistent
anchor point at all times during its arc. Where the tail should be
anchored moves around the sprite, so that's fun!
I also want this to look reasonably natural, so that's a problem. I'm
doing this design in public because I am very much Not A Real Programmer
- I don't dislike programming, but tbh I find it much less
engaging than pure game systems design, and I've never been one of those
crazy people that does crypto for fun; the primary reason I'm not the
worst kind of "games programmer" type is that I force myself to at least
try to maintain sound habits, and that's entirely because I know I
don't have a natural inclination to such and I need to keep a codebase
that's not completely unmanageable because I can produce a better game
if I can experiment and iterate with some degree of freedom - and I
want someone to tell me if I seem to be going about this problem the
Okay, let's break this down in terms of behaviors. I'm gonna say right
now that there's nothing which has the anchor point change during an
attack cycle; if it seems, down the road, that this boss needs such an
attack then fine, but right now that'd be a lot of complexity for not
much in return.
1) tail follows body
Well, this is the easy part. But I can't use transform parenting
here, unfortunately, so it does get a little dicier - remember the
"looking natural" thing? I want to enforce a delay on the more outlying
segments of the tail, so they respond to movements after the ones nearer
to the body, and you get a nice arcing motion and a sense of inertia.
Enemy_BossManta_Tail MonoBehaviour, attached to the main body
gameobject: manages the tail segments. Stores anchor point as a Vector3,
the last n anchor points as a Vector3, and tail direction as a
Direction. Each tail segment then has an Enemy_BossManta_TailBit
MonoBehaviour attached. The TailBit has a reference to the controlling
Tail behaviour, and when not attacking uses that to recalculate its own
position each frame based on the anchor point, an integer
distance-along-tail, and the Direction specified. Distance-along-tail
provides both how far out from the anchor point we want to be and which
anchor point we're referring to - we only use the current anchor point
if we're the segment closest to the body; if we're at least 1 segment
away, we use oldAnchorPoints[distanceFromBody - 1]. But since the Tail
continues recalculating its anchor point and refreshing that array anyway: we naturally sync up as long as the boss isn't presently moving too fast for us to follow.
This does enforce a speed limit on the boss of no more than 12px/frame, because at no point should there be more than 4px' gap between tail segments.
I'm going to go on record as saying I don't believe that I need to have
the boss covering more than 4.5 screen widths in a second, so nbd.
2) tail sweep
This is fun because we want an arcing pattern, and we have to force each segment to move proportionally. In a single frame, the further-out segments cover more space than the nearer ones.
So let's kill one layer of complexity right off the bat: fuck doing a real arc. Curves are a motherfucker and there's no reason to bother when I can fake it. The "arc" is composed of six points.
There's a start position that we move the tail tip to, and then we
bring it through four intermediate points en route to the end position,
and from the end position we reset to neutral.
Use a "virtual tailtip" to determine where to position the actual tail segments. This is a Vector3 stored by the Enemy_BossManta_Tail component that just represents where the tip of the tail would be if there was no delay attached to it.
We move each segment by (VirtualTailtipPos -
lastFrameVirtualTailtipPos) * (1 / (tailLength - distanceFromBody)). And
we move segments manually like this by adding a Vector3 to a queue.
The segment commits the movement by adding that Vector3 to its
transform. This allows us to enforce outlying segment delay even during
attacks - start the queue off with a number of Vector3.zero instances
equal to its distanceFromBody and it'll "commit" these null movements
before it starts processing the actual ones.
Each point in the arc is a separate bounding box. These bounding
boxes move around relative to the boss body, such that they're always in
the "right" place if it decides to start a swing. We find a heading
that drags the virtual tailtip through the first of these boxes, apply
it until the virtual tailtip is inside the box, repeat the process with
the next one, and continue until the virtual tailtip and real tailtip
are both inside the last box. At that point, we reset the tail to
This is easy enough now that we've worked out a lot of the more basic
problems. It's basically the same as the sweep, except a hell of a lot
faster, and instead of using the fake arc system we just want the
virtual tailtip to sit at a point 16px behind the player. This is
literally just "player.collider.bounds.center.x - 16 if anchor point
> player.collider.bounds.center.x, else
player.collider.bounds.center.x + 16," and same for y.
So this guy's basically brainless right now, and it doesn't do anything else, and (surprising no one) there's a bug with the minimap I need to fix.
But, hey: that fucking tail sweep works.