A Swift package for managing 3D entities and scenes in RealityKit applications.
Why use this package?
This package provides a streamlined way to manage multiple 3D entities in visionOS applications. It handles the complexity of loading, positioning, and managing multiple models in a scene, making it easy to create rich spatial experiences. Perfect for applications that need to display and interact with multiple 3D objects simultaneously.
- Scene management with entity configurations
- Support for animations, physics, and interactions
- Easy integration with RealityKit and SwiftUI
- Thread-safe entity management
- Memory-efficient resource handling
import SwiftUI
import RealityKit
import DicyaninEntityManagement
struct MySceneView: View {
var body: some View {
// Simple usage with default scene and default entity
DicyaninEntityView()
// Use default entity configuration with custom scene details
DicyaninEntityView(
sceneId: "my_scene",
sceneName: "My First Scene",
sceneDescription: "A simple scene with default entity"
)
// Use custom entity configuration
DicyaninEntityView(
sceneId: "custom_scene",
sceneName: "Custom Scene",
sceneDescription: "A scene with custom entities",
entityConfigurations: [
DicyaninEntityConfiguration(
name: "spinning_cube",
position: SIMD3<Float>(0, 0, -1),
scale: SIMD3<Float>(repeating: 0.3),
animation: ModelAnimation(type: .spin(speed: 2.0, axis: SIMD3<Float>(0, 1, 0)))
)
]
)
// Use default entity configuration with custom handlers
DicyaninEntityView(
onLoadingStateChanged: { isLoading in
print("Loading state: \(isLoading)")
},
onError: { error in
print("Error occurred: \(error)")
},
onEntitiesLoaded: { entities in
print("Loaded \(entities.count) entities")
}
)
}
}Add the package to your Xcode project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/hunterh37/DicyaninEntityManagement.git", from: "0.0.1"),
.package(url: "https://github.com/hunterh37/DicyaninEntity.git", from: "0.0.1") // Required dependency
]Note: This package requires
DicyaninEntityas a dependency. Make sure to include both packages in your project.
The core class responsible for managing scenes and entities.
public class DicyaninEntityManager {
/// The root entity that contains all scenes
let rootEntity: Entity
/// Currently loaded scene
private(set) public var currentScene: DicyaninScene?
/// Returns the total number of loaded scenes
public var sceneCount: Int
/// Loads a scene configuration
/// - Parameter scene: The scene configuration to load
/// - Returns: Array of created entities
@MainActor
public func loadScene(_ scene: DicyaninScene) async throws -> [DicyaninEntity]
/// Unloads a scene and removes its entities
/// - Parameter scene: The scene to unload
@MainActor
public func unloadScene(_ scene: DicyaninScene) async throws
/// Returns all entities for a specific scene
/// - Parameter sceneId: The ID of the scene
/// - Returns: Array of DicyaninEntity objects for the scene
public func getEntitiesForScene(_ sceneId: String) -> [DicyaninEntity]?
}Represents a scene configuration containing multiple entities.
public struct DicyaninScene {
/// Unique identifier for the scene
public let id: String
/// Name of the scene for display purposes
public let name: String
/// Description of the scene
public let description: String
/// Array of entity configurations that make up this scene
public let entityConfigurations: [DicyaninEntityConfiguration]
}A builder for creating scene configurations with a fluent interface.
public struct DicyaninSceneBuilder {
/// Creates a new scene builder
/// - Parameters:
/// - id: Unique identifier for the scene
/// - name: Display name for the scene
/// - description: Description of the scene
public init(id: String, name: String, description: String)
/// Adds a single entity configuration to the scene
/// - Parameter configuration: The entity configuration to add
/// - Returns: The builder instance for method chaining
public func addEntity(_ configuration: DicyaninEntityConfiguration) -> DicyaninSceneBuilder
/// Adds multiple entity configurations to the scene
/// - Parameter configurations: Array of entity configurations to add
/// - Returns: The builder instance for method chaining
public func addEntities(_ configurations: [DicyaninEntityConfiguration]) -> DicyaninSceneBuilder
/// Builds the final scene configuration
/// - Returns: A DicyaninScene instance
public func build() -> DicyaninScene
}Protocol defining the requirements for a custom entity view provider.
public protocol DicyaninEntityViewProvider {
/// The scene configuration to be loaded
var scene: DicyaninScene { get }
/// Optional loading state handler
/// - Parameter isLoading: Boolean indicating if the scene is currently loading
var onLoadingStateChanged: ((Bool) -> Void)? { get }
/// Optional error handler
/// - Parameter error: The error that occurred during scene loading
var onError: ((Error) -> Void)? { get }
/// Optional entity loaded handler
/// - Parameter entities: Array of loaded entities
var onEntitiesLoaded: (([DicyaninEntity]) -> Void)? { get }
}A default implementation of DicyaninEntityViewProvider.
public struct DefaultDicyaninEntityViewProvider: DicyaninEntityViewProvider {
/// Creates a new default provider
/// - Parameters:
/// - scene: The scene configuration to load
/// - onLoadingStateChanged: Optional loading state handler
/// - onError: Optional error handler
/// - onEntitiesLoaded: Optional entity loaded handler
public init(
scene: DicyaninScene,
onLoadingStateChanged: ((Bool) -> Void)? = nil,
onError: ((Error) -> Void)? = nil,
onEntitiesLoaded: (([DicyaninEntity]) -> Void)? = nil
)
}A SwiftUI view for displaying and managing 3D entities.
public struct DicyaninEntityView: View {
/// Creates a new entity view
/// - Parameter provider: The provider to use for scene configuration
public init(provider: DicyaninEntityViewProvider = DefaultDicyaninEntityViewProvider(...))
}import DicyaninEntityManagement
import RealityKit
// Create a scene configuration
let scene = DicyaninScene(
id: "my_scene",
name: "My Scene",
description: "A scene with multiple entities",
entityConfigurations: [
// A bouncing cube
DicyaninEntityConfiguration(
name: "bouncing_cube",
position: SIMD3<Float>(0, 0, -1),
scale: SIMD3<Float>(repeating: 0.3),
animation: ModelAnimation(type: .bounce(height: 0.5, duration: 1.0))
),
// A physics-enabled sphere
DicyaninEntityConfiguration(
name: "physics_sphere",
position: SIMD3<Float>(1, 0, -1),
scale: SIMD3<Float>(repeating: 0.3),
physics: ModelPhysics(mass: 1.0, isDynamic: true),
collision: ModelCollision(shape: .sphere(radius: 0.3), isStatic: false)
)
]
)
// Create and load the scene
let entityManager = DicyaninEntityManager()
let entities = try await entityManager.loadScene(scene)
// Unload the scene when done
try await entityManager.unloadScene(scene)You can use the default implementation:
import SwiftUI
import RealityKit
import DicyaninEntityManagement
struct ContentView: View {
var body: some View {
// Use default scene and entity
DicyaninEntityView()
// Or customize just the scene details
DicyaninEntityView(
sceneId: "custom_default",
sceneName: "Custom Default",
sceneDescription: "A customized default scene"
)
// Or use custom entities
DicyaninEntityView(
sceneId: "custom_entities",
sceneName: "Custom Entities",
sceneDescription: "A scene with custom entities",
entityConfigurations: [
DicyaninEntityConfiguration(
name: "custom_entity",
position: SIMD3<Float>(0, 0, -1),
scale: SIMD3<Float>(repeating: 0.5)
)
]
)
}
}Or create your own custom implementation with the builder pattern:
import SwiftUI
import RealityKit
import DicyaninEntityManagement
// Create a custom scene provider
struct MySceneProvider: DicyaninEntityViewProvider {
let scene: DicyaninScene
// Optional handlers for scene lifecycle events
var onLoadingStateChanged: ((Bool) -> Void)?
var onError: ((Error) -> Void)?
var onEntitiesLoaded: (([DicyaninEntity]) -> Void)?
init(
sceneId: String,
sceneName: String,
sceneDescription: String,
entityConfigurations: [DicyaninEntityConfiguration],
onLoadingStateChanged: ((Bool) -> Void)? = nil,
onError: ((Error) -> Void)? = nil,
onEntitiesLoaded: (([DicyaninEntity]) -> Void)? = nil
) {
// Create scene using the builder pattern
self.scene = DicyaninSceneBuilder(id: sceneId, name: sceneName, description: sceneDescription)
.addEntities(entityConfigurations)
.build()
self.onLoadingStateChanged = onLoadingStateChanged
self.onError = onError
self.onEntitiesLoaded = onEntitiesLoaded
}
}
// Example usage in a view
struct CustomContentView: View {
private let entityConfigurations: [DicyaninEntityConfiguration] = [
DicyaninEntityConfiguration(
name: "custom_entity_1",
position: SIMD3<Float>(0, 0, -1),
scale: SIMD3<Float>(repeating: 0.5)
),
DicyaninEntityConfiguration(
name: "custom_entity_2",
position: SIMD3<Float>(1, 0, -1),
scale: SIMD3<Float>(repeating: 0.5)
)
]
var body: some View {
DicyaninEntityView(
provider: MySceneProvider(
sceneId: "custom_scene",
sceneName: "My Custom Scene",
sceneDescription: "A custom scene with multiple entities",
entityConfigurations: entityConfigurations,
onLoadingStateChanged: { isLoading in
print("Loading state: \(isLoading)")
},
onError: { error in
print("Error occurred: \(error)")
},
onEntitiesLoaded: { entities in
print("Loaded \(entities.count) entities")
}
)
)
}
}You can also use the builder pattern directly with the default provider:
struct BuilderExampleView: View {
var body: some View {
let scene = DicyaninSceneBuilder(
id: "builder_scene",
name: "Builder Scene",
description: "Scene created using the builder pattern"
)
.addEntity(DicyaninEntityConfiguration(
name: "entity_1",
position: SIMD3<Float>(0, 0, -1),
scale: SIMD3<Float>(repeating: 0.5)
))
.addEntity(DicyaninEntityConfiguration(
name: "entity_2",
position: SIMD3<Float>(1, 0, -1),
scale: SIMD3<Float>(repeating: 0.5)
))
.build()
return DicyaninEntityView(
provider: DefaultDicyaninEntityViewProvider(
scene: scene,
onLoadingStateChanged: { isLoading in
print("Loading state: \(isLoading)")
},
onError: { error in
print("Error occurred: \(error)")
},
onEntitiesLoaded: { entities in
print("Loaded \(entities.count) entities")
}
)
)
}
}Entities can be configured with various properties:
let config = DicyaninEntityConfiguration(
name: "my_entity",
position: SIMD3<Float>(0, 0, -1),
rotation: simd_quatf(angle: .pi/4, axis: SIMD3<Float>(0, 1, 0)),
scale: SIMD3<Float>(repeating: 0.5),
playAnimation: true,
animation: ModelAnimation(type: .spin(speed: 2.0, axis: SIMD3<Float>(0, 1, 0))),
physics: ModelPhysics(mass: 1.0, isDynamic: true),
collision: ModelCollision(shape: .box(size: SIMD3<Float>(repeating: 0.5)), isStatic: false)
)You can easily integrate this package with other packages by using the onEntityLoaded handler. This is called for each entity as it's loaded, allowing you to customize or extend the entity with functionality from other packages:
import SwiftUI
import RealityKit
import DicyaninEntityManagement
import YourOtherPackage
struct IntegratedSceneView: View {
var body: some View {
DicyaninEntityView(
onEntityLoaded: { entity in
// Integrate with another package
entity.enableCustomFeature() // From your other package
entity.setupCustomBehavior() // From your other package
// Or add custom components
entity.components[YourCustomComponent.self] = YourCustomComponent()
}
)
}
}This approach allows you to:
- Add custom functionality to each entity
- Integrate with other packages seamlessly
- Keep the integration code clean and maintainable
- Handle each entity individually as it's loaded
- visionOS 1.0+
- Xcode 15.0+
- Swift 5.9+
Hunter Harris. Copyright © 2025 Dicyanin Labs.
This project is licensed under the MIT License - see the LICENSE file for details.