I am making a rhythm game in Unity. I have been using Transform.Translate for moving images on a canvas as the music notes. However, I cannot consistently use this method on different screen widths. The notes move the same amount each frame.

I want to use interpolation for my music notes. I want to have the notes reach the center of the canvas at the same time despite the width of the screen. How can I write such a script?


1 Answer 1


If you want to linearly interpolate between two values, the jargon for that is "lerp". The basic idea is you pass a lerp function two values and a number between 0 and 1 that specifies how far between the two you want the current position to be. In this way you can control when a moving object hits the target by controlling when the value between 0 and 1 hits 1.

Here's an official Unity article on lerping.

After adapting examples from there to your particular use case where at least two notes need to hit the same place at the same time I get this:

Vector3 noteAStart = new Vector3 (...); 
Vector3 noteBStart = new Vector3 (...); // choose these however you like
Vector3 center = new Vector3 (...); // calculate the center of the screen here.

Vector3 noteACurrent = Vector3.Lerp (noteAStart, center, progress);
Vector3 noteBCurrent = Vector3.Lerp (noteBStart, center, progress);

You can then set your notes' positions directly to noteACurrent and noteBCurrent, which will be change based on the current value of progress. So, if we can figure out what to set progress to each frame, then we're set. But how do we do that?

First, we need to know how long we want the animation to take. Let's assume we want it to take half a second.

Well, assuming the game runs at exactly 60 frames per second, the most straight-forward way would be to just increment progress by 1/30th each time, so we get to 1 after exactly 30 frames.

const float PROGRESS_PER_UPDATE = 1.0 / 30.0;

However, Update does not necessarily get called exactly 60 frames per second, depending on the system load. Depending on your timing needs the occasional lag may be both noticeable and highly undesirable.

One way to deal with this is to factor in the time that the last frame took, (the Time.deltaTime,) into the interpolation, by doing something like this:

progress += Time.deltaTime / DESIRED_DURATION_IN_SECONDS;

But this approach can produce undesirable results if Time.deltaTime is ever significantly larger than it usually is, depending on what other code is triggered by progress reaching or exceeding 1.0. You also may want to clamp progress to be at most 1.0, depending on how far things move in a frame.

Another method which Unity has built-in for you, is to use FixedUpdate instead which attempts to provide a more consistent time step, allowing you to just use a constant amount of progress per frame. It is still true that ultimately a system with sufficiently heavy load, (say several other heavy programs are running in the background besides just your game,) will need to skip some frames, and/or call the method at a different frequency, some of the time. In practice this is likely not an issue.

If the way Unity deals with the an overloaded machine ever does become an issue, it would be possible to keep track of the actual elapsed time yourself, and react accordingly. You could even set up your own loop that handles heavy load the way you want to, but this is much more complicated, particularly making such a loop play nice with Unity's expectations.

Worked example regarding screen sizes

Let's assume that you've got a progress variable which is changing at the desired rate, based on one of the above methods.

At some point your game will need to read the screen dimensions from The Screen class.

You can get the center position of the screen like so:

var center = new Vector3(Screen.width * 0.5, Screen.height * 0.5, 0.0);

(Since most screens have even dimensions, there is no center pixel. This gives a pixel that is as close as possible to the center. You can add 0.5 as appropriate to talk about the actual center of the screen if desired, but this is probably unnecessary complication.)

I don't know how you have been setting your notes' positions so far, but if you want you can set them based on the screen size as well, by multiplying by the appropriate dimension. For example:

new Vector3(Screen.width * 0.125, Screen.height * 0.125, 0.0)

creates a position near one corner of the screen, and

new Vector3(Screen.width * 0.875, Screen.height * 0.875, 0.0)

creates one near the opposite corner. Here's the example from above with all the ... parts filled in:

Vector3 noteAStart = new Vector3 (Screen.width * 0.125, Screen.height * 0.125, 0.0); 
Vector3 noteBStart = new Vector3 (Screen.width * 0.875, Screen.height * 0.875, 0.0); // choose these however you like
Vector3 center = new Vector3 (Screen.width * 0.5, Screen.height * 0.5, 0.0); // calculate the center of the screen here.

Vector3 noteACurrent = Vector3.Lerp (noteAStart, center, progress);
Vector3 noteBCurrent = Vector3.Lerp (noteBStart, center, progress);
  • \$\begingroup\$ Hi, thanks for the tip. But doesn't lerping slow down once it gets closer to the object? How do I make the notes reach a consistent speed? \$\endgroup\$
    – SadBread
    Jul 2, 2020 at 5:36
  • \$\begingroup\$ @SadBread I've edited the answer, does the current version address your concern? \$\endgroup\$
    – Ryan1729
    Jul 2, 2020 at 12:33
  • \$\begingroup\$ Hi, Thanks for the edit. But, I'm looking for a method to have the notes reach the center of the canvas despite screen lengths \$\endgroup\$
    – SadBread
    Jul 7, 2020 at 19:32
  • \$\begingroup\$ Okay, I've added a worked example, which includes how to calculate the center position of the screen explicitly. \$\endgroup\$
    – Ryan1729
    Jul 8, 2020 at 11:11
  • \$\begingroup\$ Okay, Thank you so MUCH! It works like a charm. \$\endgroup\$
    – SadBread
    Jul 10, 2020 at 5:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .