View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • visionOS向けTabletopKitについて

    TabletopKitを使用してvisionOS向けのボードゲームをゼロから構築しましょう。ゲームをセットアップする方法、RealityKitを活用して強力なレンダリングを追加する方法や、FaceTimeの空間Personaを使用したマルチプレイヤーでのプレイをコードを数行追加するだけで実現する方法をご紹介します。

    関連する章

    • 0:00 - Introduction
    • 2:37 - Set up the play surface
    • 7:45 - Implement rules
    • 12:01 - Integrate RealityKit effects
    • 13:30 - Configure multiplayer

    リソース

    • Creating tabletop games
    • Forum: Graphics & Games
    • TabletopKit
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC24

    • Reality Composer Proにおけるインタラクティブな3Dコンテンツの作成
    • SharePlayでの空間Personaテンプレートのカスタマイズ

    WWDC23

    • アプリにSharePlayを追加する
  • ダウンロード

    こんにちは TabletopKitチームの Juliaです 今日は 新しいTabletopKitと Apple Vision Proでのゲーム制作について ご紹介したいと思います まず TabletopKitで テーブルゲームを作る際の 基本的な要素についてご説明します

    ゲームの仕組みとしては プレイヤーがサイコロを振って盤上を移動し カードを集めていきます ゲーム盤の形状は超現実的なもので 現実世界の可能性を超えた アニメーションが駆使され 後片付けも必要ありません サンプルコードもご用意しています TabletopKitのフレームワークを使えば Apple Vision Pro用の 空間的なマルチプレイヤーの テーブルゲームを制作できます カードゲームやサイコロゲーム より複雑なボードゲームも可能です ジェスチャやレイアウトは TabletopKitで処理できるので デベロッパは楽しく革新的なユーザー体験を デザインすることに集中できます TabletopKitはGroupActivitiesや RealityKitなどのフレームワークとも 自然に統合できるので スムーズなネットワーキングと 魅力的なレンダリングを 数行のコードで追加できます

    スマートなデフォルト設定や 多くの便利な機能を提供することで 機能的なゲームを素早く立ち上げると同時に 自由なカスタマイズを行って 真にユニークなゲームを 作り上げることができます

    テーブルゲームはすでに Vision Proで人気があります その良い例が App Storeの Game Roomのソリティアです

    ただし ゲームを開発する時は 様々な要素について 考慮する必要があります 表のレイアウト アニメや物理シミュレーション 高性能なマルチプレイヤーなどです

    TabletopKitは そのような障壁を取り除き ゲーム制作の楽しさと 創造性を引き出すように設計されています 今日ご紹介するゲームは 古典的なボードゲームをアレンジしたもので どこか懐かしく楽しいものです まず最初に ゲームの初期状態を設定します プレイ中に目にしたり操作したりする すべてのものが対象です

    その後 得点のつけ方や無効な動作の禁止など ゲームの論理的な仕組みを決定します

    次に RealityKitを使って 視覚効果やオーディオ効果を加え ゲームに命を吹き込みます 最後に GroupActivitiesを使って コードを数行だけ追加し マルチプレイヤー機能を有効にします

    ではまず ゲームの設定から見ていきましょう

    テーブルゲームについて考えるには 盤上を見るのが一番です

    まず テーブルを用意して全員の席を決めます そこにマス目を設定した盤を置き 具体的なオブジェクトを追加します プレイの駒 カード サイコロなどの要素で ゲームに命を吹き込みましょう

    ゲームで最初に記述すべきものは テーブルです 設定もプレイも すべて盤上で行われるからです テーブルは半径のある円形でも 幅と奥行きのある長方形でも構いません テーブルは各ゲームに必須です 少人数のゲームなら 小さい円形のテーブルがよいかもしれません 例えば 友達とのチェッカーなどです 広々とした長方形のテーブルは 大型のボードや数多くの要素を使用する 複雑なボードゲームに最適です

    テーブルができたら 発生しうるすべての位置や向きを テーブルの原点と座標系を基準に記述します

    テーブルを記述するのは簡単です ここでの形状と寸法は これから作成するゲームの プレイ可能領域を表します この形状は多くの場合 レンダリングする テーブルエンティティと一致しますが そうである必要はありません

    ここでは 長方形のテーブルを作成し フレームワークでエンティティの バウンディングボックスを指定します テーブルを定義した後 テーブルの周りに座席を配置します

    座席の位置は テーブルの原点を基準に決定します 1つのシートに割り当てられるのは 一度に1名のプレイヤーのみで 各プレイヤーはゲームに参加するために 座席を確保する必要があります ゲームの観戦者は着席できません

    プレイヤーはゲーム全体を通じて 様々な席を獲得できます

    このゲームの参加者は3人までなので 固有のIDを割り当てた3つの席を テーブル周りに等間隔に配置し 皆がテーブルの中央を向くようにします

    マルチプレイヤーセッションでは 他のメンバーも観戦できますが 着席はできないため テーブル上のオブジェクトを 操作することはできません テーブルと座席を用意したら あとはゲーム自体の設定をするだけです テーブル上にあるものはすべて要素です

    この例では ボード マス目 駒 カード サイコロはすべて 種類の異なる要素になります

    では それぞれの要素を使ってどのように ゲームのコンポーネントを 表現するのかをご紹介します

    まず 駒の記述方法を説明します 駒は各プレイヤーがボード上で動かすもので ゲーム内の物理的なオブジェクトであり RealityKitエンティティとして- 表示されるため 固有のサイズがあり プレイヤーが操作できます

    駒はそれぞれの対応する座席の前から出発し その座席の所有物なので その座席のプレイヤーだけが 動かすことができます

    これがサンプルの駒のコードです 駒はEntityEquipmentプロトコルに 準拠しているため RealityKitエンティティがアタッチされており ゲーム内の有形オブジェクトになります

    初期状態では 駒の主要なプロパティを設定します seatControlは対応する 座席のみに設定したので 自分の駒を動かせるのは その席のプレイヤーだけです

    駒の開始位置は テーブルを基準に設定したため 駒はまず 各プレイヤーの目の前に配置されます

    また エンティティを引き渡せば 駒のバウンディングボックスが フレームワークによって決定されます

    このゲームの要素のもう1つの例は マス目です 高架のベルトコンベヤが ゲーム盤の役割を果たし 駒はその上にあるマス目に沿って移動します

    個々のマス目は盤上の特定の地点を表します

    ゲームの中では プレイヤーが自分の駒を 様々なマス目に移動させます

    2人のプレイヤーが同じマスに到達した場合 1つのマスに複数の駒が 同時に入ることができます

    各マスにはカテゴリもあり それがゲームプレイや 表示されるアニメーションに影響します

    これがコンベヤのマスで Equipmentプロトコルに準拠しています

    駒と同じように 初期状態の マスの様々なプロパティを記述します

    まず ボードを親に設定します これは マス目がボードのバウンディング ボックス上にあることを意味します

    次に 位置を設定します マス目はボードの子なので 位置はすべてボードの座標系を 基準としたものになります

    最後に マスのエンティティは レンダリングしないので バウンディングボックスを明示します

    同様のパターンでカード1組 サイコロ そしてカードを受け取る 各プレイヤーの手を追加します

    これでゲームを始めるために必要なものが すべて揃いましたので ゲームの仕組みを構成していきましょう ゲームは通常 様々なプレイヤーがオブジェクトを 操作することで進んでいきます ゲームをどの程度自動化し どの程度をユーザーが自分で 操作できるようにするかは 設定によって変更できます 例えば カードの配布が退屈なら 自動的に全員にカードを配る ボタンを追加できます それでもプレイヤーが手で山札から カードを引けるようにしておけば より臨場感を演出できます この例では プレイヤーがサイコロを振り 駒を動かしてカードを集めます TabletopKitでは システムジェスチャが監視されるので SwiftUIのシーンを利用して アプリをビルドする時と同じジェスチャが 処理されたうえで インタラクションの形で戻されます

    インタラクションには アクションを追加して ゲームの状態を変えることができます 具体的な例を見てみましょう プレイヤーが山札から カードをつまみ出してドラッグし いずれかの山の上に置くとします そして システムのピンチ動作を監視し それをTabletopKitの インタラクションにして そのカードを新しい山に移動する アクションを追加します

    システムジェスチャによって TabletopKitの インタラクションが生成されます ジェスチャが変わるたびに TabletopKitはインタラクションの コールバックを呼び出します コールバックでは対象となる要素と 現在のフェーズが指定されます ジェスチャフェーズは システムジェスチャのフェーズを示すので ユーザーがピンチすると同時に開始され ピンチ動作をやめると終了します インタラクションフェーズは TabletopKitの インタラクションのフェーズなので 例えば ユーザーがサイコロを拾うと開始され 投げられたサイコロがテーブルに落ちると 終了します

    ジェスチャは開始されると同時に 開始フェーズに入り 持続中はずっと更新フェーズになります ジェスチャは実行中に いつでもキャンセルできます 例えば 手でドラッグしている最中に その手を背中の後ろへ回したりした場合です キャンセルは例えば ピンチをやめてオブジェクトを 落とすような意図的な終了とは異なります

    ゲームをRealityViewにアタッチする時は TabletopInteractionオブジェクトの 実装を指定します

    更新のコールバックは ジェスチャの更新のたびに呼び出されます コンテキストには対象となる要素や その配置場所といった 書き込み可能なプロパティがいくつかあり インタラクションのキャンセルや終了などの 変更を行う関数もあります

    値にはジェスチャやインタラクションの フェーズ 提案される移動先 関連するポーズなど 読み取り可能な情報が含まれます インタラクションが更新される時が ゲーム状態を調整する機会です

    アクションはゲーム状態に適用される 個別の操作で 駒を移動したり カードをめくったりすることです

    アクションは指示されるとキューに入り 1つずつ実行されます

    一般的な例は 親同士の間でのオブジェクト移動などです このコードスニペットでは 操作可能なオブジェクトを あらゆる有効な親要素に ドロップできるようにしています

    ジェスチャが終了したという コールバックを受信したら 提示された有効な親があるかどうかを 確認します

    ある場合は 操作のコンテキストにアクションを追加して その要素を提示された親要素に移動します

    これでプレイヤーは盤上で駒を移動したり カードを引いたり サイコロを振ったりできるようになります

    ゲーム状態の変化については ユーザーが完全にコントロールできます TabletopKitは 何が動かされたかについての情報を示し 何が起こるべきかについては ユーザーが選択できます 例えば チェスのゲームを作るとしたら まず 1つのモードでゲームのルールを紹介し 可能な動きを紹介して実演します そしてもう1つの練習モードで ルールを強制することなく 少しゲームを楽しめるようにします

    ゲームメカニクスは 楽しいゲーム体験の重要な要素ですが ソーシャルダイナミクスも同じです それらをゲームごとに組み合わせるのです この時点でサンプルゲームは 技術的にプレイ可能です でも Apple Vision Proのゲームは 単にプレイ可能なだけではありません RealityKitはすでに ほとんどの作業を行って ゲームに魔法のような 視覚効果を与えてくれています 影と光を使った非常にリアルなモデルや 風変わりな定型アニメーションなど 制作できるものはみな RealityKitでレンダリングできます このサンプルアプリでは 駒がいい位置に到達すると ロボットが喜んではしゃぎ 悪い位置に着くと落ち込みます とても可愛いです

    さっき見たように インタラクティブな要素は ゲームの進行に合わせて 自動的に動き回ります エンティティは手動でロードするので 様々な特殊効果を自由に追加し ユーザーの操作に合わせて トリガすることができます RealityKitで効果音を再生するのは 非常に簡単なので サイコロを転がす時に音を鳴らしてみます これが先ほど説明した インタラクションの更新コールバックです

    ここで プレイヤーがサイコロを離すという ジェスチャの終了を監視します

    オーディオライブラリコンポーネントで 好きな音を探します

    音響リソースが見つかったら それをサイコロで再生するよう RealityKitに伝えるだけです RealityKitは 空間オーディオに対応しているため 音はどこでもサイコロがある場所で 鳴らすことができます

    では 実際にサイコロを投げてみましょう

    テーブルゲームは 他の人と 一緒にプレイする時が一番楽しいものです FaceTimeの空間Personaはすばらしい機能で 非常にリアルなので これを利用して新しい作品を生み出せば 家族や友人が同じ部屋にいない時でも 一緒にゲームを楽しむことができます

    デフォルト設定がある ネットワークゲームの場合は 数行のコードで GroupActivitiesセッションを開始し それをフレームワークに渡します そこから優れた新機能や カスタムの空間テンプレートを活用して ゲーム体験をカスタマイズし プレイヤーや観戦者を室内の好きな場所に 配置することができます TabletopKitはマルチプレイヤー ネットワークに対応しており とても簡単に設定できます このフレームワークでは アクションが同期され 全プレイヤーのゲーム状態が一致します

    プレイヤーがカードを拾うなどの アクションを送信すると それが検証され 決定論的な順序で ゲーム状態に追加されます

    アニメーションや物理シミュレーションなど 処理負荷の高い機能の一部は 各自のローカルで処理されるため マルチプレイヤー画面は 素早くスムーズに動作し続けます

    この例では プレイヤーがいつでも好きな時に SharePlayを開始できるよう ツールバーにボタンを用意してみます このコードは単純な SharePlayボタンを示しています SharePlayシンボルを使った SwiftUIボタンです

    このボタンが押されたら 新しいGroupActivitiesセッションを 開始します

    セッションが有効になったら それをTabletopKitに伝え そのGroupActivitiesセッションと調整します これで基本的な設定は完了です ゲームの状態は すべてのプレイヤー間で同期されます ゲームで独自の空間レイアウトを使用するなら 空間Personaの カスタムテンプレートAPIを利用できます

    デフォルトでは 最初の設定時に記述した座席を使って GroupActivitiesセッションの デフォルトのテンプレートが定義されます

    そのため このサンプルでは それぞれの人物が テーブル上の座席の脇に配置され 中央を向いて回転します

    別の空間設定を使用したい場合は Custom Spatial Template APIを 利用して 任意のテンプレートを設定すれば デフォルトのテンプレートが オーバーライドされます 詳しくはビデオをご覧ください

    TabletopKitを使えば ゲームに命を吹き込むことができます 魅力的な体験を かつてなく簡単に作り上げ FaceTimeの空間ペルソナで 社会的な交流をサポートすることができます

    私たちはゲーム開発の際によくある 複雑な問題のいくつかを解決していますが ゲームの見た目や雰囲気 動作などは 皆さんが自由に決定できます

    また RealityKitや GroupActivitiesなど 他の優れたAppleフレームワークとも 自然に統合できるので 開発プロセスはさらにシンプルになります 詳細は これらのビデオでご確認ください

    TabletopKitを使えば すべてのデベロッパがゲームを開発できます 皆さんの作品を楽しみにしています

    • 3:52 - Make a rectangular table

      // Make a rectangular table.
      
      let entity = try! Entity.load(named: "table", in: table_Top_KitBundle)
      let table: Tabletop = .rectangular(entity: entity)
    • 4:25 - Place seats

      // Place 3 seats around the table, facing the center.
      
      static let seatPoses: [TableVisualState.Pose2D] = [
        .init(position: .init(x: 0, y: Double(GameMetrics.tableDimensions.z)),
              rotation: .degrees(0)),
        .init(position: .init(x: -Double(GameMetrics.tableDimensions.x), y: 0),
              rotation: .degrees(-90)),
        .init(position: .init(x: Double(GameMetrics.tableDimensions.x), y: 0),
              rotation: .degrees(90))
      ]
    • 5:40 - Define player pawns

      // Define an object that describes a pawn for each player.
      
      struct PlayerPawn: EntityEquipment {
        let id: ID
        let entity: Entity
        var initialState: BaseEquipmentState
      
        init(id: ID, seat: PlayerSeat, pose: TableVisualState.Pose2D, entity: Entity) {
          self.id = id
          self.entity = entity
          initialState = .init(seatControl: .restricted([seat.id]),
                      pose: pose,
                      entity: entity)
        }
      }
    • 6:55 - Define an object that describes a tile

      // Define an object that describes a tile on the conveyor belt
      
      struct ConveyorTile: Equipment {
        enum Category: String {
          case red
          case green
          case grey
        }
         
        let id: ID
        let category: ConveyorTile.Category
        let initialState: BaseEquipmentState
      
        init(id: ID, boardID: EquipmentIdentifier, position: TableVisualState.Point2D, category: ConveyorTile.Category) {
          self.id = id
          self.category = category
          initialState = .init(parentID: boardID,
                    pose: .init(position: position, rotation: .init()),
                    boundingBox: .init(center: .zero, size: .init(x: 0.06, y: 0, z: 0.06)))
    • 9:53 - Monitor interactions

      // The view contains all the content in the game.
      
      RealityView { (content: inout RealityViewContent) in
        content.entities.append(loadedGame.renderer.root)
      }.tabletopGame(loadedGame.tabletop, parent: loadedGame.renderer.root) { _ in
        GameInteraction(game: loadedGame)
      }
      
      
      // Define an object that manages player interactions.
      
      struct GameInteraction: TabletopInteraction {
        func update(context: TabletopKit.TabletopInteractionContext, 
                      value: TabletopKit.TabletopInteractionValue) {
          switch value.phase {
            //...
        }
    • 10:48 - Respond to interaction updates

      // Respond to interaction updates.
      
      func update(context: TabletopKit.TabletopInteractionContext, 
                    value: TabletopKit.TabletopInteractionValue) {
        switch value.phase {
          //...
          case .ended: {
            guard let dst = value.proposedDestination.equipmentID else {
              return
            }
            context.addAction(.moveEquipment(matching: value.startingEquipmentID, childOf: dst))
          }
       }
    • 12:52 - Add a sound effect to the die roll

      // Respond to interaction updates.
      
        func update(context: TabletopKit.TabletopInteractionContext, 
                      value: TabletopKit.TabletopInteractionValue) {
          switch value.gesturePhase {
            //...
            case .ended: {
              if let die = game.tabletop.equipment(of: Die.self, 
                                             matching: value.startingEquipmentID) {
                if let audioLibraryComponent = die.entity.components[AudioLibraryComponent.self] {
                  if let soundResource = audioLibraryComponent.resources["dieSoundShort.mp3"] {
                    die.entity.playAudio(soundResource)
                  }
                }
              }
            }
          }
        }
    • 14:44 - Set up multiplayer with SharePlay

      // Set up multiplayer using SharePlay.
      // Provide a button to begin SharePlay.
      
      import GroupActivities
      func shareplayButton() -> some View {
        Button("SharePlay", systemImage: "shareplay") {
        Task {try! await Activity().activate() }
        }
      }
      
      
      // After joining the SharePlay session, start multiplayer.
      
          sessionTask = Task.detached { @MainActor in
            for await session in Activity.sessions() {
              tabletopGame.coordinateWithSession(session)
            }
          }
  • 特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。

    クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。

Developer Footer

  • ビデオ
  • WWDC24
  • visionOS向けTabletopKitについて
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン