版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.08.07 星期五 |
前言
Core Haptics
是iOS13
的新的SDK,接下来几篇我们就一起看一下这个专题。感兴趣的可以看下面几篇文章。
1. Core Haptics框架详细解析(一) —— 基本概览(一)
开始
首先看下主要内容:
在本
Core Haptics
教程中,您将学习如何创建和播放触觉模式,将音频与触觉事件同步以及如何创建可响应外部刺激的动态触觉模式。内容来自翻译。
下面看下写作环境:
Swift 5, iOS 13, Xcode 11
接着就是正文啦
Apple
在iOS 13
中引入了Core Haptics
。这是一个新的API,可以生成自定义的高分辨率触觉和音频反馈,使振动和隆隆声听起来像老式的头。 准备将自己沉浸在触觉体验的世界中。
在本教程中,您将通过新的触觉(haptic)
体验来增强应用程序。 您将学习如何:
- 1) 创建并播放触觉模式。
- 2) 将音频与触觉事件同步。
- 3) 创建动态触觉模式,这些模式会响应外部刺激而发生变化。
注意:本中级教程假定您可以轻松使用Xcode构建iOS应用并编写Swift。 您还需要一台运行
iOS 13
并支持触觉反馈的设备。 本教程使用教程 How To Make a Game Like Cut the Rope With SpriteKit中的游戏作为开始项目。 如果您想了解SpriteKit
的介绍,请先阅读该教程。
打开起始项目并运行,你会看到游戏:
您拥有可爱的图形,物理模拟和动画流畅,声音效果将玩家置于丛林中。但是您会强烈感觉到缺少某些东西。切菠萝时还有就是落入水中或者鳄鱼吃的时候没有声音。
是时候使用Core Haptics
了!
Adding Your First Haptic Experience
最好的开始方法是快速进行简单的触觉体验并对其进行测试。您的第一次触觉体验将为玩家切开藤本时的感觉提供一些声音效果。
创建触觉体验的标准过程是:
- 1) 检查设备兼容性。
- 2) 创建一个触觉引擎对象。
- 3) 创建触觉事件模式。
- 4) 做一个模式玩家。
- 5) 启动触觉
(haptic)
引擎。 - 6) 播放模式。
- 7) 停止触觉
(haptic)
引擎。
为避免向GameScene.swift
添加更多代码,请展开SnipTheVine ▸ Classes
,然后查找并打开Haptics.swift
。这是一个空文件供您使用。添加以下内容:
import CoreHaptics
class HapticManager {
// 1
let hapticEngine: CHHapticEngine
// 2
init?() {
// 3
let hapticCapability = CHHapticEngine.capabilitiesForHardware()
guard hapticCapability.supportsHaptics else {
return nil
}
// 4
do {
hapticEngine = try CHHapticEngine()
} catch let error {
print("Haptic engine Creation Error: \(error)")
return nil
}
}
}
此代码的作用如下:
- 1)
hapticEngine
拥有对CHHapticEngine
的引用。 - 2) 初始化程序会失败,因此您可以检查游戏场景中的初始化程序是否为
nil
,并使用它来指示触觉不可用。 - 3) 在初始化程序中要做的第一件事是检查触觉是否可用。 调用
CHHapticEngine.capabilitiesForHardware()
获得一个对象,您可以通过简单检查supportsHaptics
来测试该对象。 - 4) 最后,创建一个引擎对象。
CHHapticEngine
初始化程序可以抛出异常,因此您需要将其包装在do / catch
块中,如果引发错误,则返回nil
。
注意:导致触觉在设备上不可用的原因有很多,因此您需要为许多
API
使用do-catch
块。 这也意味着您需要确保对任何触觉体验都有一个fallback
。
现在,继续前进,直到可以尽快测试您的第一个触觉为止。
仍在Haptics.swift
中,将此扩展添加到类中:
extension HapticManager {
private func slicePattern() throws -> CHHapticPattern {
let slice = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.35),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.25)
],
relativeTime: 0,
duration: 0.25)
let snip = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.08)
return try CHHapticPattern(events: [slice, snip], parameters: [])
}
}
slicePattern()
返回您的第一个Core Haptics
模式。 它创建两个触觉事件和使用它们的触觉模式。 您将很快探索事件和模式的详细信息,但是现在,继续前进吧!
最后,向HapticManager
添加方法以播放模式:
func playSlice() {
do {
// 1
let pattern = try slicePattern()
// 2
try hapticEngine.start()
// 3
let player = try hapticEngine.makePlayer(with: pattern)
// 4
try player.start(atTime: CHHapticTimeImmediate)
// 5
hapticEngine.notifyWhenPlayersFinished { _ in
return .stopEngine
}
} catch {
print("Failed to play slice: \(error)")
}
}
此代码执行以下操作:
- 1) 顾名思义,您可以调用
slicePattern()
来获取您的触觉切片模式(haptic slice pattern)
。 - 2) 然后,您在触觉引擎上调用
start()
。 - 3) 用您的切片模式制作一个模式玩家。
- 4) 接下来,播放模式,并使用
CHHapticTimeImmediate
调用start(atTime :)
立即播放它。 - 5) 调用
notifyWhenPlayersFinished(finishedHandler :)
并返回.stopEngine
以在引擎完成播放后停止引擎。
好的,您快到了,但是您需要对GameScene.swift
进行一些更新。 首先打开文件,并在类顶部添加以下属性:
private var hapticManager: HapticManager?
然后将以下内容添加到didMove(to :)
的顶部:
hapticManager = HapticManager()
在checkIfVineCut(withBody :)
中,将此行添加到运行切片声音操作的行上方:
hapticManager?.playSlice()
建立并运行并切成薄片的藤蔓! 您能感觉到游戏玩法已经改善吗?
Exploring the Events That Make up the Pattern
好的,一次就很多了。 现在,花点时间深入了解您在这里所做的事情。
着眼于模式,您可以看到它由事件组成:
let slice = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.35),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.25)
],
relativeTime: 0,
duration: 0.5)
let snip = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.08)
由CHHapticEvent
表示的触觉事件可以是以下两种类型之一:.hapticTransient
或.hapticContinuous
。瞬态事件是瞬时的,就像是一个鼓声,而连续事件则是一个隆隆声。您还可以创建音频事件,但以后会更多。
每个事件都有两个属性,这些属性控制CHHapticEventParameter
表示的触觉。每个都有一个参数ID
和一个介于0
和1
之间的值。
ID
为.hapticIntensity
的参数表示感觉的强度;值越高,则值越强。 ID
为.hapticSharpness
的参数表示一种物理质量,该质量在比例尺的高端具有精确的机械感。在低端,它具有更圆润的有机感觉。
事件的relativeTime
表示从事件发生的模式开始算起的秒数。对于连续事件,还有一个duration
属性来定义事件进行的时间。
您的第一个触觉效果是立即开始低0.25
秒钟的隆隆声,并在开始时0.08
秒发出尖锐而强烈的跳动。您可以在图中表示模式:
接下来,您将学习如何更有效地使用触觉事件。
Managing Energy Usage
Core Haptics
在您的iOS设备中使用Taptic Engine
硬件。与iOS设备上的所有硬件组件一样,您只需要在必要时将其激活,以免浪费能源。何时运行引擎取决于您的应用程序需求,但以下是一些准则:
- 如果您的模式立即播放一个瞬态事件,请调用引擎
start()
,播放该模式,然后调用stop(completionHandler :)
。如Apple
的触觉模式文档Apple’s haptic pattern documentation中所述,这是最节能的方式。 - 如果您正在播放更长或更复杂的模式,请像已经完成的那样,调用
notifyWhenPlayersFinished(finishedHandler :)
并返回.stopEngine
。 - 但是,如果您的应用程序像
Snip The Vine
一样连续播放或重复播放多个模式,则需要引擎始终运行。
现在,您将了解如何确保引擎继续运行。
首先,从playSlice()
中删除以下对notifyWhenPlayersFinished(finishedHandler :)
的调用:
hapticEngine.notifyWhenPlayersFinished { error in
return .stopEngine
}
接下来,将其添加到init?()
的末尾:
do {
try hapticEngine.start()
} catch let error {
print("Haptic failed to start Error: \(error)")
}
这样可以确保在加载游戏场景时触觉引擎已准备就绪。
仍在init?()
中,将以下行添加到末尾:
hapticEngine.isAutoShutdownEnabled = true
这实现了触觉引擎的自动关闭。这就是您负责任的能源管理。但是,这意味着iOS可以随时关闭触觉引擎,并且您无法假定它在需要时仍会运行。
Designing a Haptic Experience
您为什么要使用这些特定的事件,属性和时间安排?这次经历对您有何感想?您有没有看到刀片在空中切成薄片和锐利的snip的印象?这些是为用户设计触觉体验时需要询问的问题。
精心设计的触觉体验会以微妙的方式增强应用程序;设计不当的体验会令人烦恼和分散注意力。
精心设计的触觉体验也需要时间。您需要测试许多细微的变化,但是能够与朋友说您刚刚度过了一个下午,为鳄鱼吃菠萝的完美触觉体验,这是一种荣幸。
每个崭露头角的触觉设计师都应阅读Haptics上的Apple人机界面指南页面 Apple Human Interface Guidelines page on Haptics。 Designing with Haptics
部分提供了大量设计技巧。
何时使用
Core Haptics
:一般规则是,如果您在应用程序中使用UIKit
,则如果使用标准UIKit
控件,则可以自由使用Apple
设计的Haptics
。如果您需要一些不同的东西,则可以使用UIFeedbackGenerator进行通知,影响或选择效果。
如果您确实需要发挥创造力,那么
Core Haptics
可以满足您的需求。但是,从UIFeedbackGenerator
到Core Haptics
的复杂性跳跃很大。
现在,该开始研究鳄鱼咬入菠萝时如何获得完美的感觉!
Feeding the Crocodile
那么如何开始设计触觉体验呢?
构建并运行,看看您的鳄鱼,在菠萝掉落时高兴地咀嚼它。
您可以从中获得一些提示:声音和动画。 在Resources/Sounds
中查找并在GarageBand
或您选择的音频编辑应用中打开NomNom.caf
。 您可以看到该声音效果的波形:
波形具有两个明显的高点:初始*crunch*
,然后结束时较小的*munch*
声。
打开Haptics.swift
并在slicePattern()
之后添加一个新方法:
private func nomNomPattern() throws -> CHHapticPattern {
let rumble1 = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0,
duration: 0.15)
let rumble2 = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.3,
duration: 0.3)
return try CHHapticPattern(events: [rumble1, rumble2], parameters: [])
}
这是鳄鱼*nom-nom*
模式的开始-两个连续的事件,分别对应于nom nom
声音效果中的两种声音。
1. Playing Different Patterns
在播放新的模式之前,首先编写一种通用的模式播放方法,以避免代码重复。 在Haptics.swift
中,在playHapticSlice()
上方添加以下方法:
private func playHapticFromPattern(_ pattern: CHHapticPattern) throws {
try hapticEngine.start()
let player = try hapticEngine.makePlayer(with: pattern)
try player.start(atTime: CHHapticTimeImmediate)
}
此方法将播放传递给它的任何模式。 它仍然必须尝试启动触觉引擎,因为iOS可能会随时关闭引擎。 因此,您现在可以简化playSlice()
:
func playSlice() {
do {
let pattern = try slicePattern()
try playHapticFromPattern(pattern)
} catch {
print("Failed to play slice: \(error)")
}
}
您还可以在playSlice()
下添加一种方法来播放新效果:
func playNomNom() {
do {
let pattern = try nomNomPattern()
try playHapticFromPattern(pattern)
} catch {
print("Failed to play nomNom: \(error)")
}
}
在GameScene.swift
中,找到运行nomNomSoundAction
的didBegin(_ :)
,并在其上方添加以下行:
hapticManager?.playNomNom()
构建并运行。 当然,您将需要足够的技能来喂养鳄鱼以测试您的新效果。
查看GameScene.swift
中的runNomNomAnimation(withDelay :)
。 当鳄鱼抓到他的款待时,它的值为0.15
。 动画运行如下:
- 1) 闭嘴
- 2) 等待0.15秒。
- 3) 张开嘴。
- 4) 等待0.15秒。
- 5) 闭嘴
加上几个强劲的节拍来配合那些咬紧牙关的动作,真是太好了。 为此,向nomNomPattern()
添加两个以上的事件。 将行return try CHHapticPattern...
替换为:
let crunch1 = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0)
let crunch2 = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.3)
return try CHHapticPattern(
events: [rumble1, rumble2, crunch1, crunch2],
parameters: [])
如前所述,瞬态事件是瞬时的,就像单个鼓拍一样。 .hapticIntensity
代表感觉的强度(strength of the sensation)
,.hapticSharpness
代表physical quality
。 更高的值会产生更强大,更突出的触觉事件。 可以根据自己的喜好随意调整数字。 在这里,您添加了两个瞬态事件以匹配这些声音。
构建并运行。 漂亮*snappy*
,对不对? 您想要给用户带来愉悦的感觉,因为这是游戏屏幕的成功结果。 令人满意的*snap* *snap*
是完美的:
Syncing Audio Events
到目前为止,您已经将音频和触觉分开,但是Core Haptics
还支持触觉模式的音频事件。 使用此功能之前,您必须向触觉引擎注册每个音频资源,这将返回一个资源ID
,您可以使用该ID
来标识用于事件的音频波形。
将以下属性添加到HapticManager
:
var sliceAudio: CHHapticAudioResourceID?
var nomNomAudio: CHHapticAudioResourceID?
var splashAudio: CHHapticAudioResourceID?
这些保存您的音频资源ID。
接下来,在init?()
之后添加以下方法:
private func setupResources() {
do {
if let path = Bundle.main.url(forResource: "Slice", withExtension: "caf") {
sliceAudio = try hapticEngine.registerAudioResource(path)
}
if let path = Bundle.main.url(forResource: "NomNom", withExtension: "caf") {
nomNomAudio = try hapticEngine.registerAudioResource(path)
}
if let path = Bundle.main.url(forResource: "Splash", withExtension: "caf") {
splashAudio = try hapticEngine.registerAudioResource(path)
}
} catch {
print("Failed to load audio: \(error)")
}
}
setupResources()
检查每个音频文件的路径。 然后,它使用registerAudioResource(_:options :)
注册它们,返回资源ID
。
如果找不到该文件,该属性将保持为nil
,您可以在模式方法中进行检查。 您尚未开始splash
模式,稍后再说。
现在,您需要在init?()
的末尾添加对setupResources()
的调用:
setupResources()
将音频事件添加到nomNomPattern()
中的模式。 将return try CHHapticPattern ...
替换为:
var events = [rumble1, rumble2, crunch1, crunch2]
// 1
if let audioResourceID = nomNomAudio {
// 2
let audio = CHHapticEvent(
audioResourceID: audioResourceID,
parameters: [],
relativeTime: 0)
events.append(audio)
}
// 3
return try CHHapticPattern(events: events, parameters: [])
- 1) 首先,检查资源
ID
是否为nil
。 如果检查失败,您将仅使用触觉事件。 那就是你的fallback
。 - 2) 您可以使用
CHHapticEvent
中的特殊初始化程序为音频事件创建音频事件,并传入资源ID
。 - 3) 最后,您返回模式。
在那里,将音频添加到切片模式。 将slicePattern()
中的return try CHHapticPattern ...
替换为:
var events = [slice, snip]
if let audioResourceID = sliceAudio {
let audio = CHHapticEvent(
audioResourceID: audioResourceID,
parameters: [],
relativeTime: 0)
events.append(audio)
}
return try CHHapticPattern(events: events, parameters: [])
这与您在nomNomPattern()
中所做的非常相似。 您检查用于切片操作的资源ID
是否不为nil
,以便使用该资源ID
创建音频事件。
由于您现在将音频包含在触觉模式中,因此游戏场景无需播放该音频。 打开GameScene.swift
并找到setUpAudio()
。
在该方法的最后,您已经设置了sliceSoundAction,splashSoundAction
和nomNomSoundAction
属性:
sliceSoundAction = .playSoundFileNamed(...)
splashSoundAction = .playSoundFileNamed(...)
nomNomSoundAction = .playSoundFileNamed(...)
您需要进行更改,以便游戏场景播放触觉模式而不是音频,但前提是触觉管理器成功注册了这些音频资源ID
并可以播放它们。
将setUpAudio()
中上面的代码替换为以下代码:
guard let manager = hapticManager else {
sliceSoundAction = .playSoundFileNamed(
SoundFile.slice,
waitForCompletion: false)
nomNomSoundAction = .playSoundFileNamed(
SoundFile.nomNom,
waitForCompletion: false)
splashSoundAction = .playSoundFileNamed(
SoundFile.splash,
waitForCompletion: false)
return
}
setupHaptics(manager)
该代码首先确保hapticManager
不为nil
。 如果是这样,它将正常创建声音动作。 这是第一个fallback
位置。
如果hapticManager
不为nil
,它将调用setupHaptics
,您现在将其添加到setUpAudio()
下:
private func setupHaptics(_ manager: HapticManager) {
}
您将使用setupHaptics(_ :)
创建可播放触觉模式的SKAction
对象,但是如果触觉音频资源ID
为nil
,则还需要进行回退。 在这种情况下,您可以创建一个SKAction
组group
,该组将一起播放声音并运行没有音频的触觉模式。
将以下内容添加到setupHaptics(_ :)
:
// 1
let sliceHaptics = SKAction.run {
manager.playSlice()
}
if manager.sliceAudio != nil {
// 2
sliceSoundAction = sliceHaptics
} else {
// 3
sliceSoundAction = .group([
.playSoundFileNamed(SoundFile.slice, waitForCompletion: false),
sliceHaptics
])
}
- 1) 首先,创建触觉动作。 这是调用
playSlice()
的简单run
操作。 - 2) 如果
sliceAudio
不为nil
,则将此操作分配给sliceSoundAction
。 - 3) 但是,如果
sliceAudio
为nil
,则创建带有两个子动作的组group
动作。 第一个是playSoundFileNamed
动作,第二个是sliceHaptics
动作。
现在,为nomNomSoundAction
添加相同的方法:
let nomNomHaptics = SKAction.run {
manager.playNomNom()
}
if manager.nomNomAudio != nil {
nomNomSoundAction = nomNomHaptics
} else {
nomNomSoundAction = .group([
.playSoundFileNamed(SoundFile.nomNom, waitForCompletion: false),
nomNomHaptics
])
}
除了使用nomNomHaptics
之外,这与sliceSoundAction
非常相似。
现在,为splashSoundAction
添加一个简单的playSoundFileNamed
动作:
splashSoundAction = .playSoundFileNamed(
SoundFile.splash,
waitForCompletion: false)
您尚未设计那种触觉体验; 这样可以避免在运行游戏且splashSoundAction
为nil
时发生崩溃的情况。
构建并运行! 现在,Core Haptics
播放您的片段和nom-nom
音频。
Setting a Reset Handler
现在,您正在使用触觉音频资源,您需要考虑一个新问题。 如果设备上的触觉服务器从故障中恢复,则您的触觉引擎实例将重置。 发生这种情况时,引擎将停止并丢失所有音频资源ID
引用。 为防止这种情况,您需要一个重置处理程序。
添加重置处理程序很容易。 首先,将此新方法添加到HapticManager
中:
func handleEngineReset() {
do {
// 1
try hapticEngine.start()
// 2
setupResources()
} catch {
print("Failed to restart the engine: \(error)")
}
}
- 1)
Apple
建议您首先尝试启动引擎。 - 2) 如果可行,您可以恢复以前注册的所有音频资源ID。
接下来,将以下内容添加到init?()
中,以在引擎重置时调用handleEngineReset()
:
hapticEngine.resetHandler = { [weak self] in
self?.handleEngineReset()
}
有关更多信息,请参阅Preparing Your App to Play Haptics中的Apple文档。
下一步,当鳄鱼错过菠萝时,您将添加触觉。
Ramping Intensity Up and Down — Pineapple Splashdown
听Splash.caf
声音效果时,会有一个* splish *
沉重的声音,然后是* splash *
较长的拖尾。 将新方法添加到HapticManager
来制作代表声音体验的模式:
private func splashPattern() throws -> CHHapticPattern {
let splish = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0)
let splash = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.1,
duration: 0.6)
var events = [splish, splash]
if let audioResourceID = splashAudio {
let audio = CHHapticEvent(
audioResourceID: audioResourceID,
parameters: [],
relativeTime: 0)
events.append(audio)
}
return try CHHapticPattern(events: events, parameters: [])
}
您的新触觉体验在* splish *
的开始处有一个强烈但全面的瞬态事件,然后是一个更长,更柔和的连续事件,始于0.1
秒,* splash *
持续了0.6
秒:
在播放之前,您需要在playNomNom()
下向HapticManager
添加一个新方法:
func playSplash() {
do {
let pattern = try splashPattern()
try playHapticFromPattern(pattern)
} catch {
print("Failed to play splash: \(error)")
}
}
返回GameScene.swift
中的setupHaptics(_ :)
,删除临时的splashSoundAction
代码,然后添加以下代码来设置splashSoundAction
:
let splashHaptics = SKAction.run {
manager.playSplash()
}
if manager.splashAudio != nil {
splashSoundAction = splashHaptics
} else {
splashSoundAction = .group([
.playSoundFileNamed(SoundFile.splash, waitForCompletion: false),
splashHaptics
])
}
构建并运行和测试它。 * splish *
效果很好,但是* splash *
只是一个很长的隆隆声; 它太一维了。 它应该更像是波涛汹涌。 幸运的是,有一些事件属性可以为您提供帮助。 使用三个新属性更新启动事件:
let splash = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1),
CHHapticEventParameter(parameterID: .attackTime, value: 0.1),
CHHapticEventParameter(parameterID: .releaseTime, value: 0.2),
CHHapticEventParameter(parameterID: .decayTime, value: 0.3)
],
relativeTime: 0.1,
duration: 0.6)
-
.attackTime
是一个属性,用于控制事件从事件开始时的0
开始达到指定强度值所需的秒数。 将其视为加速时间。 -
.decayTime
相反,表示强度降低到0
所需的时间。 -
.releaseTime
控制何时开始衰减衰减。
构建并运行,体验错过鳄鱼和溅入海洋的令人失望的失望。 你能感觉到波浪吗? 在声音播放完毕之前,它应该降低到0
强度。
Controlling Intensity With a Parameter Curve
由于您通过这些触觉体验而变得富有创意,为什么不改善片段触觉模式以获得更令人满意的* SsssNIP *
感觉? 触觉模式也可以接受适用于整个模式的参数。
首先,像这样更新slice
事件属性:
let slice = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
],
relativeTime: 0,
duration: 0.5)
这增加了强度,清晰度和持续时间。
接下来,在slicePattern()
中的两个slice
和snip
事件之后,创建一种新型的参数CHHapticParameterCurve
:
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)
参数曲线类似于动画曲线,控制点类似于动画关键帧。
此参数曲线具有ID .hapticIntensityControl
,它用作模式中所有事件强度值的乘数。 因为是曲线,所以参数会随着模式播放在控制点之间平滑插值。
例如,第一个控制点在时间0
处的值为0.2
,这意味着它在开始时将所有事件强度值乘以0.2
。 到0.08
秒时,它将平滑地增加到1.0
的倍数。 到0.24
秒,它将平滑地回落到0.2
,依此类推。
外观如下:
要使用参数曲线,您需要使用CHHapticPattern(events:parameterCurves :)
初始化图案对象。
仍在Haptics.swift
中,用以下内容替换slicePattern()
中的return
语句:
return try CHHapticPattern(events: events, parameterCurves: [curve])
这将使用您指定的曲线创建触觉模式。
进行构建并运行,以体验新的动态触觉体验。
Updating Pattern Parameters in Real Time
如果您认为动态参数曲线很棒,请等到看到Core Haptics
的高手:CHHapticAdvancedPatternPlayer
。 这是一个模式播放器,您可以在播放时对其进行控制。
您的游戏中缺少一些重要的东西。 当玩家在屏幕上挥动手指时,您可以看到粒子效果,但是刀片在空中划片的感觉在哪里? 使用CHHapticAdvancedPatternPlayer
,您甚至可以实时控制强度,从而使玩家的手指移动得越快,强度就越强。
首先,向HapticManager
添加属性以保存对新player
的引用:
var swishPlayer: CHHapticAdvancedPatternPlayer?
接下来,添加一种创建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)")
}
}
这是一种简单的模式:一个持续时间很长的连续事件。 您可以通过调用makeAdvancedPlayer(with :)
创建player
。
接下来,在HapticManager
中,将以下行添加到setupResources()
中:
createSwishPlayer()
这样,无论何时在初始化程序和触觉引擎重置处理程序中调用setupResources()
时,都可以创建swish
播放器。 引擎重置时,播放器参考也会重置。
接下来,您需要添加一种启动播放器的方法。 在HapticManager
的末尾添加以下内容:
func startSwishPlayer() {
do {
try hapticEngine.start()
try swishPlayer?.start(atTime: CHHapticTimeImmediate)
} catch {
print("Swish player start error: \(error)")
}
}
startSwishPlayer()
首先调用hapticEngine.start()
,以防引擎停止。 然后,它使用CHHapticTimeImmediate
调用模式播放器的start(atTime :)
,以便播放器立即启动。
您还需要添加一种方法来停止播放器。 还要在HapticManager
的末尾添加此代码:
func stopSwishPlayer() {
do {
try swishPlayer?.stop(atTime: CHHapticTimeImmediate)
} catch {
print("Swish player stop error: \(error)")
}
}
在这里,您尝试通过传递CHHapticTimeImmediate
来尽快(几乎立即)停止模式播放器。
返回GameScene.swift
,找到touchesBegan(_:with :)
并添加以下行以在玩家开始扫过时启动模式player
:
hapticManager?.startSwishPlayer()
接下来,找到touchesEnded(_:with :)
并添加以下行以在播放器的滑动结束时停止模式播放器:
hapticManager?.stopSwishPlayer()
构建并运行,在屏幕上移动手指时,您应该体验player
的启动和停止。
现在,该添加魔法了!
1. Making the Player Dynamic
接下来,您将根据用户的动作来调整摆幅的强度。 将以下方法添加到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)")
}
}
- 1) 新的
updateSwishPlayer(intensity :)
接受一个float
参数:一个介于0
和1
之间的值。 - 2) 使用该值创建
ID
为.hapticIntensityControl
的CHHapticDynamicParameter
。 此参数的功能与您创建的上一个参数曲线非常相似,可以用作模式中所有事件强度值的乘数。 与曲线不同,这是一次更改。 - 3) 将动态参数发送给
player
,使其可以立即应用于正在播放的模式。
返回GameScene.swift
并将以下内容添加到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)
每次系统调用touchesMoved(_:with :)
时,您都会更新动态播放器的强度控制值。 您可以使用一种简单的算法来计算强度:从上一次触摸移开的次数越多,强度值就越高。
构建并运行。 现在,剪断藤蔓感觉就像您是一名挥舞着轻剑的绝地骑士!
如果您相信这一点,那么您仅涉及Core Haptics
可以实现的目标。 还有更多值得探索的地方。
观看WWDC 2019
的Apple会议:
浏览Core Haptics documentation。 也有一些示例Xcode
项目可供下载。
不要忘记Haptics
上的Apple Human Interface Guidelines以及Designing with Haptics
中的技巧。
您可能还需要阅读有关Apple Haptic和Audio Pattern(AHAP)文件格式的信息Apple Haptic and Audio Pattern (AHAP) file format。
后记
本篇主要讲述了
Core Haptics
的一个简单示例,感兴趣的给个赞或者关注~~~