Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Changing a transform gradually towards another.

Discussion in 'Scripting' started by Paliandro, Jun 5, 2023.

  1. Paliandro

    Paliandro

    Joined:
    Aug 9, 2017
    Posts:
    20
    I have a script, which is supposed to update gradually the position of some game objects from point a to b, defined in the inspector. However in game, the objects are actually moving in wrong direction than they are meant to. To note, the scaling here works correctly, only the positioning of the objects is wrong.
    I wonder if it could have something to do with the objects being the child of another? Or maybe my operations are just flawed?

    Code (CSharp):
    1. public void AdjustTransformation()
    2.     {
    3.         currentCallCount++;
    4.  
    5.         // Calculate the difference between starting and max values
    6.         Vector3 positionDifferenceLoob = maxPositionLoob - startingPositionLoob;
    7.         Vector3 positionDifferenceRoob = maxPositionRoob - startingPositionRoob;
    8.         Vector3 scaleDifferenceLoob = maxScaleLoob - startingScale;
    9.         Vector3 scaleDifferenceRoob = maxScaleRoob - startingScale;
    10.  
    11.         // Calculate the incremental change based on growth rate
    12.         float positionIncrementLoob = positionDifferenceLoob.magnitude / growthRate;
    13.         float positionIncrementRoob = positionDifferenceRoob.magnitude / growthRate;
    14.         float scaleIncrementLoob = scaleDifferenceLoob.magnitude / growthRate;
    15.         float scaleIncrementRoob = scaleDifferenceRoob.magnitude / growthRate;
    16.  
    17.         // Update the position towards the max value
    18.         loob.position = Vector3.MoveTowards(loob.position, maxPositionLoob, positionIncrementLoob);
    19.         roob.position = Vector3.MoveTowards(roob.position, maxPositionRoob, positionIncrementRoob);
    20.  
    21.         // Update the scale towards the max value
    22.         loob.localScale += scaleDifferenceLoob * scaleIncrementLoob;
    23.         roob.localScale += scaleDifferenceRoob * scaleIncrementRoob;
    24.  
    25.         // Check if the max values are reached
    26.         if (currentCallCount >= growthRate)
    27.         {
    28.             // Reset the call count
    29.             currentCallCount = 0;
    30.  
    31.             // Snap the transformation to the max values
    32.             loob.position = maxPositionLoob;
    33.             roob.position = maxPositionRoob;
    34.             loob.localScale = maxScaleLoob;
    35.             roob.localScale = maxScaleRoob;
    36.         }
    37.     }
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    How should we know what's growthRate supposed to be? And btw positions do not 'grow'.
    Regardless, you have to decide whether this is an incremental motion, or an absolute one.

    With absolute motion you kind of need to know how much time you're willing to spend. With incremental motion, you let the object drift toward the target on a step-by-step basis, but you regulate the maximum speed.

    MoveTowards is intended for the latter case.

    This here looks like it's something in between; usually scaling kind of works well with that (it simply looks better with exponential growth factors, because areas likes non-linear treatment), but translation and rotation do not. I also don't know what to expect from positionIncrementLoob. Docs are very clear on this: Distance to move current per call.

    So it's not supposed to drastically change each frame, and it's not supposed to have a wrong sign. You seem to extract the start early, then extrapolate other parameters from that, and I'm positive you're simply abusing MoveTowards.

    You can check how to use it from the example in the docs.
     
  3. Paliandro

    Paliandro

    Joined:
    Aug 9, 2017
    Posts:
    20
    Ah, I probably should've been more clear what's meant to happen with the script.
    Essentially, every time it's called, it's supposed to scale the objects by one step towards the desired maximum, while also adjusting their position. So, it's an instant change, occurring whenever the script runs, until the maximum value has been reached. The growthRate determines the maximum amount of calls. So, if my growthRate would be 3, the script would scale and reposition the object within 3 calls from the minimum value to the maximum, at evenly divided increments. At least that was the intention.
    Thank you for your answer, even though my question was poorly formulated.
     
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Ok, so you know the amount of calls prior to the motion? I.e. the amount of steps required. That means you know how many frames there are, which in turn means you know how much time it would take.

    In that case it's better to use Lerp for the movement, not MoveTowards.
    Code (csharp):
    1. // currentStep and totalSteps are ints
    2. void Update() {
    3.   var t = currentStep / (float)totalSteps; // casting to float prevents integer division
    4.   transform.position = Vector3.Lerp(start, end, t);
    5.   currentStep++;
    6. }
    Optionally you can apply easing functions when working like this, i.e.
    Code (csharp):
    1. void Update() {
    2.   var t = currentStep / (float)totalSteps; // optionally divide by (steps - 1) to reach 100%
    3.   transform.position = Vector3.Lerp(start, end, easeInOutQuad(t));
    4.   currentStep++;
    5. }
    6.  
    7. static float easeInOutQuad(float n)
    8.   => n < .5f? 2f * n * n : 1f - MathF.Pow(-2f * n + 2f, 2f) / 2f;
    You can see this particular (step) function here.

    Edit: fixed typos and integer division
     
    Last edited: Jun 5, 2023
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    An in fact, you can do a similar thing for the scaling, however as I said before, scaling really likes to work on exponential/logarithmic curves, looks more natural that way.

    For this you can try using this instead of standard linear interpolation ("lerp")
    Code (csharp):
    1. static public float ExpLerp(float a, float b, float t)
    2.   => MathF.Pow(a, (1f - t)) * MathF.Pow(b, t);
    Edit:
    Couldn't finish writing this at the time.. Normally this is used for zooming, but can also be used as a uniform scaling multiplier. Usually you fix your destination
    b
    as 1 and express
    a
    as
    startScale / endScale
    . Feel free to experiment with it though.

    This example will uniformly tween only x and y, while keeping z at 1.
    Code (csharp):
    1. transform.localScale = cmul(endScale, toxy1(ExpLerp(startScale / endScale, 1f, t) * Vector2.one));
    2.  
    3. static Vector3 cmul(Vector3 a, Vector3 b) => new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
    4. static Vector3 toxy1(Vector2 v) => new Vector3(v.x, v.y, 1f);
    If you want this to work with vectors, here's a solution for that, although I don't know how useful that would be.
    Code (csharp):
    1. static public Vector3 ExpLerp(Vector3 a, Vector3 b, float t) {
    2.   return v3l( i => MathF.Pow(a[i], (1f - t)) * MathF.Pow(b[i], t) );
    3.   static Vector3 v3l(Func<int, float> l) => new Vector3(l(0), l(1), l(2));
    4. }
     
    Last edited: Jun 5, 2023