让我们来看看如何在使用 NativeScript 的 Vision Pro 开发中与多个场景交互并呈现粒子系统。我们将使用 @nativescript/swift-ui 提供简洁的 API,以按需打开和关闭场景,甚至使用上下文数据动态更新场景。
我们可以使用 Vision Pro 项目探索这些功能;我已经创建了一个项目,你可以参考这里
👉 演示 Github 仓库,该仓库可以在 Mac 上的 Vision Pro 模拟器或物理设备上运行。
如果您有物理 Vision Pro,您也可以通过在 Vision Pro 应用商店中搜索并安装“NativeScript 预览”应用来完整体验此功能,该应用将允许您使用此 StackBlitz
或者您也可以随时使用以下方法创建一个
npm i -g nativescript@vision
ns create myapp --vision
如果您想使用任何首选的 JavaScript 版本,请参考此处的 visionOS 文档。
为了支持多个场景,您始终需要检查您的 App_Resources/visionOS/Info.plist
,以确保它至少包含此设置
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
我们通过其唯一的标识符和 @nativescript/swift-ui API 与场景交互
openScene(options: OpenSceneOptions)
: 切换打开/关闭场景updateScene(options: OpenSceneOptions)
: 向任何打开的场景更新上下文数据OpenSceneOptions
接口如下所示
export interface OpenSceneOptions {
/**
* The id of the scene to open
*/
id: string;
/**
* Whether the scene is immersive or not.
* Only set this when you know the WindowGroup is defined with immersive style.
*/
isImmersive?: boolean;
/**
* Any data bindings to pass to the scene.
*/
data?: any;
}
您可以根据需要扩展这些选项来为您的 data
强类型化。让我们探索一个示例。
首先,您需要通过 App_Resources/visionOS/src/NativeScriptApp.swift
定义您希望应用程序提供的任意数量的场景
import SwiftUI
@main
struct NativeScriptApp: App {
var body: some Scene {
NativeScriptMainWindow()
// NEW SCENE: A distinct window of a playable video
WindowGroup(id: "Video") {
VideoSceneView()
}
.windowStyle(.plain)
}
}
在这里,我们定义了一个具有 id
为 'Video' 的 WindowGroup,它具有普通的 windowStyle。
VideoSceneView
(在 示例仓库 中提供)是您如何在多场景应用程序中实现任意数量内容的良好示例。
我们可以从 NativeScript 应用程序中打开该场景,如下所示
import { openScene } from '@nativescript/swift-ui';
openScene({
id: 'Video'
});
视频播放器将在一个新窗口中打开,该窗口可以放置在 3D 空间中的任何位置。再次调用 openScene
并使用相同的 id
将始终关闭具有匹配 id
的已打开场景。
您也可以在打开或更新场景时为任意数量的 SwiftUI @State 属性提供数据。
import { openScene } from '@nativescript/swift-ui';
openScene({
id: 'Video',
data: {
url: '<any link to a playable video resource, mp4, etc.>'
}
});
这允许 SwiftUI 如下所示使用上下文数据来执行任何需要操作的内容
struct VideoSceneView: View {
@State var context: NativeScriptSceneContext?
func createPlayer(url: String) -> AVPlayer {
let player = AVPlayer(url: URL(string: url)!)
player.play()
return player
}
var body: some View {
ZStack {
if context != nil {
VideoPlayer(player: player)
.scaledToFill()
} else {
EmptyView()
}
}.onAppear {
setupContext()
}
}
func setupContext() {
context = NativeScriptSceneRegistry.shared.getContextForId(id: "Video")
let url = context!.data["url"] as! String
player = createPlayer(url: url)
}
}
我们可以从 NativeScript 应用程序中动态更新该场景,如下所示
import { updateScene } from '@nativescript/swift-ui';
updateScene({
id: 'Video',
data: {
url: '<new url to play>'
}
});
我们可以通过添加一个 onReceive 修饰符来支持对任何场景视图的动态更新
var body: some View {
ZStack {
// video player
}.onReceive(NotificationCenter.default
.publisher(for: NSNotification.Name("NativeScriptUpdateScene")), perform: { obj in
// allow our scene data to be updated if already opened!
setupContext()
}).onAppear {
// upon first open
setupContext()
}
}
visionOS 中 粒子发射器 的首次亮相为我们带来了一个激动人心的 API。让我们看看如何通过沉浸式空间与粒子交互。
一个 沉浸式空间 是另一种类型的场景,我们可以使用 RealityKit 在其中显示粒子系统。
让我们看看如何使用 ParticleEmitterComponent 以及探索 Vortex,它是 @twostraws 开发的一个 Swift 包,它带来了一些不错的补充。
我们可以修改我们的 nativescript.config.ts
来包含用于 visionos 的 SPM(Swift 包管理器)
visionos: {
SPMPackages: [
{
name: 'Vortex',
libs: ['Vortex'],
repositoryURL: 'https://github.com/twostraws/Vortex.git',
version: '1.0.0'
}
]
}
然后,我们可以尝试从 Vortex 中使用一个示例视图设置
struct FirefliesView: View {
var body: some View {
VortexViewReader { proxy in
ZStack(alignment: .bottom) {
VortexView(.fireflies.makeUniqueCopy()) {
Circle()
.fill(.white)
.frame(width: 32)
.blur(radius: 3)
.blendMode(.plusLighter)
.tag("circle")
}
}
}
}
}
让我们添加一个要打开的新场景
@main
struct NativeScriptApp: App {
var body: some Scene {
NativeScriptMainWindow()
WindowGroup(id: "Fireflies") {
FirefliesView()
}
.windowStyle(.plain)
我们现在可以通过 NativeScript 应用程序中的 openScene
打开动态粒子效果
import { openScene } from '@nativescript/swift-ui';
openScene({
id: 'Fireflies'
});
现在让我们尝试添加一个带有粒子的沉浸式空间
@main
struct NativeScriptApp: App {
@State private var immersionStyle: ImmersionStyle = .mixed
var body: some Scene {
NativeScriptMainWindow()
ImmersiveSpace(id: "ParticleEmitter") {
ParticleEmitterView()
}
.immersionStyle(selection: $immersionStyle, in: .mixed, .full)
我们可以设置我们的 ParticleEmitterView
来也支持来自 NativeScript 应用程序的动态数据
struct ParticleEmitterView: View {
@State var context: NativeScriptSceneContext?
@State var preset: String = "magic"
var body: some View {
ZStack {
if context != nil {
RealityView { content in
var particles = ParticleEmitterComponent.Presets.magic
switch preset {
case "fireworks":
particles = ParticleEmitterComponent.Presets.fireworks
default:
particles = ParticleEmitterComponent.Presets.magic
}
let particleEntity = Entity()
particleEntity.components[ParticleEmitterComponent.self] = particles
content.add(particleEntity)
}
} else {
EmptyView()
}
}.onAppear {
setupContext()
}
}
func setupContext() {
context = NativeScriptSceneRegistry.shared.getContextForId(id: "ParticleEmitter")
preset = context!.data["preset"] as! String
}
}
我们可以在 NativeScript 应用程序中再次使用 openScene
,但这次我们将使用 isImmersive
选项来指定这专门用于打开沉浸式空间
import { openScene } from '@nativescript/swift-ui';
openScene({
id: 'ParticleEmitter',
isImmersive: true,
data: {
preset: 'fireworks'
}
});
太棒了!
nStudio 致力于建立一个健康的开源治理模型,以服务于围绕 NativeScript 的全球社区利益。如果您需要项目的专业协助,nStudio 提供跨多个学科的服务,您可以通过以下方式联系我们:[email protected].