Speed lines tutorial

SpeedlinesScreen-wide effects can give a sense of the player ‘being there’ that things only visible out in front of them lack. That trigger to the player’s peripheral vision is a strong cue, even if the effect itself is fairly mild. Think of things like Skyrim’s ‘wounded haze’ or faint blur around the corners of your visual field. For this particular tutorial, I’m going to talk about a speed line effect I implemented for Travelogue recently. It looks pretty neat and its actually very simple.

The effect is basically just a bunch of little shaded triangular segments. Because this is animated, and animated fast, I’m using far fewer of them than you’d see in print media. The individual lines will jump around a lot and fill visual space despite seeming pretty sparse in this screenshot.

There are a couple subtle things that help it look better. If they’re a fixed translucency, they meet in the middle, which looks pretty bad. There’s two ways to deal with this – move the tips out to a circle of some radius, or use a radial gradient to make them fade towards the center. I went with the radial gradient style because I thought having that shading would also make them look a bit nicer away from that center as well.

I played around with a few animation types – you really don’t want static lines here, so your options are basically lines that flicker around randomly or lines that move. I felt that the control given by moving lines was worth the extra complexity, since then you can also use the same code for rays of light emitting from an object, different speeds, etc.

So we have a lot of knobs on this thing already. For ease of use, lets pack these into a JSON object.

var params = {
  color1: "rgba(0,0,0,0)", // Color in the center of the gradient
  color2: "rgba(0,0,0,0.5)", // Color on the outside - black, but shaded at 50%
  count: 20, // How many lines we want
  speed: 25, // How fast we want them to flit around in radians/second
  duration: 1, // How long we want the effect to play for
  innerRad: 32, // Inner transparent radius
  outerRad: 256, // radius where it goes completely opaque
  thickness: 0.025 // Angular thickness of the speed lines in radians
};

Now, for the Speedlines object itself. I’m going to try to avoid getting bogged down in implementation details here, so I won’t bother talking too much about how to count out the duration, etc. Instead, lets skip to the important bits.

First, we need to figure out what angles the speedlines are going to be at, and how fast they’re going. We’ll want to store that information for animating them. Lets create an array for that, and fill it with random angles and speeds consistent with the ‘speed’ scale we’ve chosen:

var thetas=[], omegas=[]; // thetas are the angles, omegas are the speeds
for (var i=0;i<params.count;i++) {
   thetas[i]=Math.PI*2*Math.random();
   omegas[i]=(2*Math.random()-1)*params.speed;
}

Now lets go on to the actual rendering of these things. I’m going to assume that the following bit of code has access to all the variables up till now, as well as a variable ‘time’ that just counts out the time and a drawing context to the canvas ‘ctx’. What I’m going to do here is create a radial gradient with our colors and then draw a bunch of triangular segments that will be filled in with the gradient.

/* width and height here are the canvas width and height */
var grd = ctx.createRadialGradient(width/2, height/2, innerRad, width/2, height/2, outerRad); 
grd.addColorStop(0, params.color1); // setting up the gradient colors
grd.addColorStop(1, params.color2);

ctx.fillStyle=grd; // use this to fill the triangular slices

for (var i=0;i<params.count;i++) {
/* here we're finding the current position of the line by taking the initial angle and adding speed*time */
  var theta = thetas[i] + time*omegas[i]; 

  ctx.beginPath();
/* We'll move to a point outside the screen at the angle theta, connect to the center of the screen, and then connect to another point outside at theta+thickness */
  ctx.moveTo(width/2+width*Math.cos(theta), 
             height/2+height*Math.sin(theta)); 
  ctx.lineTo(width/2, height/2); 
  ctx.moveTo(width/2+width*Math.cos(theta+params.thickness), 
             height/2+height*Math.sin(theta+params.thickness));

  ctx.closePath();
  ctx.fill(); 
}

And that’s basically it for the code. The rest is choosing the right parameters. I found that you want a lot of contrast with your background – white on tan for example makes it look like there’s something flickering at the corner of your eye rather than a big punchy effect. I thought yellow was a ‘fast’ color but it looks awful against the paper backdrops in Travelogue.

Speeds above about 20 basically look nearly random, but with some sort of hint of order in the motion. At a speed of 10, its pretty clear that the lines are rotating around rather than jumping around. Between those two is a kind of unpleasant-looking zone that looks like the things are spinning around but getting strobed by the monitor’s refresh rate. This may vary based on refresh rates and screen size – smaller segments of the display will probably be more forgiving than full-screen effects where the eye sees continuous motion at the center and discontinuous motion at the outside.

glowy-lines

You can do a lot on top of this to make it fancier. For example, you could draw two set of these triangles at slightly different time values with different alphas to get a motion blur effect and maybe help a bit with the ‘screen refresh rate’ issues. You could also stroke the triangles in a different color, or have two sets of lines with different colors. Another effect I think would look quite nice in the right situations would be to superimpose the speed lines on a second radial gradient, but with a different outer-radius. I think to really make that work though,  you want to have a lot of lines or a few much thicker ones.

Another neat trick is to add some screen animation – have the screen do a tiny zoom while this is playing, and then relax back.

Leave a comment