• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
Question by vexe · Oct 29, 2013 at 05:17 PM · updatetimefixedupdatetimescaleslowmotion

Slow motion all but one - yet another round, hopefully the last

Hey guys, I know this is like, the third or fourth time something like this has been asked, but surprisingly, none of them gave a 100%-working answer. Hopefully this will be the last question, on the subject.

So here's what the docs says:

When timeScale is set to zero the game is basically paused if all your functions are frame rate independent.

This means that (correct me if I'm wrong) changing Time.timeScale will affect everything that depends on game time. So if I had something moving in Update like:

 transform.position += new Vector(x, y, z) * speed * Time.deltaTime;

This will get affected if we change the time scale. But:

 transform.position += new Vector(x, y, z) * speed;

This shouldn't get affected, if we were doing it in Update, since it runs every frame (time independent) right?

Well, if you make a simple test you'll see that changing the time scale will also affect this movement.

Try this in FixedUpdate, it will also get affected - in fact you'll get a very jerky movement.

So here's a couple of things I tried (in Update):

  1. transform.position += new Vector(x, y, z) * speed / Time.timeScale;

This actually works pretty good, if you change the time scale to something like 0.1f, your movement won't get affected - But the problem is, this is frame-dependent, meaning performance will vary upon the hardware - Something very basic I learned when I started with Unity, is to multiply by Time.deltaTime - But if I do that, now it's now gametime-dependent, meaning changing the time scale will affect the movement. So, we got our smooth movement, but we lost our slowmo invincibility.

  1. transform.position += new Vector(x, y, z) * speed / Time.timeScale * Time.fixedDeltaTime;

Now this seems to be the winning card I came up with, it works just like my first point in that changing the time scale won't affect it, but I'm not sure, if it gives smooth movement like the one we get when we multiply by Time.deltaTime - My guess is, since it actually makes changing the time scale has no effect, it doesn't make our movement time-dependent, it's still frame-dependent, so again, performance will vary upon hardware.

Now let me show you my test environment - I have a Player script attached to my player, which is just a sphere, and an Enemy script attached to my enemy spheres, which makes then move randomly using coroutines:

     public class Player : MonoBehaviour
     {
         public float speed = 25f;
         bool slow = false;
 
         void Update()
         {
             float h = Input.GetAxis("Horizontal");
             float v = Input.GetAxis("Vertical");
 
             transform.localPosition += new Vector3(h, 0, v)
                                         * Time.fixedDeltaTime
                                         * speed
                                         * 1 / Time.timeScale;
 
             if (Input.GetKeyDown(KeyCode.Space)) {
                 slow = !slow;
                 Time.timeScale = slow ? .1f : 1f;
             }
         }
     }

     public class Enemy : MonoBehaviour
     {
         public float timeToTake = 1f;
         public Transform[] locations;
 
         bool hasReachedTarget;
         Transform mTransform;
 
         void Awake()
         {
             mTransform = transform;
             hasReachedTarget = false;
         }
 
         IEnumerator Start()
         {
             while (true) {
                 Vector3 randPosition = locations[Random.Range(0, locations.Length)].position;
                 StartCoroutine(MoveTo(mTransform, randPosition));
                 while (!hasReachedTarget)
                     yield return null;
             }
         }
 
         // This is a modified version of the original `MoveTo` from UnityGems - Credits go there - Specifically the Coroutines++ tutorial.
         IEnumerator MoveTo(Transform objectToMove, Vector3 targetPosition)
         {
             float t = 0;
             Vector3 originalPosition = objectToMove.position;
             hasReachedTarget = false;
             while (t < 1) {
                 t += Time.deltaTime / timeToTake;
                 objectToMove.position = Vector3.Lerp(originalPosition, targetPosition, t);
                 yield return null;
             }
             hasReachedTarget = true;
         }
     }

I also made a video :) - To test out the previous case - I mess around changing different values.

You'll notice 2 things:

  1. If I slow down time, it's true that I won't get affected, but collision start to mess up - I can go through walls! (~0:20)

  2. If you were moving in normal time, and slow down time while you're moving, you'll get an incredible boost in speed! (~0:30)

