WWDC Quick Look 💓 By SwiftGGTeam
Practice audio haptic design

Practice audio haptic design

元の動画を見る

ハイライト

Apple は、HapticRicochet の例を使用して、コア ハプティクス、オーディオ、およびアニメーションを同じインタラクション モーメントにバインドする方法を実証しました。これにより、iPhone アプリケーションの衝突、シールド、ローリング テクスチャのフィードバックが明確なソースと一貫したリズムを持ち、キー アクション中にのみ表示されるようになります。

主要内容

小規模なゲームを作成する場合、画面フィードバックは実装が最も簡単です。ボールが境界に当たると、速度を変えることができます。ユーザーがボールをクリックすると、アニメーションを再生できます。背景テクスチャを別の画像に変更できます。

問題は打感と音です。

衝突音が聞こえても触覚ではない場合、ボールは画面に張り付いた層のように動作します。タッチが頻繁に発生すると、ユーザーはうるさいと感じます。音やタッチのリズムが違うとインタラクションが緩くなってしまいます。

このセッションでは、HapticRicochet を使用してこの問題について話します。これは HapticBounce サンプルからのものです。ボールはiPhoneの方向に転がり、壁に当たると音声と触覚を生み出します。ユーザーがボールを指すと、ボールが大きくなります。彼が再びそれを指すと、ボールはシールドを獲得します。衝突するとシールドが失われ、最終的にはボールが爆発します。

Appleは2つの動きに注目している。

最初のアクションはシールドを追加することです。この瞬間には 500 ミリ秒のビジュアル アニメーションがあり、「保護されている」状態の変化を伝えるために音とタッチも必要です。

2 番目のアクションは、スクロール テクスチャを有効にすることです。ユーザーが背景をクリックすると、背景にドット テクスチャが表示されます。ボールがテクスチャ上を転がるとき、その感触は連続的であり、ボールの速度に応じて変化する必要があります。

Core Haptics (Core Haptics Framework) は、低レベルの機能を提供します。フィードバックは、エンジン、プレーヤー、パターン、イベントの 4 つのオブジェクトに分割されます。開発者は、AHAP (Apple Haptic Audio Pattern) ファイルからパターンを作成し、次にプレーヤーを作成し、最後にインタラクションが発生したときに再生します。

このプロセスにより、コラボレーションの問題が解決されます。デザイナーは、AHAP で触覚とオーディオを調整できます。エンジニアはパターンを実際のインタラクションに結び付けます。両側で同じ文書を使用してフィードバックについて話し合い、「より硬い」または「より厚い」などの抽象的な形容詞を減らします。

詳細

コアハプティクスの 4 つのオブジェクト

04:41

Core Haptics の API は、エンジン、プレーヤー、パターン、イベントの 4 つのオブジェクトを中心に動作します。

エンジンはiPhoneの物理アクチュエーターに接続されます。プレーヤーは再生、停止、一時停止をコントロールします。パターンは、時間内に配置されたイベントの集合です。イベントは実際のフィードバック単位であり、一般的なタイプには一時的なものと連続的なものがあります。

セッションでは、実践的な部分では、読み込みモード、変更モード、再生モードの 3 つのことに焦点を当てると述べました。

import CoreHaptics

final class HapticController {
    private let engine: CHHapticEngine
    private var player: CHHapticPatternPlayer?

    init() throws {
        engine = try CHHapticEngine()
        try engine.start()
    }

    func loadPattern(named name: String) throws {
        guard let url = Bundle.main.url(forResource: name, withExtension: "ahap") else {
            return
        }

        let pattern = try CHHapticPattern(contentsOf: url)
        player = try engine.makePlayer(with: pattern)
    }

    func play() throws {
        try player?.start(atTime: CHHapticTimeImmediate)
    }
}

キーポイント:

  • import CoreHaptics触覚フレームワークを紹介します。
  • CHHapticEngineセッションのエンジンに相当する、再生ハプティクスへの入り口です。
  • try engine.start()パターンを再生する前にエンジンを始動してください。
  • Bundle.main.url(forResource:withExtension:)アプリケーションパッケージから読み取る.ahap書類。
  • CHHapticPattern(contentsOf:)AHAP ファイルからパターンを作成します。
  • engine.makePlayer(with:)パターンからプレーヤーを作成します。
  • start(atTime: CHHapticTimeImmediate)現在のモードをすぐにプレイします。

