Friday, May 6, 2016

Hammer devlog 19

ok, so:

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 distance.

(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 wrong way.

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 neutral.

3) stab

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.

[​IMG]

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.