You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 18/MacOS 15下同步控制双Stereo VR RealityView的方法

问题:双视图Stereo VR场景下同步控制两侧实体的实现方法

在iOS 18/MacOS 15环境中,通过HStack两次调用MainView实现双视图Stereo VR方案,但目前拖拽手势、双击启停动画、Game Controller控制均仅能单独作用于单个视图,无法同步控制两个视图内的实体。现寻求通过触摸或Game Controller同步控制两侧实体的实现方法。


触摸控制版本代码

import SwiftUI
import RealityKit

struct ContentView : View {
  var body: some View {
    HStack {
        MainView()
        MainView()
    }
    .background(.black)
  }
}


struct MainView : View {
  @State var anchor1 = AnchorEntity()
  @State var rotateTime: Timer!
  @State var rotateCounter: Float = 0.0

  @State var animateMode = false

  @State var position: UnitPoint = .zero

  let ratio: Float = 0.005

  var body: some View {
    RealityView { rvc in
        let item = ModelEntity(mesh: .generateBox(size: 0.5), materials: [SimpleMaterial()])
        anchor1.addChild(item)
        anchor1.orientation = .init(angle: .pi/4, axis:[0,1,1])
        rvc.add(anchor1)
    }
    .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            rotateTime?.invalidate()
            rotateTime = Timer.scheduledTimer(
                withTimeInterval: 0.01, repeats: true, block: { _ in
                    rotate(anchor1)
                }
            )
        }
    }
    .gesture(dragThis)
    .gesture(doubleClickThis)
}

func rotate(_ anchor: AnchorEntity) {
    rotateCounter += 1
    
    if rotateCounter >= 360 { rotateCounter = 0.0 }
    
    anchor.orientation = .init(angle: (rotateCounter * (.pi/180)), axis: [0,1,0])
}

var dragThis: some Gesture {
    DragGesture(minimumDistance: 15, coordinateSpace: .global)
        .onChanged { value in
            print(value.translation)
            print(value.location)
            
            anchor1.position.x = Float(value.translation.width + position.x) * ratio
            anchor1.position.y = Float(value.translation.height + position.y) * -ratio
            
        }
        .onEnded{value in
            position.x += value.translation.width
            position.y += value.translation.height
        }
}

var doubleClickThis: some Gesture {
    TapGesture(count: 2)
        .onEnded { value in
            animateMode.toggle()
            
            if animateMode {
                rotateTime?.invalidate()
                rotateTime = Timer.scheduledTimer(
                    withTimeInterval: 0.01, repeats: true, block: { _ in
                        rotate(anchor1)
                        //rotate(anchor2)
                    }
                )
            }
            else
            {
                rotateTime?.invalidate()
            }
        }
  }
}

支持触摸和Game Controller的版本代码

import SwiftUI
import RealityKit
import GameController

struct ContentView : View {
var body: some View {
    HStack {
        MainView()
        MainView()
    }
    .background(.black)
}
}


struct MainView : View {
@State var anchor1 = AnchorEntity()
@State var rotateTime: Timer!
@State var rotateCounter: Float = 0.0

@State var animateMode = false

@State var position: UnitPoint = .zero

@State var gameController: GCController!

@State private var stickTime: Timer!

@State var SRDelayer = false

let ratio: Float = 0.005

var body: some View {
    RealityView { rvc in
        let item = ModelEntity(mesh: .generateBox(size: 0.5), materials: [SimpleMaterial()])
        anchor1.addChild(item)
        anchor1.orientation = .init(angle: .pi/4, axis:[0,1,1])
        rvc.add(anchor1)
    }
    .onAppear {
        setupGameController()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            rotateTime?.invalidate()
            rotateTime = Timer.scheduledTimer(
                withTimeInterval: 0.01, repeats: true, block: { _ in
                    rotate(anchor1)
                }
            )
        }
    }
    .gesture(dragThis)
    .gesture(doubleClickThis)
}