シールド フィードバック: 最初にモードをロードし、アニメーション中に再生します。

08:05

シールドアクションは2段階に分かれています。

最初のステップは、初期化フェーズでスキーマを作成することです。セッションコードは次のとおりですShieldTransientこの AHAP ファイルはパターンを作成し、それを使用します。engine.makePlayer(with:)作成するshieldPlayer

2 番目のステップでは、シールドのアニメーションが開始されるときにフィードバックを再生します。shield()関数呼び出しstartPlayer(shieldPlayer)、次に置きますshieldAnimationに追加sphereView.layer優れた。

import UIKit
import CoreHaptics

final class ShieldViewController: UIViewController {
    private var engine: CHHapticEngine!
    private var shieldPlayer: CHHapticPatternPlayer?
    private let sphereView = UIView()
    private let shieldAnimation = CABasicAnimation(keyPath: "borderWidth")
    private var isAnimating = false

    override func viewDidLoad() {
        super.viewDidLoad()
        engine = try? CHHapticEngine()
        try? engine.start()
        initializeShieldHaptics()
    }

    func initializeShieldHaptics() {
        guard let pattern = createPatternFromAHAP("ShieldTransient") else {
            return
        }

        shieldPlayer = try? engine.makePlayer(with: pattern)
    }

    func shield() {
        startPlayer(shieldPlayer)

        isAnimating = true
        sphereView.layer.add(shieldAnimation, forKey: "Width")
    }

    private func createPatternFromAHAP(_ name: String) -> CHHapticPattern? {
        guard let url = Bundle.main.url(forResource: name, withExtension: "ahap") else {
            return nil
        }

        return try? CHHapticPattern(contentsOf: url)
    }

    private func startPlayer(_ player: CHHapticPatternPlayer?) {
        try? player?.start(atTime: CHHapticTimeImmediate)
    }
}

キーポイント:

  • engine = try? CHHapticEngine()ハプティックエンジンを作成します。
  • try? engine.start()エンジンを始動します。
  • initializeShieldHaptics()インタラクションが発生するまでシールド タッチが作成されないように、事前にシールド タッチを準備してください。
  • createPatternFromAHAP("ShieldTransient")セッションに対応ShieldTransientリソース。
  • engine.makePlayer(with: pattern)生成するshieldPlayer
  • shield()実際にシールドの変形が発生する場所です。
  • startPlayer(shieldPlayer)まず触覚とオーディオを試してください。
  • sphereView.layer.add(shieldAnimation, forKey: "Width")シールドのビジュアルアニメーションを再生します。

AHAP を変更: お気に入りのサウンドを適切なタッチに接続します

10:11

Apple は 2 つのシールド リソースを披露しました。

ShieldTransient音には「守られている」感覚がありますが、タッチは3つの過渡的なイベントで構成されています。音が途切れず、両者のリズムが合わない。

ShieldContinuousタッチは連続前進で、より「シールド生成」に適しています。ただし、付属の音声は減衰してしまい、効果は適切ではありません。

最後のアプローチは簡単です。オープンです。ShieldContinuous.ahap、オーディオ イベントを見つけて、ファイル名を次のように変更します。ShieldB.wavに変更しますShieldA.wav。ハプティクスは連続モードのままで、サウンドは最初のリソースのサウンドに置き換えられます。

{
  "Event": {
    "Time": 0,
    "EventType": "AudioCustom",
    "EventWaveformPath": "ShieldA.wav",
    "EventParameters": [
      {
        "ParameterID": "AudioVolume",
        "ParameterValue": 1.0
      }
    ]
  }
}

キーポイント:

  • EventType使用AudioCustom、このイベントがカスタム オーディオ ファイルを再生することを示します。
  • EventWaveformPathを指さして.wav書類。
  • セッション中の変更は、ShieldB.wavと置き換えますShieldA.wav
  • AudioVolume使用ParameterValue音量を調整します。
  • この変更は AHAP ファイル内で行われ、Swift 再生ロジックを変更する必要はありません。

