Search Unity

Smoothing Motion

Discussion in 'Scripting' started by shawnpigott, Oct 29, 2005.

  1. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    I have a script which causes an object to move forward a specific distance with the 'a' key and turn a specific distance with the 'b' key. It raycasts so that the object will not pass through other objects. I'm using this script to help a disabled boy play games with his 2 chin switches.

    I'd would like to smooth out the motion between the jumps and turns as he is finding it disorientating. The Script is:

    var walkStep = 50.0;
    var angleStep = 90.0;

    function Update () {

    var fwd = transform.TransformDirection (Vector3.forward);

    if (Physics.Raycast (transform.position, fwd, walkStep)) {
    // You can only turn as there is something in front of you
    if (Input.GetKeyDown("b")) {
    transform.RotateAround(transform.position, Vector3.up, angleStep);
    }
    }
    else {
    // You can move and turn
    if (Input.GetKeyDown("a")) {
    transform.position += transform.TransformDirection(Vector3.fwd * walkStep);
    }
    if (Input.GetKeyDown("b")) {
    transform.RotateAround(transform.position, Vector3.up, angleStep);
    }
    }

    }


    'aarku' suggested using Vector3.Lerp to accomplish this so I checked the manual and I'm afraid it's a little beyond me right now.

    Any help would be appreciated.
     
  2. robertseadog

    robertseadog

    Joined:
    Jul 23, 2005
    Posts:
    374
    have you tried multipplying it with Time.smoothDeltaTime?

    anyway I think lerp works this way:

    Code (csharp):
    1.  
    2. transform.position = Vector3.Lerp (transform.position, transform.position + transform.TransformDirection(Vector3.fwd * walkStep), 0.5);
    3. }
    4.  
    never used it but it should smooth the value to the value between the current and the goal.. if you want you can still do the "+=" but I think it will speed things up a notch :S

    Love your project though! This is exactly what Unity should be used for.
     
  3. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    robertseadog

    I tried your suggestion and I think it's getting me closer. Thanks for the help.

    I think I understand what I need to do. I use the lerp function to generate the steps between where I am and where I want to be and then I use Time.smoothDeltaTime to make the transitions happen.

    My programming background comes from REALbasic so I understand the concepts but I don't think I know how to do this in Javascript.

    Just to clarify, I need to animate a smooth transition between two positions or between two angles.

    Could anyone can suggest a way to do this or where I may find a tutorial with Time.smoothDeltaTime examples.

    Thanks.
     
  4. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    I have played with Time.smoothDeltaTime a bit and think that my problem has to be solved by using variables.

    1. Hit the key ('a' or 'b')
    2. Determine how far to go (movement foreword or the turn)
    3. Change a variable from 0 (no move) to 1 ( yes move)
    4. Update
    5. Check progress and if we're there then change the variable back to 0

    Any thoughts or suggestions on how to do this.

    I found that Time.smoothDeltaTime doesn't play well with Input.GetKeyDown as it only functions when the key has been hit so it only works once. That's why I think the variable (or boolean maybe) might be the way to go.
     
  5. robertseadog

    robertseadog

    Joined:
    Jul 23, 2005
    Posts:
    374
    well, something like that. Time.smoothDeltaTime is often used to make transitions time-dependant instead of frame dependant.

    But this might work (I don't have unity on this machine so I can't really test it)?

    Code (csharp):
    1.  
    2. function Update () {
    3.    if (Input.GetButton ("a")) {
    4.    transform.Translate (Vector3.Lerp (transform.position, transform.position + transform.TransformDirection(Vector3.fwd * walkStep), 0.5));
    5.    }
    6. }
    7.  
    Im not sure how well it would really work in this example but if you wanted a smooth transition on something you could do something like this:
    Code (csharp):
    1.  
    2. function Update () {
    3. transform.position.y += 10 * Time.smoothDeltaTime;
    4. }
    5.  
    and that would make the transform move (smoothly) 10 units per second instead of per frame..

    Javascript is a bit different from other languages out there, but I think it's very intuitive, and although you might have to spit out more code in relation to C or obj-c, I think it's so nice to work with in the end.

    Another thing:
    Im a little confused as to what RealBasic really is so i was hoping you might know this stuff; It's a package for creating apps right? So it's kind of like Director but with more options? Does it compile in obj-c or is it a "real" language that exists in all macs and pc's? Im currently learning C and Cocoa (very hard) so Im interested in info about different languages so I can learn the best ones :eek: Ruby seems nice but I can't use it with Unity so I think Im gonna leave that for a while..
     
  6. robertseadog

    robertseadog

    Joined:
    Jul 23, 2005
    Posts:
    374
    ah I dwelled to long with my reply again.

    you have to use GetButton for this I think..
    so each frame when Input.GetButton ("Run"); is held down it should update.. But you might try with Time.deltaTime instead, it returns basicly the same just not so smoothed out. BTW: I forgot to mention, deltaTime is the time between each frame, not a second (slapping his forehead)!
     
  7. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    I've managed to make some progress but I'm still not getting the results I need. To simplify things I have created 3 scripts.
    1. Checks for an 'a' or 'b' keystroke and then changes a variable to signal that movement is to start. It also notes the destination position.
    2. Checks the object to see if it has reached the destination position
    3. Starts or stops the motion

    The problem is that the object starts moving but doesn't stop. I'm also not sure how to control the rotation.

    Script 1 (OnandOff)
    Code (csharp):
    1.  
    2. // turn move on and determin end point for checking script
    3.  
    4. var onoff = 0;
    5. var onoffmove = 0;
    6. var walkStep = 10.0;
    7. var angleStep = 90.0;
    8. var movewhere : Vector3;
    9. var turnwhere = 0;
    10.  
    11.  
    12. function Update () {
    13.     //var hit : RaycastHit;
    14.         var fwd = transform.TransformDirection (Vector3.forward);
    15.  
    16.       if (Physics.Raycast (transform.position, fwd, walkStep)) {
    17.     // You can only turn as there is something in front of you
    18.                 if (Input.GetKeyDown("b")) {
    19.                     onoff = 1;
    20.             turnwhere =  transform.rotation.y + 90;    
    21.                 }
    22.          }
    23.        else {
    24.     // You can move and turn
    25.                 if (Input.GetKeyDown("a")) {
    26.                     onoffmove = 1;
    27.             movewhere = transform.position + transform.TransformDirection(Vector3.fwd * walkStep);    
    28.         }
    29.             if (Input.GetKeyDown("b")) {
    30.                     onoff = 1;
    31.             turnwhere =  transform.rotation.y + 90;    
    32.         }
    33.         }
    34.  
    35. }
    36.  
    Script2 (OnandOffCheck)
    Code (csharp):
    1.  
    2. //check to turn off movement
    3.  
    4. var Camera : Transform;
    5. var moveplace : OnandOff;
    6.  
    7. function Update () {
    8.     //check if move is complete
    9.     if ( Camera.position == moveplace. movewhere)   {
    10.                 moveplace. onoffmove = 0;
    11.             }
    12.  
    13.     //check if turn is complete
    14.     if ( Camera.rotation.y == moveplace. turnwhere)   {
    15.                 moveplace. onoff = 0;
    16.             }
    17. }
    18.  
    Script 3 (onandOffMove)
    Code (csharp):
    1.  
    2. var onoff1 : OnandOff;
    3. var onoff2 : OnandOff;
    4. var turnspeed = 100;
    5. var movespeed = 50;
    6.  
    7. function Update () {
    8.  
    9. //rotate on and off
    10.     if (onoff1.onoff == 1)
    11.         {
    12.             var translation = Time.deltaTime * turnspeed;
    13.             transform.RotateAround(transform.position, Vector3.up, translation);
    14.         }
    15.     if (onoff1.onoff == 0)
    16.         {
    17.             var translation1 = Time.deltaTime * 0;
    18.             transform.RotateAround(transform.position, Vector3.up, translation1);
    19.         }
    20.  
    21. //move on and off
    22.  
    23.     if (onoff1.onoffmove == 1)
    24.         {
    25.             var translation2 = Time.deltaTime * movespeed;
    26.                 transform.Translate (0, 0, translation2);
    27.         }
    28.     if (onoff1. onoffmove == 0)
    29.         {
    30.             var translation3 = Time.deltaTime * 0;
    31.             transform.Translate (0, 0, translation3);
    32.         }
    33.  
    34.  
    35. }
    36.  
    Any help would be appreciated.
     
  8. Bampf

    Bampf

    Joined:
    Oct 21, 2005
    Posts:
    369
    I've only been skimming your code but your problem with it not stopping might be the following line:
    Code (csharp):
    1.  
    2. if ( Camera.position == moveplace. movewhere)   {
    3.             moveplace. onoffmove = 0;
    4.          }
    5.  
    Checking for equality between two floating point numbers/vectors is problematic. The slightest precision difference and the numbers will never be equal.

    Many ways to get around this. You could measure the distance from the starting point, and when it equals OR EXCEEDS the desired distance then stop.

    You could do a looser equality test; if the floating numbers are "close enough" then treat them as equal. But this isn't foolproof. If I'm reading your code correctly, a bad skip in the frame rate could cause the Time.deltaTime value to be a little too large, and again it won't turn off.

    I haven't used Lerp but it looks like a classic linear interpolation. If the third arg is 0 then it will return the first value, if it's 1 then the second value, and anything in-between would be the appropriate blend of the two values. 0.5 would return the average of the two arguments. Let's say you are at time t0 and want to arrive at the destination in 5 seconds (t0 + 5.0). At a given point in time (t) you pass in for the third argument (t - t0)/5.0. Initially this is 0 but reaches 1.0 at 5 seconds.

    Lerp may treat anything greater than 1.0 as 1.0, but I suspect not, so you would need to clamp the third argument to the range [0,1]. That way if there's a skip in the time step you won't move past exactly where you need to be at 5 seconds. (That's also when you would turn off the movement.)
    [/code]
     
  9. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    The positive at this point is that I've had to learn a bunch of JavaScript and I feel I'm getting a better handle on how things work in Unity versus my experience with REALbasic.

    The negative is that I think I may have over complicated the problem. So I thought I should restate it.

    I want an object to slowly turn 90 degrees when the 'a' key is hit. And slowly move foreword a specific distance when the 'b' key is hit.

    I have been trying 'for-next' loops and 'while' loops and various other ideas but I'm sure there must be an easier way to do this than the 3 script approach I came up with above.

    Thanks to everyone for their suggestions so far.
     
  10. robertseadog

    robertseadog

    Joined:
    Jul 23, 2005
    Posts:
    374
    Code (csharp):
    1.  
    2. private var ispressed = false;
    3. private var count = 0;
    4.  
    5. function Update () {
    6.    if (Input.GetButton ("a")  ispressed == false) {
    7.    ispressed = true;
    8.    }
    9.      if (ispressed  count < 90) {
    10.      transform.localRotation.y += 1 * Time.smoothDeltaTime;
    11.      count += 1 * Time.smoothDeltaTime; /*this might not work, if not use count++; instead since that number is guaranteed to be int.*/
    12.          if (count > 88) {
    13.          count = 0;
    14.          ispressed = false;
    15.          }
    16.      }
    17. }
    18.  
    a for statement works like this:
    Code (csharp):
    1.  
    2.    for (i=1;i<90;i++) {
    3.    transform.localRotation.y += 1 * Time.smoothDeltaTime;
    4.    }
    5.  
     
  11. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    It's definitely getting closer.

    The script basically works but there are 2 strange problems.
    1. The object rotates 180 degrees instead of 90 degrees. I tried halving the 'count' values but it keep going to 180.
    2. It only works once. Not sure about this as it looks to me as thought the script resets once it reaches the end point. I was watching the rotation value and it never actually stops so that may be the issue.

    As I said before I'm new to the syntax of Javascript so I couldn't figure out how to get the ++ working so I'll look that up when I get home.

    Thanks for all you help. I'm going to play with your script and see if I can solve the problem.
     
  12. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    I figured out the ++ and got a strange result.

    Each hit of the 'a' key turns the object closer and closer to 180 degrees. Once at 180 the 'a' key no longer works. The strange part is that the first hit seems to produce a different value each time.
    First Try 145.4534
    Second Try 144.6547
    Third Try 146.6747

    I would have assumed that I would get the same results each time.

    The positives are that the 'a' key is starting a motion that stops after certain amount of rotation and that the motion is smooth.

    I'll keep playing with it but if anyone has an explanation for the the results I would appreciated it.
     
  13. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    I managed to create a script that rotates the object 90 degrees but I can't seem to find a way to trigger it by hitting the 'a' key.

    Code (csharp):
    1.  
    2. private var nextrotation = 0;
    3.  
    4.   while (Time.time < .1)
    5. // get the current rotation value
    6. {
    7.     nextrotation = (transform.eulerAngles.y + 90);
    8.     yield 1;
    9. }
    10.  
    11. while (Time.time < 1.1)
    12. //rotate for 1 sec at 90 degrees persecond
    13. {
    14.     transform. Rotate (Vector3.up, Time.deltaTime * 90);
    15.     yield 1;
    16.  
    17. }
    18.  
    19.    
    20. while (Time.time < 2.1)
    21. //make sure the rotation is 90 degrees (Or divisible by 90)
    22. {
    23.     transform.eulerAngles.y = nextrotation + 90;
    24.     yield 1;
    25. }
    26.  
    27.  
     
  14. robertseadog

    robertseadog

    Joined:
    Jul 23, 2005
    Posts:
    374
    well if you were using the delta time it should produce different values each time, but using the Value++; sign is the same as using Value += 1; or at least it should be.

    The thing I cooked up should stop after 90 degrees (hmm) and then you would have to press again to add another 90 degrees.. Maybe I misunderstood what you wanted?

    Now your script; Wouldn't that stop working after 2.1 seconds? I don't really understand what you are trying to do there, but in general if you want to have a while statement called at a specific time you could either check a value (EG: while (blabla == true)) or you could have the whole thing inside a function :

    Code (csharp):
    1.  function rotate () {
    2. while (blabla) {
    3. blabla++;
    4. yield;
    5. }  }
    and then call it like a cocoutine (inside a if statement to check input perhaps?) : StartCoroutine ("rotate");

    Hope you get this thing working, and that I haven't confused you too much with my crooky scripting..

    EDIT: So if I continue with my previous example, you could do this:
    Code (csharp):
    1.  
    2. private var ispressed = false;
    3. private var count = 0;
    4. private var myValue = 0;
    5. function Update () {
    6.    if (Input.GetButton ("a")  ispressed == false) {
    7.    ispressed = true;
    8.    myValue = transform.localRotation.y + 90;
    9.    StartCoroutine ("startMe");
    10.    }
    11.    if (count > MyValue - 1) {
    12.    transform.localRotation.y = MyValue;
    13.    MyValue = 0;
    14.    count = 0;
    15.    ispressed = false;
    16.    }
    17. }
    18. function startMe () {
    19.    while (ispressed == true  count < MyValue) {
    20.    count += 1 * Time.smoothDeltaTime;
    21.    transform.localRotation.y += 1 * Time.smoothDeltaTime;
    22.    yield;
    23.    }
    24. }
     
  15. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    ‘robertseadog’, your script does basically what I need but it still rotates the object 180 degrees and then it won't rotate again. I really don't understand why it goes to 180 instead of 90 and why the 'a' key doesn't work more than one.

    I was trying the other approach based on the 'scripted camera movement' example.

    I have tried changing values in your script but then the 'a' key works more than once but only until the rotation is equal to 180 degrees.

    The logic of your code is sound to me but for some reason Unity interprets it differently.

    To clarify I want to press the 'a' key and slowly rotate the object 90 degrees and press the 'b' key and slowly move the object a specific distance.

    Thanks for your continued interest and assistance.
     
  16. freyr

    freyr

    Joined:
    Apr 7, 2005
    Posts:
    1,148
    The 180 bug is because you are adding to localEulerAngles... use the Rotate method instead

     
  17. shawnpigott

    shawnpigott

    Joined:
    Sep 28, 2005
    Posts:
    262
    Thank you. I made the changes and have the script working at 99%. I managed to modify the logic behind it to make the movement script work also.

    The only problem at this point is that I gain a degree or 2 when I change the rotation angle. It works perfectly for 90 degrees but when I change to 35 degrees I gain 1 degree in the first few rotations. But only 1 degree and not on continued hits. When the turn angle is set to 10 degrees I get 1 degree added and then another on the next hit. Then no more addition degrees so it almost works perfectly.

    Thanks for everyone's help. I know I've asked a lot of questions but your responces have helped me learn quite a bit. If anyone has a suggestion for the extra degree(s) that would be great.

    Code (csharp):
    1.  
    2. private var ispressed = false;
    3. private var count = 0;
    4. private var nextrotation = 0;
    5. private var firstone = false;
    6. private var stoppoint = 0;
    7. var rotangle = 90;
    8. var turnspeed = 2;
    9.  
    10. function Update () {
    11.    if (Input. GetKeyDown ("a")  ispressed == false) {
    12.     ispressed = true;
    13.     nextrotation = (transform.eulerAngles.y + (rotangle+1));
    14.     stoppoint = ((rotangle/turnspeed)-1);
    15.  
    16.         if (firstone == false) {
    17.             //this is to correct an extra degree on the first hit
    18.             nextrotation = nextrotation -1;
    19.             firstone = true;
    20.         }
    21.  
    22.    }
    23.      if (ispressed  count <  rotangle ) {
    24.      transform.Rotate(Vector3.up * turnspeed);
    25.      count++;
    26.          if (count > (stoppoint)) {
    27.          count = 0;
    28.          transform.eulerAngles.y = nextrotation;
    29.          ispressed = false;
    30.          }
    31.      }
    32. }
    33.  
    Thanks again.