there's a fun thing wrong with my brain where i just cannot bear to leave uncovered cases or undefined behavior or other kinds of potential future potholes for myself. i can spot a lot of these a mile away, too: the unspoken assumptions made in what i'm saying to myself, or what someone is saying to me. (for example, every time i hear someone say they simply pause their game by setting the timescale to zero, seventeen klaxons go off around my desk.)
sometimes i describe a problem, where some convoluted (but entirely plausible) level geometry will cause A Problem, and inevitably an onlooker will suggest "i would simply not put that geometry in a level". but to me that is like saying "i would simply not have too much traffic on the bridge"
this is very abstract. i should make it concrete.
i am adding moving platforms to fox flux.
...well, okay, the jam game already had moving platforms. i'm overhauling the implementation, because i'm about to introduce them in the full game.
moving platforms have two parts: the platform itself, and the track that it moves along. the track is a polyline object drawn in Tiled. it does exist as a separate actor in-game, but mainly so the platform can refer to it; it doesn't actually do anything. (although i suppose it would be cool if it could draw itself sometimes?)
the implementation is fairly simple (except for some annoying physics calculus at the end of the track, where the platform slows smoothly to a stop). the platform remembers which segment of the track it's on, and which direction it's moving it, and it moves towards the next point on the track. when it reaches a point, it either continues along to the next one, or turns around.
now, see, in the jam game, i couldn't actually control where a platform started. it would always start at the first point on its track. i'd even place it there in the map editor as a reminder to myself, but it sort of gave the impression i could move it somewhere else.
and this is kind of inflexible. like if i want to start from the other end, that's too bad, because i don't think you can reverse a polyline. and in the case of a loop (i.e. a polygon), i certainly don't think you can change which point is "first". like why would you ever want to do that
so, now there's a new initialization step for moving platforms: they have to work out where on the track they are, so they know what the heck they're doing when they start moving.
my first crack at this just checked for the nearest point. because why would i start a platform out not at a point? if the platform wasn't on a point then, i dunno, something would happen. the platform would find a point and continue along to the next one on the track section it thought it was on, so, maybe its first movement would be kind of diagonal. i guess that's weird. but i could put a platform halfway between two points and it would do the right thing, probably.
but then i went to write a little docstring about how this worked, and i found bad track examples popping into my head, and i found myself apologizing for how they wouldn't work correctly. for example, imagine a flattened triangle, and the platform is placed so it starts in the middle like this:
(platforms are anchored at their top center.)
well, ah, whoops. it's pretty obvious what i meant, but the nearest point is the one at the top, so now... the platform will start out moving towards one of the side points, and then turn right around and go back along the bottom edge before finally starting to trace out a triangle? what the fuck.
and, dearest reader, this is the whole thing. i just cannot fucking bear when i can immediately think of an entirely reasonable thing i might do that will blow up in my face. it is like i am burying land mines in my own engine for no reason at all. when i inevitably try this in a few months, it's not like i'm going to see the goofy behavior and think "ho ho, well, i guess i can't do that, better do something else!". no i'm going to swear at my past self (who is currently me) for making me have to go back into this code and fix it again now that i've forgotten what dumbass thing it's doing.
so why not just fix it now while i'm thinking about it anyway?
thus begins round two: figure out which track section i'm on. this seems straightforward enough, except for the annoying part of "is this point on a segment". it's easy enough to work out if a point is on a line, but segments have endpoints, and those make it annoying.
and now the little gears in my little fox brain start turning, and a fascinating question emerges. because i know that what i'm doing here, fundamentally, is converting a point to a track section. like this is a function, in the mathematical sense, that takes one and produces the other.
but there are no real constraints on that point, right? it could, in principle, be anything. and so i wonder:
what happens if the platform isn't actually on the track to start with?
and here's where the easy answers are "simply don't do that" or "i dunno, something bad probably". but that feels like taking a hacksaw to the level design before i've even designed anything — suddenly this parameter has been reduced from "anywhere" to "only along this line", a reduction of infinity. and if "something bad" happens, then, what does that mean exactly? if i place a platform a pixel away from its track by accident, the game fucking explodes? what kind of failure case is that?
what's especially fascinating is that about half the time, sensible behavior isn't even that difficult. consider how you might place a platform on a track in the first place:
- iterate over track sections
- if the platform is on this track section, attach here and return; we're done
- we fell through, so blow up i guess
ah, but we skipped a step there. remember that whole thing about segment endpoints? yeah that's annoying. i think you'll end up doing something with vector projection to take care of it, at which point you are essentially at:
- iterate over track sections
- compute the platform's distance to the track section
- if that distance is zero, attach here and return; we're done
- we fell through, so blow up i guess
but now we're not very far away from:
- iterate over track sections
- compute the platform's distance to the track section
- if that distance is zero, attach here and return; we're done
- otherwise, remember the minimum distance seen so far
- we fell through, so attach to the nearest section...
- ...and use the closest point on the section as a temporary goalpoint
i mean, that's using stuff i was gonna compute anyway. i'm just not throwing it away any more.
and now something interesting has happened. what is a "temporary goalpoint"? well here it must be a point the platform moves towards first, before bothering with the track. in this case the temporary point is on the track — that's the whole idea, after all — but once again, nothing says it has to be. i've just invented a tiny new feature. almost for free! and i know it works because i'm already using it for something.
what else could i possibly use this for? i have no idea. it's just tucked in the back of my head as a little bookmark. but i'm already thinking about a platform that takes a brief detour off of its track for some reason, or using multiple tracks to fake track switches and moving between them, or a platform that has no track at all and is moved by some other means.
and part of this effort has been extracting the track code out of platforms entirely, so it can be placed on other objects. hmm. what else might want to wander off its track temporarily? creatures perhaps?
maybe i'll never use this. but i like feeling possibilities open up. i guess when i'm doing the dev work and the level design, it can feel like the code constrains what i even allow myself to consider for the levels, so the last thing i want to do is add to those constraints. on the other hand, doing something sensible in an edge case tends to let me imagine more broadly, which i think is worth it on its own
something else i did with platforms is— okay so i said they slow smoothly to a stop at the end, right? and, just so you know, they also accelerate smoothly up to top speed when they get going. (the twiddle for this "ramping" is a distance, not an acceleration, just because it was easier for me to reason about "how far along the track will this thing be before it's at full speed" if i gave a distance. the math isn't too much worse though.)
a lot of platforms follow a single-segment track: a straight line, horizontal or vertical. so it's very possible that the accel and decel parts happen on the same track section, and i have to make sure that works too. okay, not really a problem.
but then something immediately nags at me. i'm curious if you've spotted it. it's the kind of thing that sets off klaxons for me, but that most other people seem to overlook. i don't know if it's because i think about things mathematically or because i had an ear in infosec circles for a while or what. but i spell this out and right away i think...
what happens if the track is too short?
as in, what happens if the track is shorter than twice the ramp distance? then there's not enough room to get up to full speed and slow back down again!
so here's what i did, are you ready
i handled that too
i said, ok, we can't get up to full speed, but we do have enough room to get up to some speed and then slow down again, at the usual rate. so let's work out what that speed is, and only accelerate until we hit it, and then stop. at that point the deceleration will kick in because we're guaranteed to be within the deceleration zone. problem solved!
is this crazy? i don't know. at the moment, i have no reason to make teeny tiny track sections like that. but it would bug the hell out of me to know that i wrote something that already doesn't work sometimes. and maybe i would make a short track for something else that isn't a platform, like a grumblebee.
incidentally, part of why it's fine to leave the ramp acceleration implicit: when decelerating, i don't use it anyway. i calculate how fast i'd have to decelerate, given my current speed and distance from the end of the track, to come to a halt exactly on time. and then i decelerate that fast.
I always think it’s so nice when I come back to something like this and discover that it just kinda… works? Does the thing I need it to do? All because I was diligent when I built it the first time.
I think to myself “good job me!” A little present to myself.
💭 moving platform system that doesn't know about waypoints at all, you actually map them out as just a painted line on an invisible layer that the platform follows with like a classic line-following algorithm like a little robot. if it can't find the line it just woobles around until it finds one