Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Exponentialy lower an object's progress rate the closer it gets to it's minimum value

Discussion in 'Scripting' started by SassyPantsy, Feb 19, 2023.

  1. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    136
    So, this is more of a math problem I reckon, but I'm gonna try it here since its probably in use in a lot of games.

    So I have a camera, looking straight down onto the world. Using the scroll wheel, I raise and lower the camera's height in relation to the world.
    The problem - The world is huge, and has a varying degree of detail, depending on how far away you are from it (like google maps). So, I want the player to be able to zoom in and out of the world, with a rate that is affected by the camera's distance from it: the further the camera is, the higher the rate is. The closer it gets, the lower the rate gets, to the point where the camera never actually touches the surface (kind of like how, if you always eat half of what's left of the pie, you'll never finish the pie). I'm getting something like an ease would be another good example.

    In terms of code, this is what I have now:
    Code (CSharp):
    1. var pos = transform.position;
    2.                 pos.y -= Input.GetAxis("Mouse ScrollWheel") * scrollSens * Vector3.Distance(transform.position, globe.position);
    3.                 transform.position = pos;
    Now, this works, but it feels too linear, and its either too low when far away, or two high when close up. Right now it also goes below the floor, but that can be fixed via other methods.

    I tried some variations of this function, but they all felt pretty much the same, except for when I multiplied distance to the power of another multiplier, which crashed Unity.

    Anyway, is there anybody here who knows how to go about this?

    Thanks!
     
  2. DevDunk

    DevDunk

    Joined:
    Feb 13, 2020
    Posts:
    4,396
    Maybe multiply the speed by the distance to ground? (With additional multipliers?)
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,749
    Minor film terminology quibbles:

    Everything you describe above is called a dolly. A dolly moves the camera.

    A zoom does not move the camera: a zoom changes the field of view.

    A dolly zoom does both simultaneously. :)

    ... aaaaaaaanyway...

    Exponential will quickly be a problem, but geometric should get you what you need.

    When computing how much to change the position, consider the value that you call

    (whatever this "it" is), compute the distance to it, and adjust forward / backward a relative amount per second.

    So if you are X units from the thing (however you are doing that computation above), you would increase/ decrease the distance to the object by a fraction of X (perhaps 10% of it?) times Time.deltaTime.

    Code (csharp):
    1. float distanceToIt = ....?
    2.  
    3. float dollyZSpeed = distanceToIt * 0.2f;
    4.  
    5. float dollyDistance = dollyZSpeed * Time.deltaTime;
    6.  
    7. camTransform.position += camTransform.forward * dollyDistance;
     
    DevDunk likes this.
  4. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    I would suggest storing the current zoom level linearly, then converting that to the actual distance. You can always do the math backwards if you want to get the camera to a specific position:

    Code (CSharp):
    1. // the amount we've zoomed in or out
    2. float zoomLevel;
    3.  
    4. // the actual distance from the ground
    5. float position;
    6.  
    7. // zoom out 2x
    8. zoomLevel += 1;
    9.  
    10. // zoom in 4x
    11. zoomLevel -= 2;
    12.  
    13. // compute camera position for current zoom level
    14. float position = Mathf.Pow(2, zoomLevel);
    15.  
    16. // compute zoom level for desired camera position
    17. zoomLevel = Mathf.Log(position, 2);
    I'm pretty sure what you've proposed is exponential -- your speed is based on your current value!
     
    Kurt-Dekker likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,749
    hm... I took it that exponential would be:

    y = e raised to the power of x


    so I'm saying geometric as;

    y = something times x


    I could even be wrong on both terms I suppose. :)

    EDIT: or are you thinking:

    y = log ( x)


    I think I would call that logarithmic...
     
  6. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    In general, something is exponential if its rate of change is directly proportional to its current value.

    Equivalently, an exponential function's value goes up by a proportional amount every time its variable is increased by a constant amount.

    So 2^x would be exponential, because every time x goes up by 1, the value doubles. On the other hand, x^2 isn't exponential; the rate at which x^2 grows is directly proportional to x, not x^2.

    e^x is the convenient case because the derivative of e^x is...e^x. So if you're at x=2, e^2 is 7.38905609893 -- and the rate of increase is also 7.38905609893!

    ...but anyway, back to the original point: the important bit is that
    y
    is the rate of change of the camera's position, so you'll zoom out faster and faster as the camera gets further and further away! Which is, indeed, exactly what the OP is looking for.
     
  7. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    136
    Ok so first of all -

    This doesn't really change anything, because I could just use the additional multiplier already as the additional multiplier.

    i.e, writing 2 * distance * 4 (2 being speed,4 being the additional multiplier) is the same as writing 8 * distance.

    This didn't really change the original as well, at least not as far as I could tell. It's still basically just increases the position based on the rate, that grows as the distance grows.

    Just to be clear, this is how I implemented it -

    Code (CSharp):
    1. if (cameraIsCentered && Input.GetAxis("Mouse ScrollWheel") > 0f)
    2.             {
    3.  
    4.                 var distance = Vector3.Distance(transform.position, globe.position);
    5.  
    6.                 var dollySpeed = distance * scrollSens;
    7.  
    8.                 var dollyDistance = dollySpeed * Time.deltaTime;
    9.  
    10.                 transform.position += transform.forward * dollyDistance;
    11.             }
    As for this, I'm not so sure how to implement this (you wrote position twice, and referred to it as the distance, but then didn't use that distance again).. Anyway, I tried writing it like this:

    Code (CSharp):
    1. if (Input.GetAxis("Mouse ScrollWheel") != 0)
    2.             {
    3.                 float zoomLevel = transform.position.y;
    4.  
    5.                 float distance = Vector3.Distance(transform.position, globe.position);
    6.  
    7.                 if (Input.GetAxis("Mouse ScrollWheel") >= 0)
    8.                 {
    9.                     zoomLevel += 1;
    10.                 }
    11.                 if (Input.GetAxis("Mouse ScrollWheel") <= 0)
    12.                 {
    13.                     zoomLevel -= 2;
    14.                 }
    15.  
    16.  
    17.                 transform.position *= Mathf.Pow(2, zoomLevel);
    18.  
    19.                 zoomLevel = Mathf.Log(distance, 2);
    20.  
    21.             }
    Where in this example the logarithmic operation doesn't really do anything, but I also tried it some other way, and in both cases got an error -
    transform.position assign attempt for 'Main Camera' is not valid. Input position is { Infinity, Infinity, NaN }.
    Which I'm guessing wasn't what you were going for :)

    Anyway, I think I do mean it to be logarithmic? Like the change curve on the AudioSource..

    Or maybe like an easing function, like this, like ease out cubic -


    rate = 1 - Math.pow(1 - x, 3)

    I'll give this one a shot and update you, I'm really bad at math :)))

    **EDIT

    I tried implementing it like so -

    Code (CSharp):
    1. if (Input.GetAxis("Mouse ScrollWheel") != 0)
    2.             {
    3.                 var distance = Vector3.Distance(transform.position, globe.position);
    4.  
    5.                 float changedSensetivity = Mathf.Pow(1 - distance, 3);
    6.  
    7.                 transform.position += transform.forward * changedSensetivity;
    8.             }
    Got the camera flying out of unity's bounds. Not the way to use it lol
     
    Last edited: Feb 21, 2023
  8. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    Oops, that was just me forgetting to remove the declaration after deciding to declare the variables up top. You can just ignore the extra declaration.

    zoomLevel is not a distance. It just keeps track of how much the player has asked to zoom in or out.

    Your code is repeatedly multiplying the position by a very big number, which is going to fly way out of bounds almost instantly!

    Also, there's no need to re-compute the zoomLevel like you're doing -- that's just an example of how you can pick the right zoom level to get a desired distance. That is only necessary if you want to, say, set the camera to be exactly 30 meters above the ground.

    You should have something like this:

    Code (CSharp):
    1. if (zoomIn) {
    2.   zoomLevel -= 0.1f;
    3. } else if (zoomOut) {
    4.   zoomLevel += 0.1f;
    5. }
    6.  
    7. float distance = Mathf.Pow(2, zoomLevel);
    8. transform.position = player.transform.position + Vector3.up * distance;
    This would be a camera centered on the player. The camera would move further and further upwards as you zoom out.
     
    SassyPantsy likes this.
  9. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    136
    Alright so this is actually almost perfect, except for the problem that it doesn't take the current camera position into account. Since it does the math 'backwards' as you said, it takes the globe's position into account when it calculates the camera's position. My scene is built in a way that the camera's position is set in meters above terrain. (its really unity units, but its important because of the different objects' sizes in the scene).

    That means that sometimes my camera will get need to be in 40,000 meters above ground level, but an input from the user will send it to - lets say - 14 'zoom level', which equates to 52,456 meters.
    In any way you call them, these are different scaling methods, and they don't take each other into account.

    I've tried playing around with this zoom level value, like subtracting the camera's current position from it, but that doesn't work either, and I'm kinda stumped
     
  10. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    Just figure out ground level, then add it to the value given by raising 2 to the power of the zoom level to get the final camera altitude. If you need to go backwards, subtract the ground level from the camera altitude before taking the log2 to get a zoom level.
     
    SassyPantsy likes this.
  11. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    136
    Ok, I'm going to try and mess around with this and post the final script when I'm done. This math is still kind of complicated to me but I think I'll manage.

    Thanks !
     
  12. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    You can make things a little prettier by using properties:

    Code (CSharp):
    1. public float zoomLevel;
    2.  
    3. public float GroundLevel
    4. {
    5.   get => 1; // fill this in with whatever you do to decide how high the ground is
    6. }
    7.  
    8. public float CameraHeight
    9. {
    10.   get => GroundLevel + Mathf.Pow(zoomLevel, 2);
    11.   set => zoomLevel = Mathf.Log(value - GroundLevel, 2);
    12. }