Usage looks something like this:
var U = MajicUnits; // Spelled funny to match my initials: MaJiC
// How fast the ship spins when you press the "right" arrow key:
// half a revolution per second.
rotationSpeed = U.radians( Math.PI ).per.second;
currentRotation = U.radians( Math.PI/4 ); // eighth-turn
// Acceleration when the user presses the "up" arrow key:
thrustAmount = U.pixels( 6 ).per.second.per.second;
currentVelocity = U.pixels( 0 ).per.second;
var nowTime = U.now(); // Equivalent to nowTime = U.seconds( (new Date).valueOf() );
var delta = nowTime.sub(lastTime); // How much time elapsed since previous frame
delta.as( U.millisecond ); // -> e.g. 40
delta.as( U.second ); // -> e.g. 0.04
var frameRate = U.frames( 50 ).per.second; // 50 f/s
var secsPerFrame = frameRate.inverse; // 0.02 s/f (20 milliseconds-per-frame)
currentVelocity = currentVelocity.add(
thrustAmount.mul( delta ) // convert px/s^2 to px/s, scaling for one frame
currentRotation = currentRotation.add(
rotationSpeed.mul( delta ) // convert rads/s to rads, scaling for one frame
rotationSpeed.as( U.radian.per.second ); // returns the number value
rotationSpeed.as( U.radian ); // throws an exception because you're wrong about the unit type
rotationSpeed.valueOf(); // Error. Don't try using as a plain number.
rotationSpeed.toString(); // -> "3.141592653589793 rad/s"
rotationSpeed.valueOf(); // Okay. .relax() enables straight use as a number.
var r = rotationSpeed.mul( U.seconds(1) ); // X rad/s * Y s = XY rad
r = r.div( U.radians( Math.PI / 2 ) ); // XY rad / Z rad = XY/Z units
r.valueOf(); // Okay! Value is in base "unit", so can be treated as a normal number (without .relax()).
U.addUnitType("action", "actions", "act"); // Add a new unit type, unassociated with others
U.addUnitType("minute", "minutes", U.seconds(60)); // Add a new unit type, exactly equivalent to 60 seconds
var bar = U.actions( 120 ).per.minute;
bar.toString(); // "2 act/s"
The reason for this little library is that I’ve frequently run into problems thinking in the wrong units in my games. One variable might hold a length of time in seconds, another in milliseconds (from new Date()), and I’ll try to use them together, forgetting to convert between them. In MajicUnits, they’re all represented in seconds under the hood, and I can specify what unit I want when pulling them back out.
Or I often forget to scale a velocity or acceleration by the inter-frame time before adding to current state, resulting in a full second’s acceleration taking place in a single frame. Using MajicUnits, I’ll see an exception thrown if I forget to multiply by the frame delta.
The library’s pretty crude at the moment: I’ve coded exactly as much as I need for the moment. There are places you could feed it bad input and break its assumptions. It automatically creates plural versions of your added units, adding ‘es’ if it ends in ‘s’, otherwise adding ‘s’. That obviously wouldn’t work with ‘activity’ (-> ‘activitys’), and can’t handle exceptions like ‘forum’ -> ‘fora’ or something. Rather than have an automatic pluralizer, I should be having the user explicitly specify when they register the unit… (All of the above is no longer accurate.)
A bigger problem is that when an exception is thrown I can’t currently just find what line of code went wrong. I throw an Error object (which includes file and line number info), but it points at the place inside the library that threw the exception, rather than at the failing invocation that caused the exception. Most implementations of Error provide some sort of stacktrace, so perhaps I can generate one and then turn it into a string that includes that stacktrace info… because when it comes out on my Firefox console I don’t get the stacktrace currently. Anyway.
You can see the current implementation (as of this writing) here (UPDATED), if you’re curious
and/or masochistic (it does some magic with JS prototypes to do its job, and lacks any useful documentation/comments, though I plan to add some).
It’s written as one part of this game Gate Arena that I’ve been working on lately, but am rewriting to emphasize a more declarative style of expressing objects and logic and their relationships, to increase code reuse and the ease of adding new enemy types, etc. I plan to reuse the resulting engine for further games, increasing my flexibility for participation in game jams and whatnot.