我正在尝试在 iOS 18 中使用 RealityView 和 SwiftUI 制作 VR 立体 3d 视图
问题:两侧没有连接在一起,这意味着当左侧视图上的 3D 项目移动时,我看不到右侧的项目以相同的方式移动或根本没有移动
预期:两个 Sids 应与单个用户移动同步移动
这是到目前为止我的代码,
import SwiftUI
import RealityKit
struct ContentView: View {
@State var box = AnchorEntity()
@State var box2 = AnchorEntity()
@State private var rotateTime: Timer!
@State var rotateCounter: Float = 0.0
var body: some View {
HStack {
RealityView{ content in
let item = ModelEntity(mesh: .generateBox(size: .init(0.5, 0.5, 0.5)), materials: [SimpleMaterial(color: .white, isMetallic: false)])
box.addChild(item)
// rotate box on x and y 45 degrees
box.transform.rotation = simd_quatf(angle: .pi / 4, axis:[0,1,1])
content.add(box)
}
RealityView{ content in
content.add(box.clone(recursive: true))
}
}
.background(.black)
.onAppear(){
// After 5 Sec the animation starts
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5.0)
{
//-- Run Rotation Loop ----
rotateTime?.invalidate() // to make sure no instance for it before
rotateTime = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { _ in
rotateMe()
})
}
}
}
func rotateMe(){
rotateCounter += 1
if rotateCounter > 360 {
rotateCounter = 0.0
}
box.transform.rotation = simd_quatf(angle: (rotateCounter * (.pi / 180)), axis: [0,1,0])
}
}
如果每个锚实体都是唯一的,一切都会正常工作。
此外,
iOS 18
/iPadOS 18
模拟器无法正常工作。
使用设备!
import SwiftUI
import RealityKit
struct ContentView : View {
@State var anchor1 = AnchorEntity()
@State var anchor2 = AnchorEntity()
@State var rotateTime: Timer!
@State var rotateCounter: Float = 0.0
var body: some View {
HStack {
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)
}
RealityView { rvc in
let item = ModelEntity(mesh: .generateBox(size: 0.5),
materials: [SimpleMaterial()])
anchor2.addChild(item)
anchor2.orientation = .init(angle: .pi/4, axis:[0,1,1])
rvc.add(anchor2)
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
rotateTime?.invalidate()
rotateTime = Timer.scheduledTimer(
withTimeInterval: 0.01, repeats: true, block: { _ in
rotate(anchor1)
rotate(anchor2)
}
)
}
}
.background(.black)
}
func rotate(_ anchor: AnchorEntity) {
rotateCounter += 1
if rotateCounter > 360 { rotateCounter = 0.0 }
anchor.orientation = .init(angle: (rotateCounter * (.pi/180)), axis: [0,1,0])
}
}
感谢 Andy Jazz,我编辑了回复以允许一个 RealityView,但我仍然无法同时控制两个视图,现在我添加了一个拖动选项,但它分别适用于每个视图,并且双击可停止动画并重新启动它,它也可以单独工作仍然需要研究如何使控制同步在一起,我将检查碰撞是否也可以同步工作 这是代码
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()
}
}
}
}
由于在 VR 中我们无法触摸屏幕,所以这里是通过触摸和使用游戏控制器进行控制的代码
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
}
}
}
}
这是真实 iPhone11 与 iOS18 的结果照片