Watch the above video (yes it's our recent mini-trailer - don't worry it's short), and pay attention to the moments when a dude gets punched and they explode. Or really anything else that explodes, we kinda dialed it up for the trailer.
You're seeing a couple of effects here:
- I'm tween kicking the camera in toward the target to add impact
- I'm emitting fist and entity particles (for dude deaths, anyways)
- Playing impact animation, making sure hit has follow-through, etc
- I'm waiting a split-second, and pulsing the timescale for slow-mo
In this article, I'm going to walk through how to use slow-mo, best practices, and then implementation tips. If you're in Unity, you CAN just go Time.timeScale = 0, but... we'll talk about why not to do that later. I mention the non-slow-mo stuff because it's part of tip #1.
Tip 1: Slow-mo Arrives Late And Leaves Early
You're probably going to start with all your effects stacked up at the moment of impact - when a fist hits a face, when a dude does a superhero fall, when someone dies, etc. You can't do this with slow-mo. If you slow-mo at the time of impact, you're going to be dwelling on a bunch of FX that... really haven't gotten to the good part yet. That awkward first blend-frame of a flinch animation, the bit where the particles haven't really spread out yet, the nasty tween frames of a dramatic tint map, etc. Wait a bit. Always give your FX time to settle for a split second, THEN hit 'em with your slow-mo pop.
Our code for this works by letting FX signal when they did a cool thing and want slow-mo. The manager takes care of waiting the split-second (and also manages multiple cool things requesting slow-mo at once):
Similarly, you may be tempted to leave it going for a long time. "Ooo, look at this dramatic camera pan around this frozen dude with a smashed face. I think I'll stay here for a few seconds" - nope, get it out of there. Treat the slow-mo like you would a hit-pause, if this were a 2D game. If you leave it going for too long, or make the slow-mo too extreme, all it does is give people a chance to realize how terrible your world looks when nothing is moving. When an object in a game stops moving for long enough to dwell on, it dies in the mind of the viewer. That being said, you can ignore this bit if the slow-mo isn't in response to a scene event, and is instead tied to an active ability or the like.
Tip 2: Don't Hard-Pop Your Timescale, Tween It
Like I said before, you CAN just Time.timeScale = 0 and be done with it, but, you really shouldn't. That will create a jarring transition to slow-mo, and can sometimes come across as a glitch rather than an effect. Especially for a short slow-mo pop, if you jerk to 0 timescale and back to 1, often your player will read it as your game losing frames. Not a good look.
All the code here is using DOTween, because I love it, but pretty much any tween library can do the same. Also, all the code here is Unity, but again, nothing about it is terribly Unity-specific. Anyways, your timescale tween will look a bit like this:
Now that gets you slow-mo that works. You can stop here, and so long as everything in your UI is running timescale independent, you'll have serviceable, simple slow-mo. But we don't want serviceable, we want FANCY.
Tip 3: Conditional Slow-Mo Is Even Cooler
If you add an interfacing function between any of your logic that requests deltaTime, and the actual deltaTime, it means you can have stuff running in different timescales. It means you can turn the entire world slow-mo, but you-the-player can still move at regular speed. This is where slow-mo pops get extremely cool.
Want to pulse the world to the beat in a way that doesn't impact player controls or negatively influence movement-feel? Now you can.
Want to do Witch Time (seriously did a memo go out? everyone's doing Witch Time now), like in Bayonetta (or in Nier's counters)? Yep, this does that.
To pull this off, you're going to have a registration function like this somewhere:
The idea is simple. For any object you want to move faster-than-slow-mo (player, mostly), you add an exclusion, in which you specify the minimum rate you want it to move. You CAN just enter 1.0 to make slow-mo not impact them at all, but I'd usually suggest something more like 0.5 to 0.8. In practice, it feels really weird to move at full speed while everything else is slow-mo, so you want to feel it a little bit, just not at full force.
Now, anywhere your code requests deltaTime (and boy howdy do I hope all your Update functions use a shared "float deltaTime = Time.deltaTime" at the top, or this will be a bear to retrofit in), instead of float deltaTime = Time.deltaTime, you just do float deltaTime = TimeManager.GetTimescaleFor(this.gameObject). That's it.
This is also handy for UI, and a host of other cases where you don't want things running at entirely-unscaled time, but you also don't want them to freeze when something like the UI freezes timescale.
Tip 4: You Can Also Timescale-Exclude Stuff Like Particles
So you've got your player running circles around frozen enemies, but... man, the blood dripping off her sword after you slash the slow-mo guy is dripping in slow-mo. Dangit. I wish that blood were also running in my Witch Time, but I don't actually control its processing, so... wait. AH HAH.
This is where the article goes off the rails for a sec, because my code for this doesn't work. I know, shameful, if you've got the fix, holler. BUT, I'm including this anyways, so you can see the principle of how you'd do this.
In principle, you need a function like this:
This gives you a timescale factor that, when applied to something processing at (whatever timescale actually is), speeds it up to match whatever min-timescale you want it processing at. Say your timescale is at 0.1, but you've excluded the player to never go slower than 0.5 - so this spits back 5.0. You just plug the return for this function into the timescale factor on anything associated with an object you've got excluded from timescale. The particles on your player's sword, say. Now the particles are running at 5x the actual timescale (0.1), which is 0.5, and tada.
This has some significant issues:
- Remember how we're tweening timeScale? Since this depends on timescale, you have to do this every single frame.
- Not everything has a timescale parameter you can plug this into, so you might have to get creative. Want a physically active thing to simulate as though it were running faster? That means ramping any forces you apply into it by this amount, and ramping its gravity by this amount, etc... and it still won't quite be right, just, closer.
- This will not be accurate to real-time, it's just a convincing simulation. Deltatime fluctuates, and this just ramps deltaTime up. If you do this in a recorded demo, it will break the demo. If you're relying on the timing of a thing you speed up, weird stuff could happen. You should ONLY do this on irrelevant fancy fx stuff, like particles, or sounds, or whatever.
- As stated, my code for this does not presently work. Given that, don't copy-paste it and wonder why it doesn't work, ok? :D
... but. In principle, it works. I've got it working in limited situations. Ok. There's more tricks, though.
Tip 5: Yes, You Can Apply Timescale Pulses To Audio
Normally, engines exclude audio from timescale, because the results are garbage. I contend that this is very boring, if generally accurate. So let's junk that audio up, but in a controllable way!
This just spits out what the timescale for the object is. If you're at 1.0, it probably just gives you 1.0. If you're at 0.1 and the object isn't excluded, 0.1. If world is at 0.1, and this object is excluded at 0.5, it hands you back 0.5. You get the idea. To make this apply to audio, you just bung this value into its pitch scalar.
... and because you're nicely tweening the timescale, this not only gets you beautiful awful distorted slow-mo audio, but it ALSO blends down to bizarre distorted slow-mo audio perfectly, and then back up to real-time.
What you will very quickly find is that your audio needs to have a timescale exclusion way higher than anything else. A little timescale distortion goes a long way, with sound. If you do that, though, you can get some really bizarre stuff with this. Maybe not useful stuff, but, it's pretty cool.
Depending on audio engine, you may not have to do any of this. It's still worth mentioning though. Anyways, onward to the last tip!
Tip 6: Guess What Happens If You Timescale Exclude Above 1.0
Yes, you can, and yes, it's cool. In effect, it turns whatever you timescale excluded into The Flash. Want to give an enemy buff that literally turns them into The Flash for a second? Make sure you've got motion blur enabled, and timescale exclude them to, oh, 5.0, or maybe 10.0. A blur will flash up to you, you'll hear a staccato BLATBLATBLATBLAT sound, and you'll die.
Mostly, this is useful for simulating how OP (and extremely dumb) The Flash is as a superhero. It is, however, also just extremely cool, and pretty fun. Experiment. You can probably find some uses for it.
No, don't do this to the player. Or, I mean... go for it, but, you probably better get the blue hedgehog player skin in first.