Getting Started With Core Haptics
In this Core Haptics tutorial, you’ll learn how to create and play haptic patterns, synchronize audio with haptic events and create dynamic haptic patterns that respond to external stimuli. By Andrew Tetlaw.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started With Core Haptics
35 mins
- Getting Started
- Adding Your First Haptic Experience
- Exploring the Events That Make up the Pattern
- Managing Energy Usage
- Designing a Haptic Experience
- Feeding the Crocodile
- Playing Different Patterns
- Syncing Audio Events
- Setting a Reset Handler
- Ramping Intensity Up and Down — Pineapple Splashdown
- Controlling Intensity With a Parameter Curve
- Updating Pattern Parameters in Real Time
- Making the Player Dynamic
- Where to Go From Here?
Controlling Intensity With a Parameter Curve
Since you’re getting creative with these haptic experiences, why not improve the snip haptic pattern for a more satisfying *SsssNIP* feel? A haptic pattern can also accept parameters that apply to the pattern as a whole.
First, update the slice
event property like so:
let slice = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
],
relativeTime: 0,
duration: 0.5)
This increases the intensity, sharpness and duration.
Next, create a new type of parameter — a CHHapticParameterCurve
— after the two slice
and snip
events in slicePattern()
:
let curve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
.init(relativeTime: 0, value: 0.2),
.init(relativeTime: 0.08, value: 1.0),
.init(relativeTime: 0.24, value: 0.2),
.init(relativeTime: 0.34, value: 0.6),
.init(relativeTime: 0.5, value: 0)
],
relativeTime: 0)
A parameter curve is similar to an animation curve, and the control points are like animation keyframes.
This parameter curve has the ID .hapticIntensityControl
, which acts as a multiplier to all event intensity values across the pattern. Because it’s a curve, the parameter interpolates smoothly between the control points as the pattern plays.
For example, the first control point is at time 0
with value 0.2
, which means that it multiplies all event intensity values by 0.2
at the start. By 0.08
seconds, it will have ramped up smoothly to a multiplier of 1.0
. By 0.24
seconds, it will have ramped smoothly back down to 0.2
, and so on.
Here’s how it looks:
To use the parameter curve, you need to initialize the pattern object using CHHapticPattern(events:parameterCurves:)
.
Still in Haptics.swift, replace the return statement in slicePattern()
with the following:
return try CHHapticPattern(events: events, parameterCurves: [curve])
This creates the haptic pattern using the curve you specified.
Build and run to experience your new dynamic haptic experience.
Updating Pattern Parameters in Real Time
If you think dynamic parameter curves are cool, wait until you see Core Haptics’ heavy hitter: CHHapticAdvancedPatternPlayer
. This is a pattern player that you can control while it’s playing.
There’s something important missing in your game. When the player swishes a finger across the screen, you can see the particle effects, but where’s the feel of the blade slicing through the air? With a CHHapticAdvancedPatternPlayer
, you can even control the intensity in real time so it ramps up stronger the faster the player’s finger moves.
First, add a property to HapticManager
to hold a reference to your new player:
var swishPlayer: CHHapticAdvancedPatternPlayer?
Next, add a method to create the player:
func createSwishPlayer() {
let swish = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0,
duration: 60)
do {
let pattern = try CHHapticPattern(events: [swish], parameters: [])
swishPlayer = try hapticEngine.makeAdvancedPlayer(with: pattern)
} catch let error {
print("Swish player error: \(error)")
}
}
It’s a simple pattern: a single continuous event with a long duration. You create the player by calling makeAdvancedPlayer(with:)
.
Next, in HapticManager
, add the following line to setupResources()
:
createSwishPlayer()
By doing this, you create the swish player whenever you call setupResources()
, both in initializer and the haptic engine reset handler. Player references also reset when the engine resets.
Next, you need to add a method to start the player. Add the following at the end of HapticManager
:
func startSwishPlayer() {
do {
try hapticEngine.start()
try swishPlayer?.start(atTime: CHHapticTimeImmediate)
} catch {
print("Swish player start error: \(error)")
}
}
startSwishPlayer()
first calls hapticEngine.start()
just in case the engine has stopped. Then, it calls the pattern player’s start(atTime:)
with CHHapticTimeImmediate
, so the player starts immediately.
You’ll also need to add a method to stop the player. Add this at the end of HapticManager
as well:
func stopSwishPlayer() {
do {
try swishPlayer?.stop(atTime: CHHapticTimeImmediate)
} catch {
print("Swish player stop error: \(error)")
}
}
Here you try to stop the pattern player as soon as you can, almost immediately, by passing CHHapticTimeImmediate
.
Return to GameScene.swift, find touchesBegan(_:with:)
and add the following line to start the pattern player when the player begins swiping:
hapticManager?.startSwishPlayer()
Next, find touchesEnded(_:with:)
and add the following line to stop the pattern player when the player’s swipe ends:
hapticManager?.stopSwishPlayer()
Build and run and you should experience the player starting and stopping as you move your finger around the screen.
Now, it’s time to add the magic!
Making the Player Dynamic
Next, you’ll make the swish’s intensity depend on the user’s movements. Add the following method to HapticManager
:
// 1
func updateSwishPlayer(intensity: Float) {
// 2
let intensity = CHHapticDynamicParameter(
parameterID: .hapticIntensityControl,
value: intensity,
relativeTime: 0)
do {
// 3
try swishPlayer?.sendParameters([intensity], atTime: CHHapticTimeImmediate)
} catch let error {
print("Swish player dynamic update error: \(error)")
}
}
- Your new
updateSwishPlayer(intensity:)
takes a single float argument: a value between 0 and 1. - Use that value to create a
CHHapticDynamicParameter
with the ID.hapticIntensityControl
. This parameter functions much like the previous parameter curve you created, acting as a multiplier to all the event intensity values in the pattern. Unlike the curve, this is a one-time change. - Send the dynamic parameter to the player for it to apply immediately to the pattern that’s playing.
Return to GameScene.swift and add the following to touchesMoved(_:with:)
:
let distance = CGVector(
dx: abs(startPoint.x - endPoint.x),
dy: abs(startPoint.y - endPoint.y))
let distanceRatio = CGVector(
dx: distance.dx / size.width,
dy: distance.dy / size.height)
let intensity = Float(max(distanceRatio.dx, distanceRatio.dy)) * 100
hapticManager?.updateSwishPlayer(intensity: intensity)
Every time the system calls touchesMoved(_:with:)
, you update your dynamic player’s intensity control value. You calculate the intensity using a simple algorithm: The more you move from the previous touch, the higher the intensity value will be.
Build and run. Snipping the vine should now feel like you’re a Jedi knight wielding a light saber!
Where to Go From Here?
You can download the completed project using the Download Materials button at the top or bottom of this tutorial.
You’ve transformed Snip The Vine from an amusing distraction to a whole new immersive experience! In the field of physics-based-cutting-things-down-so-they-drop-on-animals games on the App Store, it’ll beat them all, hands down.
If you can believe it, you’ve only touched on what Core Haptics can achieve. There’s so much more to explore.
Watch the Apple sessions from WWDC 2019:
- Introducing Core Haptics
- Expanding the Sensory Experience with Core Haptics. This is definitely required viewing.
Have a look through the Core Haptics documentation. There are a few sample Xcode projects in there to download as well.
Don’t forget about the Apple Human Interface Guidelines page on Haptics and the tips in Designing with Haptics.
You may also want to read about Apple Haptic and Audio Pattern (AHAP) file format.
I hope you enjoyed this Core Haptics tutorial. If you have any questions or comments, please join the forum discussion below.