11:07

リソースの変更が完了したら、Swift コードはファイル名を変更するだけで済みます。初期化関数は次から始まりますShieldTransientに変更しますShieldContinuous

func initializeShieldHaptics() {
    guard let pattern = createPatternFromAHAP("ShieldContinuous") else {
        return
    }

    shieldPlayer = try? engine.makePlayer(with: pattern)
}

キーポイント:

  • 関数名は変更されず、呼び出しポイントも変更する必要はありません。
  • ShieldContinuousは変更された AHAP リソースです。
  • engine.makePlayer(with:)プレーヤーはまだパターンから作成されています。
  • シールドアニメーションが発動すると、shield()同じ電話をかけ続けるshieldPlayer

スクロール テクスチャ: Advanced Player で解決した後 2 秒後に停止します

12:15

スクロール テクスチャの最初の実装には問題がありました。背景テクスチャをオンにすると、ボールが転がるときに触感が感じられますが、数秒後には触感が消えます。

理由は明確です。テクスチャ AHAP ファイルには、触覚コンテンツが 2 秒しか含まれていません。

Apple の修正は、テクスチャ プレーヤーを次のように置き換えることです。CHHapticAdvancedPatternPlayer。高度なプレーヤーは、一時停止、再開、コールバックなどの機能を提供します。 HapticRicochet はループ機能のみを使用します。

import CoreHaptics

final class TextureHaptics {
    private let engine: CHHapticEngine
    private var texturePlayer: CHHapticAdvancedPatternPlayer?

    init(engine: CHHapticEngine) {
        self.engine = engine
    }

    func initializeTextureHaptics() {
        guard let url = Bundle.main.url(forResource: "Texture", withExtension: "ahap"),
              let pattern = try? CHHapticPattern(contentsOf: url) else {
            return
        }

        texturePlayer = try? engine.makeAdvancedPlayer(with: pattern)
        texturePlayer?.loopEnabled = true
    }

    func startTexture() {
        try? texturePlayer?.start(atTime: CHHapticTimeImmediate)
    }
}

キーポイント:

  • texturePlayer種類はCHHapticAdvancedPatternPlayer
  • CHHapticPattern(contentsOf:)パターンは引き続き同じテクスチャ AHAP ファイルから作成されます。
  • engine.makeAdvancedPlayer(with:)上級プレイヤーを作成します。
  • texturePlayer?.loopEnabled = trueループを開始します。
  • start(atTime:)再生の入り口は一貫したままです。

ローリングテクスチャー: タッチ強度はボール速度に応じて更新されます

12:26

セッションで言及された、updateTexturePlayerテクスチャがアクティブ化されるとフレームごとに呼び出されます。ボールの速度に基づいてタッチの強さを更新します。

ここでの設計原則は因果関係です。ユーザーは、テクスチャーのある背景上でボールが転がるのを見て、そのテクスチャーを手に感じます。ボールが速ければ速いほど、フィードバックはより顕著になります。フィードバックのソースが明確になります。

import CoreHaptics

func updateTexturePlayer(ballSpeed: Float, texturePlayer: CHHapticAdvancedPatternPlayer?) {
    let normalizedSpeed = max(0, min(ballSpeed, 1))
    let intensity = CHHapticDynamicParameter(
        parameterID: .hapticIntensityControl,
        value: normalizedSpeed,
        relativeTime: 0
    )

    try? texturePlayer?.sendParameters([intensity], atTime: CHHapticTimeImmediate)
}

キーポイント:

  • ballSpeed現在のボール速度を示し、セッションはボール速度に基づいてタッチが更新されることを示します。
  • max(0, min(ballSpeed, 1))入力を 0 ~ 1 に制限します。
  • CHHapticDynamicParameter再生時のパラメータ調整に使用します。
  • .hapticIntensityControlタッチの強さを調整します。
  • relativeTime: 0パラメータがすぐに有効になることを示します。
  • sendParameters(_:atTime:)現在プレイしている上級プレイヤーに新しい強度を送信します。

視覚的な質感も触感の密度と一致する必要があります

14:04