func rotate(_ anchor: AnchorEntity) {
    rotateCounter += 1
    
    if rotateCounter >= 360 { rotateCounter = 0.0 }
    
    anchor.orientation = .init(angle: (rotateCounter * (.pi/180)), axis: [0,1,0])
}

var dragThis: some Gesture {
    DragGesture(minimumDistance: 15, coordinateSpace: .global)
        .onChanged { value in
            print(value.translation)
            print(value.location)
            
            anchor1.position.x = Float(value.translation.width + position.x) * ratio
            anchor1.position.y = Float(value.translation.height + position.y) * -ratio
            
        }
        .onEnded{value in
            position.x += value.translation.width
            position.y += value.translation.height
        }
}

var doubleClickThis: some Gesture {
    TapGesture(count: 2)
        .onEnded { value in
            animateMode.toggle()
            playStop()
        }
}

func playStop() {
    if animateMode {
        rotateTime?.invalidate()
        rotateTime = Timer.scheduledTimer(
            withTimeInterval: 0.01, repeats: true, block: { _ in
                rotate(anchor1)
                //rotate(anchor2)
            }
        )
    }
    else
    {
        rotateTime?.invalidate()
    }
}

//MARK: - GameController

func setupGameController() {
    
    NotificationCenter.default.addObserver(forName: NSNotification.Name.GCControllerDidBecomeCurrent, object: nil, queue: nil, using: didConnectController)
    
    NotificationCenter.default.addObserver(forName: NSNotification.Name.GCControllerDidStopBeingCurrent, object: nil, queue: nil, using: didDisconnectController)
    
    GCController.startWirelessControllerDiscovery{}
    
    guard let controller = GCController.controllers().first
    else
    {
        return
    }
    registerGameController(controller)
    
}

func didConnectController(_ notification: Notification) {
    
    gameController = notification.object as? GCController
    
    print("Game Controller ◦ gameControllerIsConnected \(gameController.productCategory)")
    
    unregisterGameController()
    
    registerGameController(gameController)
}

func didDisconnectController(_ notification: Notification) {
    
    gameController = notification.object as? GCController
    print("◦ disgameControllerIsConnected \(gameController.productCategory)")
    
    unregisterGameController()
}

func registerGameController(_ gameController: GCController) {
    
    self.gameController = gameController // important for refresh screen
    
    gameController.extendedGamepad?.dpad.left.pressedChangedHandler = { (button, value, pressed) in self.button("Left", pressed) }
    gameController.extendedGamepad?.dpad.up.pressedChangedHandler = { (button, value, pressed) in self.button("Up", pressed) }
    gameController.extendedGamepad?.dpad.right.pressedChangedHandler = { (button, value, pressed) in self.button("Right", pressed) }
    gameController.extendedGamepad?.dpad.down.pressedChangedHandler = { (button, value, pressed) in self.button("Down", pressed) }
    gameController.extendedGamepad?.buttonX.pressedChangedHandler = { (button, value, pressed) in self.button("Square", pressed) }
    gameController.extendedGamepad?.buttonY.pressedChangedHandler = { (button, value, pressed) in self.button("Triangle", pressed) }
    gameController.extendedGamepad?.buttonB.pressedChangedHandler = { (button, value, pressed) in self.button("Circle", pressed) }
    gameController.extendedGamepad?.buttonA.pressedChangedHandler = { (button, value, pressed) in self.button("Cross", pressed) }
    gameController.extendedGamepad?.buttonOptions?.pressedChangedHandler = { (button, value, pressed) in self.button("Options", pressed) }
    gameController.extendedGamepad?.buttonMenu.pressedChangedHandler = { (button, value, pressed) in self.button("Menu", pressed) }
    gameController.extendedGamepad?.leftThumbstickButton?.pressedChangedHandler = { (button, value, pressed) in self.button("LeftThumbClick", pressed) }
    gameController.extendedGamepad?.rightThumbstickButton?.pressedChangedHandler = { (button, value, pressed) in self.button("RightThumbClick", pressed) }
    gameController.extendedGamepad?.leftShoulder.pressedChangedHandler = { (button, value, pressed) in self.button("LeftShoulder", pressed) }
    gameController.extendedGamepad?.rightShoulder.pressedChangedHandler = { (button, value, pressed) in self.button("RightShoulder", pressed) }
    gameController.extendedGamepad?.leftTrigger.pressedChangedHandler = { (button, value, pressed) in self.button("LeftTriggerClick", pressed)}
    gameController.extendedGamepad?.rightTrigger.pressedChangedHandler = { (button, value, pressed) in self.button("RightTriggerClick", pressed) }
    gameController.extendedGamepad?.leftTrigger.valueChangedHandler = { (button, value, pressed) in self.triggerL("LeftTriggerValue", value) }
    gameController.extendedGamepad?.rightTrigger.valueChangedHandler = { (button, value, pressed) in self.triggerR("RightTriggerValue", value) }
    gameController.extendedGamepad?.leftThumbstick.valueChangedHandler = { (button, xvalue, yvalue) in self.sticks("LeftThumbStick", xvalue, yvalue) }
    gameController.extendedGamepad?.rightThumbstick.valueChangedHandler = { (button, xvalue, yvalue) in self.sticks("RightThumbStick", xvalue, yvalue) }
    
}

