NumericUpDown in Windows Forms and a small acceleration hack...
Since I've been chatting about acceleration, I figured I'd point out how and where these types of methods could be used to make your life simpler and how sometimes a basic hack will do. The first exercise is going to be taking a NumericUpDown and setting the maximum value to somewhere around 1 million. Next, run the app, press and hold the up button. What you'll notice is that the longer you hold it the faster the increment gets. The problem you'll quickly find is that reaching 1 million is going to be hard. You could mess with the interval, but then users won't be able to set values in between the interval without editing the text of the control directly.
How does the NumericUpDown achieve acceleraton? First, they use a timer while the user holds the mouse button down. The timer is set to 500 milliseconds initially and is then modified by 0.7 and truncated (well, they appear to multiply by 7 then divide by 10). As you can see the timer messages will get more and more frequent. Each time a message is processed the control is incremented or decremented by the current interval. This is pretty much just an acceleration hack. In fact, the fastest the control can move is the speed of a propagating timer event, which is something like once every 50ms and it will always move at some constant velocity once it achieves the maximum speed of a timer.
Could we make the control a bit more robust? Sure, we could fix the timer at something like a quarater of a second, and then run the physics to figure out how many spaces have been sped past. Our initial velocity is 1, since we'll update the control by at least once each time. Our acceleration might be 4items/s^2. The Windows Forms control gets up to speed pretty fast. In fact, it hits the 20 increment (1000 / 50) point around 1 and a half seconds. Our new acceleration function would hit 20 items per second after 5 seconds... That makes it a bit slower to start, but much faster later. The designer for the control would allow you to set the actual physics though and you could modify exactly how fast the control would go. For instance, the acceleration itself might be a function. You could even have the acceleration ramps geared towards how the user operates with the control. Given enough user input on the spinner and seeing how often the user has to correct or how long they have to depress a button to get a desired result you can try to optimize the entire process.
I also want to talk about another popular style of control that you often see on TV, but rarely get a chance to experience on the computer. The counting label is constantly used to graphically animate some score or value into existence. Before the control starts all the fields are 0'ed out of course. Then the control operates for some fixed period of time, or sometimes not, and eventually settles into a final result. There are plenty of hacky ways to do this:
- Rotate all spinners simultaneously, stop each digit place on the appropriate digit. This is a reel approach and isn't a counter.
- Add a fixed amount at each time step. This is the most popular and produces those 10-15 second waits when you get a bitchin score.
- Scale the fixed amount to to the operation time. Another popular approach resulting in a fixed time to score.
If you really want to be cool, you have to use acceleration and deceleration. You want to haul butt to some point in the count-down and then you want to ease off and decelerate the final digits into place. You might set the acceleration period to something like 4 seconds, and the deceleration period to 2 seconds. The total 6 second animation would be very pleasing overall.
using System;
public class AccelerationModeler {
private static void Main(string[] args) {
int previous = 0;
for(int i = 0; i < 25; i++) {
int currentScore = GetScore(1000000, 4f, 2f, 0.25f * i);
Console.WriteLine("Current: {0}, Delta: {1}", currentScore, currentScore - previous);
previous = currentScore;
}
}
private static int GetScore(int totalScore, float tAccel, float tDecel, float tCurrent) {
// We want the acceleration period to cover a distance proportional to it's time.
float accelDistance = totalScore * (tAccel / (tAccel + tDecel));
float acceleration = (2*accelDistance)/(tAccel*tAccel);
float finalVelocity = acceleration * tAccel;
// Now, we need to slow down system
float decelDistance = totalScore - accelDistance;
float deceleration = -(finalVelocity / tDecel);
if ( tCurrent > (tAccel + tDecel) ) {
return totalScore;
} else if ( tCurrent < tAccel ) {
return (int) (0.5*acceleration*(tCurrent*tCurrent));
} else {
return (int) (accelDistance + (finalVelocity * (tCurrent - tAccel) + (0.5*deceleration*((tCurrent - tAccel)*(tCurrent - tAccel)))));
}
}
}