My last attempt (not included in the video), was not to mess with time scale, but actually with the speed factor of the movement, I simply changed a few things:

My Player.Update:

 void Update()
 {
     float h = Input.GetAxis("Horizontal");
     float v = Input.GetAxis("Vertical");

     transform.localPosition += new Vector3(h, 0, v)
                                 * Time.deltaTime
                                 * speed;

     if (Input.GetKeyDown(KeyCode.Space)) {
         slow = !slow;
         var enemies = FindObjectsOfType(typeof(Enemy)) as Enemy[];
         foreach (var enemy in enemies)
             enemy.Slow(slow);
     }
 }

In my Enemy, I added a Slow method, and a static shared slowMoFactor:

 static float slowMoFactor = .1f;
 public void Slow(bool slow)
 {
     timeToTake = slow ? timeToTake / slowMoFactor : 1f;
 }


Now this works very well - Frame-independent movement, with player invincibility to time scale changing!

But I'm pretty sure, that real games aren't as simple as spheres moving around - So, slowing down each moving element individually, might not be very efficient, especially if there was so many, they'll have animations, rigidbodies, etc.

So, how should we go about slowing down time for all objects in the world except our player, in a robust, clean and efficient way? - A way that also, handles physics-related movement for objects that has rigidbodies.

Thanks a lot for any help in advance.

Comment
TechSupportIncoming1
clunk47
zero_null
Kevinkestein

People who like this

4 Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image fafase · Oct 30, 2013 at 09:04 AM 0
Share

Problem with division by timeScale, if you hold time, you get a NaN

avatar image vexe · Oct 30, 2013 at 10:34 AM 0
Share

Yeah well maybe we could set the scale to something very small, instead of 0 when we want to pause.

6 Replies

· Add your reply
  • Sort: 
avatar image
Best Answer

Answer by Loius · Oct 30, 2013 at 01:51 AM

Speed * deltaTime / timeScale is the correct way to keep a thing moving at normal speed. Use that for your player and they'll stay normal-speed. Deltatime is already based on the time scale, so you divide out timescale to get back to realtime.

Essentially at some point Unity does this:

Time.deltaTime = (Time.realtimeSinceStartup - lastTime) * Time.timeScale;

You also need to use GetAxisRaw instead of GetAxis, because GetAxis uses gravity and sensitivity from the Input settings, which are affected by timescale.

Comment
vexe
CathChapo101
zero_null

People who like this

3 Show 7 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image vexe · Oct 30, 2013 at 04:18 AM 0
Share

Thanks for your reply. That seems very reasonable. It's one of the first many things I've tried. However it doesn't seem to work, the player seems to slow down as well. Code:

 void Update()
 {
     float h = Input.GetAxis("Horizontal");
     float v = Input.GetAxis("Vertical");

     transform.localPosition += new Vector3(h, 0, v)
                                 * Time.deltaTime
                                 * speed
                                 * 1 / Time.timeScale;

     if (Input.GetKeyDown(KeyCode.Space)) {
         slow = !slow;
         Time.timeScale = slow ? .1f : 1f;
     }
 }
avatar image Loius · Oct 30, 2013 at 02:24 PM 1
Share

Try this instead:

     float h = Input.GetKey(KeyCode.A)?-1:Input.GetKey(KeyCode.D)?1:0; //Input.GetAxis("Horizontal");
     float v = Input.GetKey(KeyCode.S)?-1:Input.GetKey(KeyCode.W)?1:0; //Input.GetAxis("Vertical");

How are your axes set up? Mine are thrashed so I had to just go straight to keycodes. My sphere keeps moving at the same speed; I had to add a debug log in just to be sure I was actually changing timescale.

--Oh hey, I tested it in a new project, definitely the Axis calculations are being weird. GetAxisRaw will definitely work correctly. It seems that Input's easing (gravity, sensitivity, and snap) settings are all based on timescale.

avatar image vexe · Oct 30, 2013 at 02:38 PM 0
Share

Well, that works! But why? What do the axis have to do with any of this?

This currently is the best working solution. It eliminates the 2nd problem I had (the speed boost) - But the first problem still exist, player could go through walls!

Not just that, in real games, the player won't be a simple sphere, and he might not be moving using axis - He'll have animations, maybe a rigidbody, etc. - How do we go using the 1/Time.timeScale idea from there?

avatar image Loius · Oct 30, 2013 at 06:21 PM 1
Share

You're using GetAxis - that uses gravity and sensitivity to determine the current value of the axis. I don't know why those are affected by timescale, but they appear to be. GetAxisRaw just reads the control directly, no time involved.

Divide everything by timeScale; you have to exert control over everything anyway. Set the animation's speed to 1/timeScale every frame. If you use a rigidbody it'll have to be kinematic and you'll have to control its physics.

Going through walls is happening because you're teleporting instead of moving the object. (teleportation happens when you set position = something) That's a whole 'nother subject, lots and lots and lots and lots and lots of questions about that already. You need to use physics controls like AddForce instead of directly setting the position.

avatar image vexe · Oct 31, 2013 at 08:10 AM 0
Share

Thanks for your input. But going through walls only happens when I slow down time. It doesn't happen when the time scale is normal.

Show more comments
avatar image

Answer by meat5000 · Oct 30, 2013 at 10:42 AM

http://docs.unity3d.com/Documentation/ScriptReference/Time-realtimeSinceStartup.html

RealTimeSinceStartup is unaffected by changing timescale. Use this to keep track of time while timescale is changed.

Comment
vexe

People who like this

1 Show 8 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image vexe · Oct 30, 2013 at 10:46 AM 0
Share

I've tried to do this, in update:

 float lastTime = Time.realTimeSinceStartup;

and then in my movement:

 trans.pos += new V3(x, y, z) * speed * (Time.realTimeSinceStartup - lastTime);

The ball didn't move at all for some reason.

I'll try to wrap my movement in a coroutine and try this again, I'm pretty sure Mike (@whydoidoit) done this before in his coroutine++ tutorial...

avatar image meat5000 ♦ · Oct 30, 2013 at 10:56 AM 0
Share

Maybe this will shed some light on it.

Note that realtimeSinceStartup returns time as reported by system timer. Depending on the platform and the hardware, it may report the same time even in several consecutive frames. If you're dividing something by time difference, take this into account (time difference may become zero!).

[Scripting Ref]

Also note that in the SR example, they use double instead of float, I guess for the accuracy.

avatar image vexe · Oct 30, 2013 at 01:37 PM 0
Share

Here's what I tried:

 float lastTime = 0;
 void Update()
 {
             float h = Input.GetAxis("Horizontal");
             float v = Input.GetAxis("Vertical");

             transform.localPosition += new Vector3(h, 0, v)
                                         * (Time.realtimeSinceStartup - lastTime)
                                         * speed
                                         * 1 / Time.timeScale;

     if (Input.GetKeyDown(KeyCode.Space)) {
         slow = !slow;
         Time.timeScale = slow ? .1f : 1f;
     }
     lastTime = Time.realtimeSinceStartup;
 }

This ended up being just like the result you see in the video! (with the 2 side effects as well: player could cross walls and get a huge speed boost if he was moving and enter slowmo)

But then I noticed I still have the 1/Time.timeScale - but when I removed it, my player slowed down and got affected by the slow motion as well even though I'm using real time :(

What am I missing?

avatar image meat5000 ♦ · Oct 30, 2013 at 07:44 PM 1
Share
  transform.localPosition += new Vector3(h, 0, v)
 * (Time.realtimeSinceStartup - lastTime)
 * speed
 * 1 / Time.timeScale;

You wont need to de-scale the expression with 1/ts here as realtime is unaffected by scale, so doesn't need to be compensated.

Also, from the docs:

If you lower timeScale it is recommended to also lower Time.fixedDeltaTime by the same amount.

avatar image vexe · Oct 31, 2013 at 08:28 AM 0
Share
 Time.fixedDeltaTime = slow ? Time.fixedDeltaTime / 10 : .02f;