func unregisterGameController() {
    
}

func button(_ button: String, _ pressed: Bool) {
    
    if button == "Cross"
    {
        if SRDelayer == false
        {
            SRDelayer = true
            
            animateMode.toggle()
            playStop()
        }
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0)
        {
            self.SRDelayer = false
        }
    }
    
    
    if button == "Up"
    {
        anchor1.position.y += 0.02
    }
    
    if button == "Down"
    {
        anchor1.position.y -= 0.02
    }
    
    if button == "Right"
    {
        anchor1.position.x += 0.02
    }
    
    if button == "Left"
    {
        anchor1.position.x -= 0.02
    }
}

func triggerR(_ button: String, _ value: Float) {

}

func triggerL(_ button: String, _ value: Float) {
}

func sticks(_ button: String, _ xvalue: Float, _ yvalue: Float) {
    if button == "RightThumbStick" || button == "LeftThumbStick"
    {
        if yvalue > 0.5 || yvalue < -0.5  || xvalue > 0.5 || xvalue < -0.5
        {
            if stickTime == nil // if its not running
            {
                stickTime?.invalidate()
                
                stickTime = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { _ in
                    pollInputSticks()
                })
            }
        }
        else
        {
            stickTime?.invalidate()
            stickTime = nil
        }
    }
    else
    {
        stickTime?.invalidate()
        stickTime = nil
    }
}

func pollInputSticks() {
    if let gamePadRight = gameController.extendedGamepad?.rightThumbstick
    {
        if gamePadRight.yAxis.value > 0.5 // Up
        {
            anchor1.position.y += 0.002
        }
        else if gamePadRight.yAxis.value < -0.5 // Down
        {
            anchor1.position.y -= 0.002
        }
        
        if gamePadRight.xAxis.value > 0.5 // Right
        {
            anchor1.position.x += 0.002
        }
        else if gamePadRight.xAxis.value < -0.5 // Left
        {
            anchor1.position.x -= 0.002
        }
    }
    
    if let gamePadLeft = gameController.extendedGamepad?.leftThumbstick
    {
        if gamePadLeft.yAxis.value > 0.5 // Up
        {
            anchor1.position.z -= 0.002
        }
        else if gamePadLeft.yAxis.value < -0.5 // Down
        {
            anchor1.position.z += 0.002
        }
        
        if gamePadLeft.xAxis.value > 0.5 // Right
        {
            anchor1.position.x += 0.002
        }
        else if gamePadLeft.xAxis.value < -0.5 // Left
        {
            anchor1.position.x -= 0.002
        }
    }
}

}

iPhone 11(iOS 18)实际运行效果

  • 无控制状态
    无控制状态
  • 触摸控制状态
    触摸控制状态
  • Game Controller控制状态
    Game Controller控制状态

内容的提问来源于stack exchange,提问作者Ostoura

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 15:59:55