ループを修正した後、設計上の問題が 1 つ残っています。

触覚パターンは高密度で、2 秒間に 100 近くの入力があります。ビジュアル背景は非常に厚く、画面上には数個のドットしかありません。質感や画像濃度が一定しない。

Apple の解決策は、背景画像リソースをより薄いバージョンに置き換えることです。タッチ モードは変更されず、視覚的なテクスチャがより緻密になり、ボールが背景を転がるときの感触が写真に近くなります。

import UIKit

final class TextureBackgroundView: UIView {
    private let backgroundImageView = UIImageView()

    func useFineTexture() {
        backgroundImageView.image = UIImage(named: "BackgroundTextureFine")
    }
}

キーポイント:

  • backgroundImageViewテクスチャ背景の表示を担当します。
  • UIImage(named:)リソースから画像を読み取ります。
  • BackgroundTextureFineセッションで言及された Fine バージョンのリソースに対応します。
  • この変更は感覚の一貫性に対処します。視覚的なドットの密度はタッチ イベントの密度に近づける必要があります。

重要ポイント

1.「オブジェクト衝突」フィードバック レイヤーを作成する

  • やるべきこと: ボール、カード、チェスの駒などの可動オブジェクトに衝突、落下、吸着の音や感触を追加します。
  • 実行する価値がある理由: 壁にボールをぶつけるセッションは因果関係を示しています。フィードバックにより、ユーザーはどのアクションがトリガーとなったかを知ることができます。
  • 開始方法: 以下を使用して、衝突ごとに AHAP ファイルを準備します。CHHapticPattern(contentsOf:)読んで再利用するengine.makePlayer(with:)プレーヤーを作成し、衝突検出コールバックで再生します。

2.「ステータスアップグレード」アニメーションフィードバックを作成する

  • 内容: ユーザーがシールドをアップグレード、ロック解除、装備するときに、アニメーション、オーディオ、およびハプティクスを同時に再生します。
  • 実行する価値がある理由: シールドの例は、状態変化が継続的なタッチ表現に適していることを証明しており、ユーザーはオブジェクトが新しい状態に入ったことを感じることができます。
  • 開始方法: まず、約 500 ミリ秒のビジュアル アニメーションを作成し、次に AHAP を使用して同じ長さの連続タッチを試します。オーディオが適切でない場合は、AHAP で直接置き換えますAudioCustomファイル名。

3.「マテリアルテクスチャ」スイッチを作成します。

  • 内容: 紙、金属、グリッド、粗い地面などの背景素材を切り替えて、オブジェクトをドラッグするときに異なるテクスチャを感じることができます。
  • 実行する価値がある理由: スクロール テクスチャの例は、視覚的なコンテキストと継続的な触感を結びつけているため、ユーザーは触感が画面上のテクスチャから来ていることを理解できます。
  • 開始方法: 素材ごとに 2 秒の AHAP を用意し、使用しますCHHapticAdvancedPatternPlayer遊んで設定するloopEnabled = true

4. 「スピード重視」の触覚システムを作成する

  • 内容: スクロール、スライド、ドラッグの触感の強さを速度に応じて変化させます。
  • 実行する価値がある理由: HapticRicochet は、ボールの速度に応じてフレームごとにテクスチャ タッチを更新し、固定強度によって引き起こされる誤った感覚を回避します。
  • 開始方法: オブジェクトの速度を計算し、速度を 0 から 1 に正規化してから使用します。CHHapticDynamicParameterそして.hapticIntensityControlプレイ中の上級プレイヤーを更新します。

5. AHAP デバッグ ワークフローを作成する

  • やるべきこと: 触覚リソースをデザイン資産として管理し、画像と音声を反復処理します。
  • 価値がある理由: セッションでは macOS 12 の Quick Look Visualizer を紹介します。選択された.ahapファイル上でスペースを押すと、タッチ モードが表示されます。
  • 開始方法:.ahap.wav.pngリソースディレクトリに置きます。 Quick Look を使用すると、設計段階で一時的なイベントと連続的なイベントをチェックできます。エンジニアリング段階はロードと再生のみを担当します。

関連セッション

コメント

GitHub Issues · utterances