tabletopkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTabletopKit
TabletopKit
Create multiplayer spatial board games on a virtual table surface using
TabletopKit. Handles game layout, equipment interaction, player seating, turn
management, state synchronization, and RealityKit rendering. visionOS 2.0+
only. Targets Swift 6.3.
使用TabletopKit在虚拟桌面创建多人空间桌游。它提供游戏布局、设备交互、玩家座位、回合管理、状态同步和RealityKit渲染能力。仅支持visionOS 2.0+
版本,适配Swift 6.3。
Contents
目录
Setup
配置
Platform Requirement
平台要求
TabletopKit is exclusive to visionOS. It requires visionOS 2.0+. Multiplayer
features using Group Activities require visionOS 2.0+ devices on a FaceTime
call. The Simulator supports single-player layout testing but not multiplayer.
TabletopKit为visionOS专属框架,需要visionOS 2.0及以上版本。使用Group Activities的多人功能需要运行visionOS 2.0+的设备处于FaceTime通话中。模拟器仅支持单人布局测试,不支持多人功能。
Project Configuration
项目配置
- in source files that define game logic.
import TabletopKit - for entity-based rendering.
import RealityKit - For multiplayer, add the Group Activities capability in Signing & Capabilities.
- Provide 3D assets (USDZ) in a RealityKit content bundle for tables, pieces, cards, and dice.
- 在定义游戏逻辑的源文件中
import TabletopKit - 以实现基于实体的渲染
import RealityKit - 如需多人功能,需在「签名与功能」中添加Group Activities能力
- 在RealityKit内容包中提供桌面、棋子、卡牌、骰子的3D资源(USDZ格式)
Key Types Overview
核心类型概览
| Type | Role |
|---|---|
| Central game manager; owns setup, actions, observers, rendering |
| Configuration object passed to |
| Protocol for the table surface |
| Protocol for interactive game pieces |
| Protocol for player seat positions |
| Commands that modify game state |
| Gesture-driven player interactions with equipment |
| Callback protocol for reacting to confirmed actions |
| Callback protocol for visual updates |
| RealityKit-specific render delegate |
| Type | Role |
|---|---|
| 核心游戏管理器,负责配置、动作、观察者、渲染逻辑 |
| 传入 |
| 桌面的协议定义 |
| 可交互游戏道具的协议定义 |
| 玩家座位位置的协议定义 |
| 修改游戏状态的指令 |
| 玩家对道具的手势驱动交互 |
| 响应已确认动作的回调协议 |
| 处理视觉更新的回调协议 |
| 专属RealityKit的渲染委托 |
Game Configuration
游戏配置
Build a game in three steps: define the table, configure the setup, create the
instance.
TabletopGameswift
import TabletopKit
import RealityKit
let table = GameTable()
var setup = TableSetup(tabletop: table)
setup.add(seat: PlayerSeat(index: 0, pose: seatPose0))
setup.add(seat: PlayerSeat(index: 1, pose: seatPose1))
setup.add(equipment: GamePawn(id: .init(1)))
setup.add(equipment: GameDie(id: .init(2)))
setup.register(action: MyCustomAction.self)
let game = TabletopGame(tableSetup: setup)
game.claimAnySeat()Call each frame if automatic updates are not enabled via
the modifier. Read state safely with
.
update(deltaTime:).tabletopGame(_:parent:automaticUpdate:)withCurrentSnapshot(_:)构建游戏分三步:定义桌面、完成配置、创建实例。
TabletopGameswift
import TabletopKit
import RealityKit
let table = GameTable()
var setup = TableSetup(tabletop: table)
setup.add(seat: PlayerSeat(index: 0, pose: seatPose0))
setup.add(seat: PlayerSeat(index: 1, pose: seatPose1))
setup.add(equipment: GamePawn(id: .init(1)))
setup.add(equipment: GameDie(id: .init(2)))
setup.register(action: MyCustomAction.self)
let game = TabletopGame(tableSetup: setup)
game.claimAnySeat()如果未通过修饰器开启自动更新,需要每帧调用。可通过安全读取状态。
.tabletopGame(_:parent:automaticUpdate:)update(deltaTime:)withCurrentSnapshot(_:)Table and Board
桌面与棋盘
Tabletop Protocol
Tabletop协议
Conform to to define the playing surface. Provide a
(round or rectangular) and a RealityKit for visual representation.
EntityTabletopshapeEntityswift
struct GameTable: EntityTabletop {
var shape: TabletopShape
var entity: Entity
var id: EquipmentIdentifier
init() {
entity = try! Entity.load(named: "table/game_table", in: contentBundle)
shape = .round(entity: entity)
id = .init(0)
}
}遵循协议定义游戏桌面,需要提供(圆形或矩形)和用于视觉展示的RealityKit 。
EntityTabletopshapeEntityswift
struct GameTable: EntityTabletop {
var shape: TabletopShape
var entity: Entity
var id: EquipmentIdentifier
init() {
entity = try! Entity.load(named: "table/game_table", in: contentBundle)
shape = .round(entity: entity)
id = .init(0)
}
}Table Shapes
桌面形状
Use factory methods on :
TabletopShapeswift
// Round table from dimensions
let round = TabletopShape.round(
center: .init(x: 0, y: 0, z: 0),
radius: 0.5,
thickness: 0.05,
in: .meters
)
// Rectangular table from entity
let rect = TabletopShape.rectangular(entity: tableEntity)使用的工厂方法创建:
TabletopShapeswift
// Round table from dimensions
let round = TabletopShape.round(
center: .init(x: 0, y: 0, z: 0),
radius: 0.5,
thickness: 0.05,
in: .meters
)
// Rectangular table from entity
let rect = TabletopShape.rectangular(entity: tableEntity)Equipment (Pieces, Cards, Dice)
游戏道具(棋子、卡牌、骰子)
Equipment Protocol
Equipment协议
All interactive game objects conform to (or for
RealityKit-rendered pieces). Each piece has an () and
an property.
EquipmentEntityEquipmentidEquipmentIdentifierinitialStateChoose the state type based on the equipment:
| State Type | Use Case |
|---|---|
| Generic pieces, pawns, tokens |
| Playing cards (tracks |
| Dice with an integer |
| Custom data encoded as |
所有可交互游戏对象都遵循协议(RealityKit渲染的道具遵循协议)。每个道具都有(类型)和属性。根据道具类型选择对应的状态类型:
EquipmentEntityEquipmentidEquipmentIdentifierinitialState| State Type | Use Case |
|---|---|
| 通用道具、棋子、代币 |
| 扑克牌类道具(可跟踪 |
| 带有整数 |
| 编码为 |
Defining Equipment
定义道具
swift
// Pawn -- uses BaseEquipmentState
struct GamePawn: EntityEquipment {
var id: EquipmentIdentifier
var initialState: BaseEquipmentState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "pieces/pawn", in: contentBundle)
self.initialState = BaseEquipmentState(
parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
// Card -- uses CardState (tracks faceUp)
struct PlayingCard: EntityEquipment {
var id: EquipmentIdentifier
var initialState: CardState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "cards/card", in: contentBundle)
self.initialState = .faceDown(
parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
// Die -- uses DieState (tracks integer value)
struct GameDie: EntityEquipment {
var id: EquipmentIdentifier
var initialState: DieState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "dice/d6", in: contentBundle)
self.initialState = DieState(
value: 1, parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}swift
// Pawn -- uses BaseEquipmentState
struct GamePawn: EntityEquipment {
var id: EquipmentIdentifier
var initialState: BaseEquipmentState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "pieces/pawn", in: contentBundle)
self.initialState = BaseEquipmentState(
parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
// Card -- uses CardState (tracks faceUp)
struct PlayingCard: EntityEquipment {
var id: EquipmentIdentifier
var initialState: CardState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "cards/card", in: contentBundle)
self.initialState = .faceDown(
parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
// Die -- uses DieState (tracks integer value)
struct GameDie: EntityEquipment {
var id: EquipmentIdentifier
var initialState: DieState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "dice/d6", in: contentBundle)
self.initialState = DieState(
value: 1, parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}ControllingSeats
座位权限控制
Restrict which players can interact with a piece via :
seatControl- -- any player
.any - -- specific seats only
.restricted([seatID1, seatID2]) - -- only the seat whose turn it is
.current - -- inherits from parent equipment
.inherited
通过限制可交互该道具的玩家:
seatControl- -- 任意玩家
.any - -- 仅指定座位的玩家
.restricted([seatID1, seatID2]) - -- 仅当前回合的玩家
.current - -- 继承父道具的权限设置
.inherited
Equipment Hierarchy and Layout
道具层级与布局
Equipment can be parented to other equipment. Override
to position children. Return one of:
layoutChildren(for:visualState:)- -- cards/tiles stacked vertically
.planarStacked(layout:animationDuration:) - -- cards fanned or overlapping
.planarOverlapping(layout:animationDuration:) - -- full 3D layout
.volumetric(layout:animationDuration:)
See references/tabletopkit-patterns.md for card fan, grid, and overlap layout examples.
道具可以挂载到其他道具作为子节点。重写方法来定位子节点,可返回以下类型:
layoutChildren(for:visualState:)- -- 垂直堆叠的卡牌/ tile
.planarStacked(layout:animationDuration:) - -- 扇形展开或重叠的卡牌
.planarOverlapping(layout:animationDuration:) - -- 全3D布局
.volumetric(layout:animationDuration:)
查看references/tabletopkit-patterns.md获取扇形卡牌、网格、重叠布局示例。
Player Seats
玩家座位
Conform to and provide a pose around the table:
EntityTableSeatswift
struct PlayerSeat: EntityTableSeat {
var id: TableSeatIdentifier
var initialState: TableSeatState
var entity: Entity
init(index: Int, pose: TableVisualState.Pose2D) {
self.id = TableSeatIdentifier(index)
self.entity = Entity()
self.initialState = TableSeatState(pose: pose, context: 0)
}
}Claim a seat before interacting: , ,
or . Observe changes via .
game.claimAnySeat()game.claimSeat(matching:)game.releaseSeat()TabletopGame.Observer.playerChangedSeats遵循协议并提供围绕桌面的位姿:
EntityTableSeatswift
struct PlayerSeat: EntityTableSeat {
var id: TableSeatIdentifier
var initialState: TableSeatState
var entity: Entity
init(index: Int, pose: TableVisualState.Pose2D) {
self.id = TableSeatIdentifier(index)
self.entity = Entity()
self.initialState = TableSeatState(pose: pose, context: 0)
}
}交互前需要认领座位:、或。可通过监听座位变化。
game.claimAnySeat()game.claimSeat(matching:)game.releaseSeat()TabletopGame.Observer.playerChangedSeatsGame Actions and Turns
游戏动作与回合
Built-in Actions
内置动作
Use factory methods to modify game state:
TabletopActionswift
// Move equipment to a new parent
game.addAction(.moveEquipment(matching: pieceID, childOf: targetID, pose: newPose))
// Flip a card face-up
game.addAction(.updateEquipment(card, faceUp: true))
// Update die value
game.addAction(.updateEquipment(die, value: 6))
// Set whose turn it is
game.addAction(.setTurn(matching: TableSeatIdentifier(1)))
// Update a score counter
game.addAction(.updateCounter(matching: counterID, value: 100))
// Create a state bookmark (for undo/reset)
game.addAction(.createBookmark(id: StateBookmarkIdentifier(1)))使用工厂方法修改游戏状态:
TabletopActionswift
// Move equipment to a new parent
game.addAction(.moveEquipment(matching: pieceID, childOf: targetID, pose: newPose))
// Flip a card face-up
game.addAction(.updateEquipment(card, faceUp: true))
// Update die value
game.addAction(.updateEquipment(die, value: 6))
// Set whose turn it is
game.addAction(.setTurn(matching: TableSeatIdentifier(1)))
// Update a score counter
game.addAction(.updateCounter(matching: counterID, value: 100))
// Create a state bookmark (for undo/reset)
game.addAction(.createBookmark(id: StateBookmarkIdentifier(1)))Custom Actions
自定义动作
For game-specific logic, conform to :
CustomActionswift
struct CollectCoin: CustomAction {
let coinID: EquipmentIdentifier
let playerID: EquipmentIdentifier
init?(from action: some TabletopAction) {
// Decode from generic action
}
func validate(snapshot: TableSnapshot) -> Bool {
// Return true if action is legal
true
}
func apply(table: inout TableState) {
// Mutate state directly
}
}Register custom actions during setup:
swift
setup.register(action: CollectCoin.self)如需游戏专属逻辑,遵循协议:
CustomActionswift
struct CollectCoin: CustomAction {
let coinID: EquipmentIdentifier
let playerID: EquipmentIdentifier
init?(from action: some TabletopAction) {
// Decode from generic action
}
func validate(snapshot: TableSnapshot) -> Bool {
// Return true if action is legal
true
}
func apply(table: inout TableState) {
// Mutate state directly
}
}配置阶段注册自定义动作:
swift
setup.register(action: CollectCoin.self)Score Counters
计分器
swift
setup.add(counter: ScoreCounter(id: .init(0), value: 0))
// Update: game.addAction(.updateCounter(matching: .init(0), value: 42))
// Read: snapshot.counter(matching: .init(0))?.valueswift
setup.add(counter: ScoreCounter(id: .init(0), value: 0))
// Update: game.addAction(.updateCounter(matching: .init(0), value: 42))
// Read: snapshot.counter(matching: .init(0))?.valueState Bookmarks
状态书签
Save and restore game state for undo/reset:
swift
game.addAction(.createBookmark(id: StateBookmarkIdentifier(1)))
game.jumpToBookmark(matching: StateBookmarkIdentifier(1))保存和恢复游戏状态,用于撤销/重置功能:
swift
game.addAction(.createBookmark(id: StateBookmarkIdentifier(1)))
game.jumpToBookmark(matching: StateBookmarkIdentifier(1))Interactions
交互处理
TabletopInteraction.Delegate
TabletopInteraction.Delegate
Return an interaction delegate from the modifier to handle
player gestures on equipment:
.tabletopGameswift
.tabletopGame(game.tabletopGame, parent: game.renderer.root) { value in
if game.tabletopGame.equipment(of: GameDie.self, matching: value.startingEquipmentID) != nil {
return DieInteraction(game: game)
}
return DefaultInteraction(game: game)
}从修饰器返回交互委托,处理玩家对道具的手势操作:
.tabletopGameswift
.tabletopGame(game.tabletopGame, parent: game.renderer.root) { value in
if game.tabletopGame.equipment(of: GameDie.self, matching: value.startingEquipmentID) != nil {
return DieInteraction(game: game)
}
return DefaultInteraction(game: game)
}Handling Gestures and Tossing Dice
处理手势和掷骰子
swift
class DieInteraction: TabletopInteraction.Delegate {
let game: Game
func update(interaction: TabletopInteraction) {
switch interaction.value.phase {
case .started:
interaction.setConfiguration(.init(allowedDestinations: .any))
case .update:
if interaction.value.gesture?.phase == .ended {
interaction.toss(
equipmentID: interaction.value.controlledEquipmentID,
as: .cube(height: 0.02, in: .meters)
)
}
case .ended, .cancelled:
break
}
}
func onTossStart(interaction: TabletopInteraction,
outcomes: [TabletopInteraction.TossOutcome]) {
for outcome in outcomes {
let face = outcome.tossableRepresentation.face(for: outcome.restingOrientation)
interaction.addAction(.updateEquipment(
die, rawValue: face.rawValue, pose: outcome.pose
))
}
}
}swift
class DieInteraction: TabletopInteraction.Delegate {
let game: Game
func update(interaction: TabletopInteraction) {
switch interaction.value.phase {
case .started:
interaction.setConfiguration(.init(allowedDestinations: .any))
case .update:
if interaction.value.gesture?.phase == .ended {
interaction.toss(
equipmentID: interaction.value.controlledEquipmentID,
as: .cube(height: 0.02, in: .meters)
)
}
case .ended, .cancelled:
break
}
}
func onTossStart(interaction: TabletopInteraction,
outcomes: [TabletopInteraction.TossOutcome]) {
for outcome in outcomes {
let face = outcome.tossableRepresentation.face(for: outcome.restingOrientation)
interaction.addAction(.updateEquipment(
die, rawValue: face.rawValue, pose: outcome.pose
))
}
}
}Tossable Representations
可投掷对象表示
Dice physics shapes: (d6), (d4), (d8),
(d10), (d12), (d20), .
All take (or for sphere) and optional .
.cube.tetrahedron.octahedron.decahedron.dodecahedron.icosahedron.sphereheight:in:radius:in:restitution:骰子物理形状包括:(六面骰)、(四面骰)、(八面骰)、(十面骰)、(十二面骰)、(二十面骰)、(球形)。所有类型都支持传入(球形传)和可选的(弹性系数)参数。
.cube.tetrahedron.octahedron.decahedron.dodecahedron.icosahedron.sphereheight:in:radius:in:restitution:Programmatic Interactions
代码触发交互
Start interactions from code: .
game.startInteraction(onEquipmentID: pieceID)See references/tabletopkit-patterns.md for group
toss, predetermined outcomes, interaction acceptance/rejection, and destination
restriction patterns.
通过代码启动交互:。查看references/tabletopkit-patterns.md获取群体投掷、预设结果、交互接受/拒绝、目的地限制等模式示例。
game.startInteraction(onEquipmentID: pieceID)RealityKit Rendering
RealityKit渲染
Conform to to bridge state to RealityKit. Provide a
entity. TabletopKit automatically positions entities.
EntityRenderDelegaterootEntityEquipmentswift
class GameRenderer: EntityRenderDelegate {
let root = Entity()
func onUpdate(timeInterval: Double, snapshot: TableSnapshot,
visualState: TableVisualState) {
// Custom visual updates beyond automatic positioning
}
}Connect to SwiftUI with on a
:
.tabletopGame(_:parent:automaticUpdate:)RealityViewswift
struct GameView: View {
let game: Game
var body: some View {
RealityView { content in
content.entities.append(game.renderer.root)
}
.tabletopGame(game.tabletopGame, parent: game.renderer.root) { value in
GameInteraction(game: game)
}
}
}Debug outlines:
game.tabletopGame.debugDraw(options: [.drawTable, .drawSeats, .drawEquipment])遵循协议将状态桥接到RealityKit,提供实体,TabletopKit会自动调整实体的位置。
EntityRenderDelegaterootEntityEquipmentswift
class GameRenderer: EntityRenderDelegate {
let root = Entity()
func onUpdate(timeInterval: Double, snapshot: TableSnapshot,
visualState: TableVisualState) {
// Custom visual updates beyond automatic positioning
}
}在上使用修饰器连接到SwiftUI:
RealityView.tabletopGame(_:parent:automaticUpdate:)swift
struct GameView: View {
let game: Game
var body: some View {
RealityView { content in
content.entities.append(game.renderer.root)
}
.tabletopGame(game.tabletopGame, parent: game.renderer.root) { value in
GameInteraction(game: game)
}
}
}调试轮廓线:
game.tabletopGame.debugDraw(options: [.drawTable, .drawSeats, .drawEquipment])Group Activities Integration
Group Activities集成
TabletopKit integrates directly with GroupActivities for FaceTime-based
multiplayer. Define a , then call .
TabletopKit automatically synchronizes all equipment state, seat assignments,
actions, and interactions. No manual message passing required.
GroupActivitycoordinateWithSession(_:)swift
import GroupActivities
struct BoardGameActivity: GroupActivity {
var metadata: GroupActivityMetadata {
var meta = GroupActivityMetadata()
meta.type = .generic
meta.title = "Board Game"
return meta
}
}
@Observable
class GroupActivityManager {
let tabletopGame: TabletopGame
private var sessionTask: Task<Void, Never>?
init(tabletopGame: TabletopGame) {
self.tabletopGame = tabletopGame
sessionTask = Task { @MainActor in
for await session in BoardGameActivity.sessions() {
tabletopGame.coordinateWithSession(session)
}
}
}
deinit { tabletopGame.detachNetworkCoordinator() }
}Implement for ,
, , and
. See
references/tabletopkit-patterns.md for custom
network coordinators and arbiter role management.
TabletopGame.MultiplayerDelegatejoinAccepted()playerJoined(_:)didRejectPlayer(_:reason:)multiplayerSessionFailed(reason:)TabletopKit可直接集成GroupActivities实现基于FaceTime的多人功能。定义后调用即可,TabletopKit会自动同步所有道具状态、座位分配、动作和交互,无需手动传递消息。
GroupActivitycoordinateWithSession(_:)swift
import GroupActivities
struct BoardGameActivity: GroupActivity {
var metadata: GroupActivityMetadata {
var meta = GroupActivityMetadata()
meta.type = .generic
meta.title = "Board Game"
return meta
}
}
@Observable
class GroupActivityManager {
let tabletopGame: TabletopGame
private var sessionTask: Task<Void, Never>?
init(tabletopGame: TabletopGame) {
self.tabletopGame = tabletopGame
sessionTask = Task { @MainActor in
for await session in BoardGameActivity.sessions() {
tabletopGame.coordinateWithSession(session)
}
}
}
deinit { tabletopGame.detachNetworkCoordinator() }
}实现协议处理、、和等回调。查看references/tabletopkit-patterns.md获取自定义网络协调器和仲裁者角色管理示例。
TabletopGame.MultiplayerDelegatejoinAccepted()playerJoined(_:)didRejectPlayer(_:reason:)multiplayerSessionFailed(reason:)Common Mistakes
常见问题
- Forgetting platform restriction. TabletopKit is visionOS-only. Do not conditionally compile for iOS/macOS; the framework does not exist there.
- Skipping seat claim. Players must call or
claimAnySeat()before interacting with equipment. Without a seat, actions are rejected.claimSeat(_:) - Mutating state outside actions. All state changes must go through
or
TabletopAction. Directly modifying equipment properties bypasses synchronization.CustomAction - Missing custom action registration. Custom actions must be registered with
before creating the
setup.register(action:). Unregistered actions are silently dropped.TabletopGame - Not handling action rollback. Actions are optimistically applied and can be
rolled back if validation fails on the arbiter. Implement
to revert UI state.
actionWasRolledBack(_:snapshot:) - Using wrong parent ID. Equipment in state must reference a valid equipment ID (typically the table or a container). An invalid parent causes the piece to disappear.
parentID - Ignoring TossOutcome faces. After a toss, read the face from
rather than generating a random value. The physics simulation determines the result.
outcome.tossableRepresentation.face(for: outcome.restingOrientation) - Testing multiplayer in Simulator. Group Activities do not work in Simulator. Multiplayer requires physical Apple Vision Pro devices on a FaceTime call.
- 忘记平台限制:TabletopKit仅支持visionOS,不要为iOS/macOS条件编译,这些平台不存在该框架。
- 未认领座位:玩家交互道具前必须调用或
claimAnySeat(),没有座位的动作会被拒绝。claimSeat(_:) - 在动作外修改状态:所有状态变更必须通过或
TabletopAction,直接修改道具属性会跳过同步逻辑。CustomAction - 未注册自定义动作:创建前必须通过
TabletopGame注册自定义动作,未注册的动作会被静默丢弃。setup.register(action:) - 未处理动作回滚:动作会被乐观应用,如果在仲裁者端验证失败会被回滚,需要实现来还原UI状态。
actionWasRolledBack(_:snapshot:) - 父ID错误:状态中的道具必须指向有效的道具ID(通常是桌面或容器),无效的父ID会导致道具消失。
parentID - 忽略TossOutcome的面数:投掷结束后,应该从读取面数,而不是生成随机值,结果由物理模拟决定。
outcome.tossableRepresentation.face(for: outcome.restingOrientation) - 在模拟器测试多人功能:模拟器不支持Group Activities,多人功能需要处于FaceTime通话中的物理Apple Vision Pro设备。
Review Checklist
检查清单
- present; target is visionOS 2.0+
import TabletopKit - created with a
TableSetup/Tabletopconforming typeEntityTabletop - All equipment conforms to or
Equipmentwith correct state typeEntityEquipment - Seats added and /
claimAnySeat()called at game startclaimSeat(_:) - All custom actions registered with
setup.register(action:) - implemented for reacting to confirmed actions
TabletopGame.Observer - or
EntityRenderDelegateconnectedRenderDelegate - modifier on
.tabletopGame(_:parent:automaticUpdate:)RealityView - defined and
GroupActivitycalled for multiplayercoordinateWithSession(_:) - Group Activities capability added in Xcode for multiplayer builds
- Debug visualization () disabled before release
debugDraw - Tested on device; multiplayer tested with 2+ Apple Vision Pro units
- 已引入,目标平台为visionOS 2.0+
import TabletopKit - 已使用遵循/
Tabletop协议的类型创建EntityTabletopTableSetup - 所有道具都遵循或
Equipment协议,并使用正确的状态类型EntityEquipment - 已添加座位,且游戏启动时调用了/
claimAnySeat()claimSeat(_:) - 所有自定义动作都通过注册
setup.register(action:) - 已实现响应已确认的动作
TabletopGame.Observer - 已连接或
EntityRenderDelegateRenderDelegate - 上已添加
RealityView修饰器.tabletopGame(_:parent:automaticUpdate:) - 已定义,多人模式下调用了
GroupActivitycoordinateWithSession(_:) - 多人构建版本已在Xcode中添加Group Activities能力
- 发布前已禁用调试可视化()
debugDraw - 已在真机测试,多人功能使用2台及以上Apple Vision Pro设备测试通过
References
参考资料
- references/tabletopkit-patterns.md -- extended patterns for observer implementation, custom actions, dice simulation, card overlap, and network coordination
- Apple Documentation: TabletopKit
- Creating tabletop games (sample code)
- Synchronizing group gameplay with TabletopKit (sample code)
- Simulating dice rolls as a component for your game (sample code)
- Implementing playing card overlap and physical characteristics (sample code)
- WWDC24 session 10091: Build a spatial board game
- references/tabletopkit-patterns.md -- 观察者实现、自定义动作、骰子模拟、卡牌重叠、网络协调等扩展模式
- Apple官方文档:TabletopKit
- 创建桌游(示例代码)
- 使用TabletopKit同步多人游戏(示例代码)
- 为游戏实现骰子投掷组件(示例代码)
- 实现扑克牌重叠和物理特性(示例代码)
- WWDC24 session 10091:构建空间桌游