First, let’s look at the underlying engine hum. When the user squeezes their controller grips we’ll create a haptic channel on our controller instance called “engine-rumble” and set its intensity to 20% like so:

controller.setVibe( 'engine-rumble' ).set( 0.2 )

The act of selecting a haptic channel with setVibe() automatically creates that channel if it does not already exist. The name of that channel is whatever string you pass to setVibe() . Notice how duration is not being specified. VRController will rumble at that intensity forever—or until you issue a new intensity command. That’s as easy as selecting the same haptic channel again by name:

controller.setVibe( 'engine-rumble' ).set( 0 )

We can also create a queue of haptic channel commands. Let’s say when you first engage the engine there’s a moment of intense shuddering before it settles down into its normal hum. Perhaps that initial shudder lasts a second and a half. Here’s how we might describe that using haptic channels:

controller.setVibe( 'engine-rumble' )

.set( 0.8 )

.wait( 1500 )

.set( 0.2 )

And just for fun, perhaps it takes an eye-blink of a moment after releasing the controller grips for the engine to disengage:

controller.setVibe( 'engine-rumble' ).wait( 250 ).set( 0 )

Under the hood, VRController is keeping track of time via window.performance.now() to know when in the future each change in vibration intensity ought to take place. Important note: Selecting the haptic channel will automatically erase its queue of future events. Why might this be desirable? Imagine the haptic behavior we described above. Now imagine a user engages the engine by squeezing their controller grips, but after one second decides to release the grips. If the event queue was not automatically scrubbed what they might experience is 1.25 seconds of 80% vibration intensity, followed by a quarter second of haptic silence, then followed by 20% vibration intensity that lasts into perpetuity. That wouldn’t feel like an engine kicking on, then shutting down. It would feel like a mistake.

Multi-channel

The above pattern of selecting a haptic channel and then applying set or wait is convenient, but using multiple channels is where VRController’s approach to haptics really shines.

Let’s say you’ve got that engine rumbling and now the user is pulling the trigger on their controller to fire a photon bolt. How do you plan to keep track of where the engine’s at in terms of its vibration intensity queue, and also apply the recoil of the cannon? And what about the rotation of the cannon head—which we want to spin at full intensity immediately, then wind down over time? With VRController you don’t have to worry about it.

Your engine’s already rumbling. Here’s what to add to your trigger-press routine:

controller.setVibe( 'cannon-recoil' )

.set( 0.8 )

.wait( 100 )

.set( 0 ) controller.setVibe( 'cannon-rotation' )

.set( 0.2 )

And for the wind-down you might add something like this to your trigger-release routine:

controller.setVibe( 'cannon-rotation' )

.wait( 500 ).set( 0.10 )

.wait( 500 ).set( 0.05 )

.wait( 500 ).set( 0.00 )

You could of course make a much more granular wind-down, but I’ve found even the above coarse degree of detail gets me close enough to the haptic expression I’m looking for.