The world would be a dull place if bits of it didn’t move around.
Making a platform move is easy. Making it move and carry around things that are on top of it… Well, that’s a whole other kettle o’ fish.
This Is Not a Platformer, But You Can See One From Here
Sure, I’m making a hack ‘n’ slash action-RPG, but we’re in 3D space here and you can’t have 3D space without jumping or the odd mobile platform.
Jumping wasn’t actually so hard to add. I implemented “aerial sliding” ages ago, and jump just fires you forwards with some vertical velocity instead of keeping it to the horizontal. The speed decay and influence of gravity apply as usual and give you a neat curve.
Maybe I should note there are actually three kinds of jump. One from standing that is a short hop, one from walking that goes a small distance, and one from running that goes a long distance. I hope to maintain this style of context sensitivity as I go along, so very few controls can give rise to a dizzying array of possibilities. (Remember: the best part of Tomb Raider was just jumping and swan-diving around.)
Having said all that, though, I’m not sure if jump will actually be in any final work. Seems a bit daft for a 5-metre-tall hunk of metal to jump, but then again, if it’s fun…
Platforms are, conceptually, not that tough to add. So your character is standing on a surface; look down and see if that surface is mobile, and if it’s moving, then apply its motion to the character before you do anything else. Job done. Right?
Ha. Now we have to talk about component execution order and how, well, there is no order. Not a deterministic one, at any rate. The thing about moving the floor is that you need to do it before moving the character, so that the character can do all its collision detection (no point checking if you can walk up to something that’s not actually there anymore). How can you move the floor before the character if you can’t guarantee the order they’ll execute in?
My solution is no doubt naive as always, but it’s hopefully the most fun. There’s a property on the mover that exposes its movement during a given frame; that whole execution-order malarkey means I can’t tell if I’m getting the movement from the last frame or the movement from this frame.
This is the bit where I’m probably being far too clever and, whaddaya know, it collapses further down the line. Ah well, you live and learn.
Unity doesn’t offer any guarantees of script execution order, but it does give you Update and LateUpdate. All the Updates run, then all the LateUpdates run. In LateUpdate, I reset the record of the platform’s movement: it has not been calculated yet for the next frame. (Effectively using it as an EarlyUpdate, but no sign of one of those in the API.)
When the next frame comes around, either the floor calculates its movement on its own — or the unit asking for its movement causes it to calculate its movement early. It is now ambivalent towards execution order: either the movement is calculated early, or it isn’t. The important thing is that, no matter what, the platform’s movement for the current frame is always available for application to things that are meant to be standing on it.

So it made conceptual sense, but the practicalities never match up to the dreams, do they? It does work… Most of the time. When it doesn’t work… Well, ah, I’m sure we’ve all fallen through the floors of lifts in games before, right? (My target is to make something from the late 90s/early 00s, so this level of wonkiness is probably acceptable under that criteria.)
Basically, the above works for floors that move upwards fairly slowly. It turns out, however, that even (apparently) applying the same movement offset to the character as to the floor, there is some minor discrepancy: and as soon as the unit falls even fractionally behind the floor, it drops below the surface of the mover, stops detecting the mover as the floor and so falls through.
(This discrepancy might be frame-rate dependent, which would imply my calculations somewhere are not independent, but I’m damned if I can see where.)
It seems like it’s a small enough discrepancy that the natural collision detection of the CharacterController wins out and keeps everything in line for a slow-moving floor, but for a fast-moving floor that might have risen half-way through the character’s old position in the next frame it’s game over.
So… Lifts were in for a few hopeful days, but now they’re out again.
Of course I’m used to developing for Warcraft III where lifts were just not a thing, but I’d really like to have them. I always wanted more verticality, and that gets mighty impractical when you have to do all of it by ramps and stairs (or cheap fade-outs). I guess for now I’ll put in some kind of speed limit and hope for the best.
HINT: This is where y’all tell me the answer.
Have the platform do coolition detection on the player? ie: I move up, the player is there, give it a shove (with or without passing some kinetic energy, depending on your physics model). But if you do that wrongly, you might end up with a bouncing player, or a player that permenantly falls without decending just above the floor 🙂
These kind of things are always nastly complicated. You need to have a clear picture of what’s going on, or there will be things going wrong.
If you want an ugly patched together fix: you could check wether the vector between the player and the lift is the player being beneath it but less so than the player’s height. If so, place player back on top of the lift. Carefull when jumping against the bottom of a lift though 😛
LikeLike
I did try adding on by the discrepancy if I detected it, but that didn’t work either.
I’d rather not have the mover know about the things standing on it, but it is one approach I dug up while looking around the internet. There are collision entry and exit functions; while I’ve been trying to re-detect the presence of a floor every frame (because that fits better with the rest of the movement code), it might be best to detect entry -> always snap -> detect exit -> stop. Of course, that’s assuming the entry-detection would also work for fast-moving platforms…
LikeLike