That actually fixed the going-through-walls problem when I slow down! - Thanks for pointing that out! We're getting closer.

You wont need to de-scale the expression with 1/ts here as realtime is unaffected by scale, so doesn't need to be compensated.

Yes, that is logical. But as I said in previous comment, even though I'm using real time, the movement is getting affected as well.

It seems to me that the solution is your answer + @Louis's (not using GetAxis, as it gets affected by time) + @fafase's (lowering down animations speed, when I have any).

But if you have anything to say/ other ideas about the fact that the movement is still being affected when I use GetAxis even though I'm using real time, then yield it.

Show more comments
avatar image

Answer by fafase · Oct 30, 2013 at 01:30 PM

Just occurred to me, instead of changing the timeScale, why don't you affect the animation speed of all object instead?

MainScript.cs

 public delegate void OnSpeedChange();
 public static event OnSpeedChange OnChange;

 void OnGUI(){
    if(GUI.Button(new Rect(0,0,100,100),"SlowDown")){
       if(OnChange != null)OnChange();
    }
 }

then on the scripts that should slow down:

 bool stopped = false;
 void Start(){
    MainScript.OnChange += SetSpeed;
 }
 void SetSpeed(){
    stopped = !stopped;  
    float value = stopped ? 0.0f:1.0f;  
    foreach (AnimationState state in animation) {
         state.speed = value;
     }
 }
Comment
vexe

People who like this

1 Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image vexe · Oct 30, 2013 at 01:35 PM 0
Share

It's still not complete, a bit similar to my last approach. What about rigidbodies and other objects, that don't have animations? - Like say for example you wanted to slow down time to evade a rock from falling onto you?

avatar image fafase · Oct 30, 2013 at 01:37 PM 0
Share

Oooh, ah yes. Then you are back into trying to figure out timeScale.

avatar image vexe · Oct 30, 2013 at 01:44 PM 0
Share

Thanks for your help. yeah I guess I have to wrestle more with time scale.

avatar image

Answer by Dracorat · Oct 30, 2013 at 08:30 PM

If it were me, I'd extend the Time object with a custom class that added parameters:

 TimeSinceLastUpdate
 ModifiedTimeSinceLastUpdate
 SpeedScale

TimeSinceLastUpdate would get the value of Time.deltaTime and then ModifiedTimeSinceLastUpdate would get the value of Time.deltaTime * SpeedScale

My custom object would be the only thing using Update - I'd then enable the LateUpdate MonoBehavior and have everything else running in LateUpdate to be sure that they get the proper time value from my custom time class.

Then, when you calculate movement, you just use the property you want - TimeSinceLastUpdate or ModifiedTimeSinceLastUpdate depending on whether the thing should be affected by the speed scale or not.

Comment
vexe

People who like this

1 Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image vexe · Oct 31, 2013 at 12:34 PM 0
Share

Thanks for your answer. Interesting, fresh, out of the box.

avatar image

Answer by drallim33 · Nov 06, 2013 at 02:57 AM

To avoid the going through walls thing, one thing that should work is altering the fixed time step to match the altered time scale. If you normally have 50 FixedUpdates per real world second with timescale=1, at timescale=0.1 you'll only have 5 FixedUpdates per real world second. So you would need to alter the fixed time step to 0.1/50=0.002 in order to maintain 50 FixedUpdates per real world second.

 if (Input.GetKeyDown(KeyCode.Space)) {
     slow = !slow;
     if (slow){
         Time.timeScale = 0.1f;
         Time.fixedDeltaTime = 0.002f;
     }
     else{
         Time.timeScale = 1.0f;
         Time.fixedDeltaTime = 0.020f;
     }
 }
Comment

People who like this

0 Show 0 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
  • 1
  • 2
  • ›

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

23 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How to add Hit Stop Frames? 0 Answers

Is there an equivalent of Time.frameCount for physics updates? 1 Answer

How do I fix jitter? 1 Answer

Calcule Player's Speed based on Time.timeScale 1 Answer

how to get smooth slow motion? 4 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges