Streaming is available in most browsers,
and in the Developer app.
-
Meet TabletopKit for visionOS
Build a board game for visionOS from scratch using TabletopKit. We'll show you how to set up your game, add powerful rendering using RealityKit, and enable multiplayer using spatial Personas in FaceTime with only a few extra lines of code.
Chapters
- 0:00 - Introduction
- 2:37 - Set up the play surface
- 7:45 - Implement rules
- 12:01 - Integrate RealityKit effects
- 13:30 - Configure multiplayer
Resources
Related Videos
WWDC24
- Compose interactive 3D content in Reality Composer Pro
- Customize spatial Persona templates in SharePlay
WWDC23
-
DownloadArray
-
-
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) } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.