diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7de454f34..e37b1efd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: echo "" echo "git clone https://github.com/untoldengine/UntoldEngine.git" echo "cd UntoldEngine" - echo "swift run DemoGame" + echo "swift run untoldsandbox" echo "" echo "This will build the engine and launch the demo using Swift Package Manager." echo "" diff --git a/Package.swift b/Package.swift index bbea4af15..c248abe08 100644 --- a/Package.swift +++ b/Package.swift @@ -55,7 +55,13 @@ let package = Package( .library(name: "UntoldEngineAR", targets: ["UntoldEngineAR"]), - // Executable for the demo game + // Executable for the sandbox app (primary name) + .executable( + name: "untoldsandbox", + targets: ["DemoGame"] + ), + + // Backward-compatible executable alias .executable( name: "DemoGame", targets: ["DemoGame"] diff --git a/README.md b/README.md index be7894942..403c3d283 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ The project focuses on building a **clean, system-driven architecture** with mod The engine is under active development and continues to evolve as new systems and workflows are added. +![untoldengine-image](/docs/images/engine-highlight-1.png) + --- ## 🎯 Who is this for? @@ -61,6 +63,12 @@ Clone the repository, run the engine and load a USDZ file: ```bash git clone https://github.com/untoldengine/UntoldEngine.git cd UntoldEngine +swift run untoldsandbox +``` + +Legacy alias (prints deprecation warning): + +```bash swift run DemoGame ``` @@ -157,45 +165,59 @@ Untold Engine aims to support applications such as: # Set Up an Xcode Project with Untold Engine -You can easily create an xcode project using the Untold Engine as dependency +Use `untoldengine-create` to generate a ready-to-run Xcode project with Untold Engine wired in. Install it from the repository: ```bash git clone https://github.com/untoldengine/UntoldEngine.git cd UntoldEngine -./scripts/install-create.sh +./scripts/install-untoldengine-create.sh ``` -Then create a new project: +## Vision Pro Example ```bash -mkdir MyGame -cd MyGame -untoldengine-create create MyGame +mkdir VisionGame +cd VisionGame +untoldengine-create create VisionGame --platform visionos +open VisionGame.xcodeproj ``` -The CLI supports multiple platforms: +## What this creates for you + +- Xcode project + platform-specific app template files +- `GameData` folder structure (`Scenes`, `Scripts`, `Models`, `Textures`, etc.) +- Engine package dependencies configured for the selected platform +- Starter `GameScene` code showing how to: + - Load a mesh (`city.usdz`) + - Enable geometry streaming + - Enable static batching + +Note: `city.usdz` should be placed in `GameData/model` (generated folder name is `GameData/Models`). + +## Platform options ```bash +# visionOS (Apple Vision Pro) +untoldengine-create create MyGame --platform visionos + # macOS (default) untoldengine-create create MyGame --platform macos -# iOS -untoldengine-create create MyGame --platform ios - # iOS with ARKit untoldengine-create create MyGame --platform iosar -# visionOS (Apple Vision Pro) -untoldengine-create create MyGame --platform visionos +# iOS +untoldengine-create create MyGame --platform ios + ``` -Features: +Dependency behavior by platform: -- Create macOS, iOS, and visionOS projects -- Multi-platform project templates -- Automated project structure generation +- `visionos`: `UntoldEngineXR` + `UntoldEngineAR` +- `iosar`: `UntoldEngineAR` +- `ios` and `macos`: `UntoldEngine` --- diff --git a/Sources/DemoGame/main.swift b/Sources/DemoGame/main.swift index d1fca54f7..250c99bbf 100644 --- a/Sources/DemoGame/main.swift +++ b/Sources/DemoGame/main.swift @@ -4,6 +4,12 @@ #if os(macOS) import AppKit + import Foundation + + let executableName = URL(fileURLWithPath: CommandLine.arguments.first ?? "").lastPathComponent + if executableName == "DemoGame" { + fputs("[DEPRECATED] DemoGame is deprecated. Please use: swift run untoldsandbox\n", stderr) + } let app = NSApplication.shared let delegate = AppDelegate() diff --git a/Sources/UntoldEngine/BuildSystem/BuildTemplates.swift b/Sources/UntoldEngine/BuildSystem/BuildTemplates.swift index 080c25870..770fc00ee 100644 --- a/Sources/UntoldEngine/BuildSystem/BuildTemplates.swift +++ b/Sources/UntoldEngine/BuildSystem/BuildTemplates.swift @@ -102,60 +102,141 @@ import Foundation // Generated by UntoldEngine Build System // - import Combine import Foundation + import simd import UntoldEngine - @MainActor - final class EngineStatsWindowStore: ObservableObject { - static let shared = EngineStatsWindowStore() + // GameScene: Initialize your game and write game-specific logic + class GameScene { - @Published var advancedMode: Bool = false - @Published private(set) var compactText: String = "" - @Published private(set) var expandedText: String = "" + private var wasRightMousePressed: Bool = false - private var timer: Timer? + init() { + // Configure asset paths + setupAssetPaths() - private init() { - refresh() - timer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in - guard let self else { return } - Task { @MainActor in - self.refresh() + // Configure game Systems + configureEngineSystems() + + setSceneReady(false) + + // load a mesh + let entity = createEntity() + + // city.usdz must be inside the GameData/model folder (generated path: GameData/Models) + setEntityMeshAsync(entityId: entity, filename: "city", withExtension: "usdz"){_ in + + translateBy(entityId: entity, position: simd_float3(0.0,-10.0,0.0)) + + // Enable Geometry Streaming + enableStreaming( + entityId: entity, + streamingRadius: 40.0, + unloadRadius: 60.0, + priority: 10 + ) + + // Enable Static Batching + setEntityStaticBatch(entityId: entity) + enableBatching(true) + generateBatches() + + setSceneReady(true) + } + } + + // MARK: - Setup Methods + + /// Load and play the first available scene + private func loadAndPlayFirstScene(sceneName: String? = nil) { + setSceneReady(false) + if let sceneURL = findFirstScene(name: sceneName) { + playSceneAt(url: sceneURL) { + setSceneReady(true) + Logger.log(message: "✅ Scene loaded: \\(sceneURL.lastPathComponent)") } + } else { + setSceneReady(true) + Logger.log(message: "⚠️ No scene files found") } } - deinit { - timer?.invalidate() + /// Configure game Systems for play mode + private func configureEngineSystems() { + gameMode = true + AnimationSystem.shared.isEnabled = true + InputSystem.shared.registerKeyboardEvents() + InputSystem.shared.registerMouseEvents() } - private func refresh() { - let snapshot = getEngineStatsSnapshot() - compactText = formatEngineStatsCompact(snapshot) - expandedText = formatEngineStatsOverlay(snapshot) + // MARK: - Game Loop + + /// Called every frame - add custom game logic here + func update(deltaTime _: Float) { + // Skip logic if not in game mode + if gameMode == false { return } + + // Add your custom update logic here + } + + /// Called for input handling - add custom input logic here + func handleInput() { + // Skip logic if not in game mode + if gameMode == false { return } + if isSceneReady() == false {return} + + let input = InputSystem.shared + let camera = findGameCamera() + + moveCameraWithInput( + entityId: camera, + input: ( + w: input.keyState.wPressed, + a: input.keyState.aPressed, + s: input.keyState.sPressed, + d: input.keyState.dPressed, + q: input.keyState.qPressed, + e: input.keyState.ePressed + ), + speed: 1.0, + deltaTime: 0.1 + ) + + if input.keyState.rightMousePressed { + if !wasRightMousePressed { + setOrbitOffset(entityId: camera, uTargetOffset: 5.0) + } + var dx = input.mouseDeltaX + var dy = input.mouseDeltaY + if abs(dx) < abs(dy) { dx = 0 } else { dy = 0 } + orbitCameraAround(entityId: camera, uDelta: simd_float2(dx, dy)) + } + wasRightMousePressed = input.keyState.rightMousePressed + + // Add your custom input handling here } } + """ - // GameScene: Initialize your game and write game-specific logic - class GameScene { + // MARK: - GameSceneUtils.swift Template - init() { - // Configure asset paths - setupAssetPaths() + static let gameSceneUtilsSwift = """ + // + // GameSceneUtils.swift + // {{PROJECT_NAME}} + // + // Generated by UntoldEngine Build System + // - // Load game content - loadBundledScripts() - loadAndPlayGameScene() + import Foundation + import UntoldEngine - // Start game systems - startGameSystems() - } + extension GameScene { - // MARK: - Setup Methods + // MARK: - Asset Setup /// Configure the asset base path for the bundled GameData - private func setupAssetPaths() { + func setupAssetPaths() { if let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) { assetBasePath = gameDataURL Logger.log(message: "✅ Found GameData at: \\(gameDataURL.path)") @@ -170,31 +251,8 @@ import Foundation } } - /// Load and play the first available scene - private func loadAndPlayGameScene(sceneName: String? = nil) { - setSceneReady(false) - if let sceneURL = findGameScene(name: sceneName) { - playSceneAt(url: sceneURL) { - setSceneReady(true) - Logger.log(message: "✅ Scene loaded: \\(sceneURL.lastPathComponent)") - } - } else { - setSceneReady(true) - Logger.log(message: "⚠️ No scene files found") - } - } - - /// Start game systems for play mode - private func startGameSystems() { - gameMode = true - AnimationSystem.shared.isEnabled = true - USCSystem.shared.startPlayMode() - } - - // MARK: - Asset Loading - /// Find a scene in GameData/Scenes by name, or the first JSON scene if no name is provided. - private func findGameScene(name: String? = nil) -> URL? { + func findFirstScene(name: String? = nil) -> URL? { guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { Logger.log(message: "⚠️ GameData directory not found") return nil @@ -222,7 +280,7 @@ import Foundation } /// Load all USC scripts from GameData/Scripts - private func loadBundledScripts() { + func loadBundledScripts() { guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { return } @@ -231,25 +289,6 @@ import Foundation let count = loadScripts(from: scriptsURL) Logger.log(message: "✅ Loaded \\(count) script(s) from bundle") } - - // MARK: - Game Loop - - /// Called every frame - add custom game logic here - func update(deltaTime _: Float) { - // Skip logic if not in game mode - if gameMode == false { return } - - // Add your custom update logic here - } - - /// Called for input handling - add custom input logic here - func handleInput() { - // Skip logic if not in game mode - if gameMode == false { return } - if isSceneReady() == false { return } - - // Add your custom input handling here - } } """ @@ -613,6 +652,7 @@ import Foundation // import Foundation + import simd import UntoldEngine // GameScene: Initialize your game and write game-specific logic @@ -622,36 +662,42 @@ import Foundation // Configure asset paths setupAssetPaths() - // Load game content - loadBundledScripts() - loadAndPlayGameScene() + // Configure game Systems + configureEngineSystems() - // Start game systems - startGameSystems() - } + setSceneReady(false) - // MARK: - Setup Methods + // load a mesh + let entity = createEntity() - /// Configure the asset base path for the bundled GameData - private func setupAssetPaths() { - if let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) { - assetBasePath = gameDataURL - Logger.log(message: "✅ Found GameData at: \\(gameDataURL.path)") - } else if let resourceURL = Bundle.main.resourceURL { - let gameDataPath = resourceURL.appendingPathComponent("GameData") - if FileManager.default.fileExists(atPath: gameDataPath.path) { - assetBasePath = gameDataPath - Logger.log(message: "✅ Found GameData in Resources: \\(gameDataPath.path)") - } else { - Logger.log(message: "⚠️ GameData not found. Bundle resources: \\(resourceURL.path)") - } + // city.usdz must be inside the GameData/model folder (generated path: GameData/Models) + setEntityMeshAsync(entityId: entity, filename: "city", withExtension: "usdz"){_ in + + translateBy(entityId: entity, position: simd_float3(0.0,-10.0,0.0)) + + // Enable Geometry Streaming + enableStreaming( + entityId: entity, + streamingRadius: 40.0, + unloadRadius: 60.0, + priority: 10 + ) + + // Enable Static Batching + setEntityStaticBatch(entityId: entity) + enableBatching(true) + generateBatches() + + setSceneReady(true) } } + // MARK: - Setup Methods + /// Load and play the first available scene - private func loadAndPlayGameScene(sceneName: String? = nil) { + private func loadAndPlayFirstScene(sceneName: String? = nil) { setSceneReady(false) - if let sceneURL = findGameScene(name: sceneName) { + if let sceneURL = findFirstScene(name: sceneName) { playSceneAt(url: sceneURL) { setSceneReady(true) Logger.log(message: "✅ Scene loaded: \\(sceneURL.lastPathComponent)") @@ -662,52 +708,10 @@ import Foundation } } - /// Start game systems for play mode - private func startGameSystems() { + /// Configure game Systems for play mode + private func configureEngineSystems() { gameMode = true AnimationSystem.shared.isEnabled = true - USCSystem.shared.startPlayMode() - } - - // MARK: - Asset Loading - - /// Find a scene in GameData/Scenes by name, or the first JSON scene if no name is provided. - private func findGameScene(name: String? = nil) -> URL? { - guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { - Logger.log(message: "⚠️ GameData directory not found") - return nil - } - - let scenesURL = gameDataURL.appendingPathComponent("Scenes") - - guard let sceneFiles = try? FileManager.default.contentsOfDirectory( - at: scenesURL, - includingPropertiesForKeys: nil - ) else { - return nil - } - - let jsonSceneFiles = sceneFiles.filter { $0.pathExtension.lowercased() == "json" } - - guard let name = name, name.isEmpty == false else { - return jsonSceneFiles.first - } - - let sceneFileName = name.hasSuffix(".json") ? name : "\\(name).json" - return jsonSceneFiles.first { - $0.lastPathComponent.caseInsensitiveCompare(sceneFileName) == .orderedSame - } - } - - /// Load all USC scripts from GameData/Scripts - private func loadBundledScripts() { - guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { - return - } - - let scriptsURL = gameDataURL.appendingPathComponent("Scripts") - let count = loadScripts(from: scriptsURL) - Logger.log(message: "✅ Loaded \\(count) script(s) from bundle") } // MARK: - Game Loop @@ -724,7 +728,6 @@ import Foundation func handleInput() { // Skip logic if not in game mode if gameMode == false { return } - if isSceneReady() == false { return } // Add your custom input handling here } @@ -822,6 +825,7 @@ import Foundation // import Foundation + import simd import UntoldEngine // GameScene: Initialize your game and write game-specific logic @@ -831,36 +835,42 @@ import Foundation // Configure asset paths setupAssetPaths() - // Load game content - loadBundledScripts() - loadAndPlayGameScene() + // Configure game Systems + configureEngineSystems() - // Start game systems - startGameSystems() - } + setSceneReady(false) - // MARK: - Setup Methods + // load a mesh + let entity = createEntity() - /// Configure the asset base path for the bundled GameData - private func setupAssetPaths() { - if let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) { - assetBasePath = gameDataURL - Logger.log(message: "✅ Found GameData at: \\(gameDataURL.path)") - } else if let resourceURL = Bundle.main.resourceURL { - let gameDataPath = resourceURL.appendingPathComponent("GameData") - if FileManager.default.fileExists(atPath: gameDataPath.path) { - assetBasePath = gameDataPath - Logger.log(message: "✅ Found GameData in Resources: \\(gameDataPath.path)") - } else { - Logger.log(message: "⚠️ GameData not found. Bundle resources: \\(resourceURL.path)") - } + // city.usdz must be inside the GameData/model folder (generated path: GameData/Models) + setEntityMeshAsync(entityId: entity, filename: "city", withExtension: "usdz"){_ in + + translateBy(entityId: entity, position: simd_float3(0.0,-10.0,0.0)) + + // Enable Geometry Streaming + enableStreaming( + entityId: entity, + streamingRadius: 40.0, + unloadRadius: 60.0, + priority: 10 + ) + + // Enable Static Batching + setEntityStaticBatch(entityId: entity) + enableBatching(true) + generateBatches() + + setSceneReady(true) } } + // MARK: - Setup Methods + /// Load and play the first available scene - private func loadAndPlayGameScene(sceneName: String? = nil) { + private func loadAndPlayFirstScene(sceneName: String? = nil) { setSceneReady(false) - if let sceneURL = findGameScene(name: sceneName) { + if let sceneURL = findFirstScene(name: sceneName) { playSceneAt(url: sceneURL) { setSceneReady(true) Logger.log(message: "✅ Scene loaded: \\(sceneURL.lastPathComponent)") @@ -871,52 +881,10 @@ import Foundation } } - /// Start game systems for play mode - private func startGameSystems() { + /// Configure game Systems for play mode + private func configureEngineSystems() { gameMode = true AnimationSystem.shared.isEnabled = true - USCSystem.shared.startPlayMode() - } - - // MARK: - Asset Loading - - /// Find a scene in GameData/Scenes by name, or the first JSON scene if no name is provided. - private func findGameScene(name: String? = nil) -> URL? { - guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { - Logger.log(message: "⚠️ GameData directory not found") - return nil - } - - let scenesURL = gameDataURL.appendingPathComponent("Scenes") - - guard let sceneFiles = try? FileManager.default.contentsOfDirectory( - at: scenesURL, - includingPropertiesForKeys: nil - ) else { - return nil - } - - let jsonSceneFiles = sceneFiles.filter { $0.pathExtension.lowercased() == "json" } - - guard let name = name, name.isEmpty == false else { - return jsonSceneFiles.first - } - - let sceneFileName = name.hasSuffix(".json") ? name : "\\(name).json" - return jsonSceneFiles.first { - $0.lastPathComponent.caseInsensitiveCompare(sceneFileName) == .orderedSame - } - } - - /// Load all USC scripts from GameData/Scripts - private func loadBundledScripts() { - guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { - return - } - - let scriptsURL = gameDataURL.appendingPathComponent("Scripts") - let count = loadScripts(from: scriptsURL) - Logger.log(message: "✅ Loaded \\(count) script(s) from bundle") } // MARK: - Game Loop @@ -933,7 +901,6 @@ import Foundation func handleInput() { // Skip logic if not in game mode if gameMode == false { return } - if isSceneReady() == false { return } // Add your custom input handling here } @@ -1212,117 +1179,220 @@ import Foundation // import Foundation + import simd import UntoldEngine // GameScene: Initialize your game and write game-specific logic class GameScene { - init() { - Logger.log(message: "🎮 GameScene initializing...") + init() { + Logger.log(message: "🎮 GameScene initializing...") + + // Configure asset paths with debug logging + logBundleInfo() + setupAssetPaths() + + // Configure game Systems + configureEngineSystems() - // Configure asset paths with debug logging - logBundleInfo() - setupAssetPaths() + setSceneReady(false) + + // load a scene + let entity = createEntity() + + // city.usdz must be inside the GameData/model folder (generated path: GameData/Models) + setEntityMeshAsync(entityId: entity, filename: "city", withExtension: "usdz"){_ in + + translateBy(entityId: entity, position: simd_float3(0.0,-10.0,0.0)) + + // Enable Geometry Streaming + enableStreaming( + entityId: entity, + streamingRadius: 40.0, + unloadRadius: 60.0, + priority: 10 + ) + + // Enable Static Batching + setEntityStaticBatch(entityId: entity) + enableBatching(true) + generateBatches() + + setSceneReady(true) + } + } - // Load game content - Logger.log(message: "📜 Loading bundled scripts...") - loadBundledScripts() + // MARK: - Setup Methods - Logger.log(message: "🎬 Searching for scene files...") - loadAndPlayGameScene() + /// Load and play the first available scene + private func loadAndPlayFirstScene(sceneName: String? = nil) { + setSceneReady(false) + if let sceneURL = findFirstScene(name: sceneName) { + Logger.log(message: "✅ Found scene: \\(sceneURL.lastPathComponent)") + playSceneAt(url: sceneURL) { + setSceneReady(true) + Logger.log(message: "✅ Scene loaded: \\(sceneURL.lastPathComponent)") + } + } else { + setSceneReady(true) + Logger.log(message: "⚠️ No scene files found - nothing will render") + Logger.log(message: "⚠️ Create a scene in the editor and rebuild the project") + } + } - // Start game systems - startGameSystems() + /// Configure game Systems for play mode + private func configureEngineSystems() { + gameMode = true + AnimationSystem.shared.isEnabled = true + InputSystem.shared.registerXREvents() + InputSystem.shared.setXRSpatialPickingBackendPreference(.octreeGPUPreferred) + InputSystem.shared.setXRTwoHandRotateAxisMode(.dynamicSnapped) + } - Logger.log(message: "✅ GameScene initialization complete") - } + // MARK: - Game Loop + + /// Called every frame - add custom game logic here + func update(deltaTime _: Float) { + // Skip logic if not in game mode + if gameMode == false { return } + + // Add your custom update logic here + } - // MARK: - Setup Methods + /// Called for input handling - add custom input logic here + func handleInput() { + // Skip logic if not in game mode + if gameMode == false { return } + if isSceneReady() == false {return} + + let state = InputSystem.shared.xrSpatialInputState - /// Log bundle information for debugging - private func logBundleInfo() { - Logger.log(message: "📦 Bundle path: \\(Bundle.main.bundlePath)") - if let resourceURL = Bundle.main.resourceURL { - Logger.log(message: "📦 Resource URL: \\(resourceURL.path)") - if let contents = try? FileManager.default.contentsOfDirectory(atPath: resourceURL.path) { - Logger.log(message: "📦 Resources directory contents: \\(contents.joined(separator: ", "))") + // tap on individual meshes/entities in the scene + if state.spatialTapActive, let entityId = state.pickedEntityId { + Logger.log(message: "Tapped entity: \\(entityId)") } + + // transform scene root. + // Pinch + Drag to move scene root + // Two-hands rotate to rotate the scene root + SpatialManipulationSystem.shared.processAnchoredSceneManipulationLifecycle( + from: state, + dragSensitivity: 10.0, + rotateSensitivity: 1.0 + ) + + // Add your custom input handling here } } - /// Configure the asset base path for the bundled GameData - private func setupAssetPaths() { - if let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) { - assetBasePath = gameDataURL - Logger.log(message: "✅ Found GameData at: \\(gameDataURL.path)") - if let contents = try? FileManager.default.contentsOfDirectory(atPath: gameDataURL.path) { - Logger.log(message: "📦 GameData contents: \\(contents.joined(separator: ", "))") - } - } else if let resourceURL = Bundle.main.resourceURL { - let gameDataPath = resourceURL.appendingPathComponent("GameData") - if FileManager.default.fileExists(atPath: gameDataPath.path) { - assetBasePath = gameDataPath - Logger.log(message: "✅ Found GameData in Resources: \\(gameDataPath.path)") - if let contents = try? FileManager.default.contentsOfDirectory(atPath: gameDataPath.path) { - Logger.log(message: "📦 GameData contents: \\(contents.joined(separator: ", "))") + @MainActor + final class EngineStatsWindowStore: ObservableObject { + static let shared = EngineStatsWindowStore() + + @Published var advancedMode: Bool = false + @Published private(set) var compactText: String = "" + @Published private(set) var expandedText: String = "" + + private var timer: Timer? + + private init() { + refresh() + timer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in + guard let self else { return } + Task { @MainActor in + self.refresh() } - } else { - Logger.log(message: "⚠️ GameData not found. Bundle resources: \\(resourceURL.path)") - Logger.log(message: "⚠️ Listing all files in bundle resources...") - listDirectoryRecursively(at: resourceURL) } - } else { - Logger.log(message: "❌ Bundle.main.resourceURL is nil!") + } + + deinit { + timer?.invalidate() + } + + private func refresh() { + let snapshot = getEngineStatsSnapshot() + compactText = formatEngineStatsCompact(snapshot) + expandedText = formatEngineStatsOverlay(snapshot) } } - /// Load and play the first available scene - private func loadAndPlayGameScene(sceneName: String? = nil) { - setSceneReady(false) - if let sceneURL = findGameScene(name: sceneName) { - Logger.log(message: "✅ Found scene: \\(sceneURL.lastPathComponent)") - playSceneAt(url: sceneURL) { - setSceneReady(true) - Logger.log(message: "✅ Scene loaded: \\(sceneURL.lastPathComponent)") + """ + + // MARK: - visionOS GameSceneUtils.swift Template + + static let visionOSGameSceneUtilsSwift = """ + // + // GameSceneUtils.swift + // {{PROJECT_NAME}} + // + // Generated by UntoldEngine Build System + // visionOS Version + // + + import Foundation + import UntoldEngine + + extension GameScene { + + // MARK: - Asset Setup + + /// Log bundle information for debugging + func logBundleInfo() { + Logger.log(message: "📦 Bundle path: \\(Bundle.main.bundlePath)") + if let resourceURL = Bundle.main.resourceURL { + Logger.log(message: "📦 Resource URL: \\(resourceURL.path)") + if let contents = try? FileManager.default.contentsOfDirectory(atPath: resourceURL.path) { + Logger.log(message: "📦 Resources directory contents: \\(contents.joined(separator: ", "))") + } } - } else { - setSceneReady(true) - Logger.log(message: "⚠️ No scene files found - nothing will render") - Logger.log(message: "⚠️ Create a scene in the editor and rebuild the project") } - } - /// Start game systems for play mode - private func startGameSystems() { - gameMode = true - AnimationSystem.shared.isEnabled = true - InputSystem.shared.registerXREvents() - InputSystem.shared.setXRSpatialPickingBackendPreference(.octreeGPUPreferred) - USCSystem.shared.startPlayMode() - } + /// Configure the asset base path for the bundled GameData + func setupAssetPaths() { + if let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) { + assetBasePath = gameDataURL + Logger.log(message: "✅ Found GameData at: \\(gameDataURL.path)") + if let contents = try? FileManager.default.contentsOfDirectory(atPath: gameDataURL.path) { + Logger.log(message: "📦 GameData contents: \\(contents.joined(separator: ", "))") + } + } else if let resourceURL = Bundle.main.resourceURL { + let gameDataPath = resourceURL.appendingPathComponent("GameData") + if FileManager.default.fileExists(atPath: gameDataPath.path) { + assetBasePath = gameDataPath + Logger.log(message: "✅ Found GameData in Resources: \\(gameDataPath.path)") + if let contents = try? FileManager.default.contentsOfDirectory(atPath: gameDataPath.path) { + Logger.log(message: "📦 GameData contents: \\(contents.joined(separator: ", "))") + } + } else { + Logger.log(message: "⚠️ GameData not found. Bundle resources: \\(resourceURL.path)") + Logger.log(message: "⚠️ Listing all files in bundle resources...") + listDirectoryRecursively(at: resourceURL) + } + } else { + Logger.log(message: "❌ Bundle.main.resourceURL is nil!") + } + } - // MARK: - Asset Loading - - /// Recursively list directory contents for debugging - private func listDirectoryRecursively(at url: URL, depth: Int = 0) { - let indent = String(repeating: " ", count: depth) - guard let contents = try? FileManager.default.contentsOfDirectory(atPath: url.path) else { - return - } - for item in contents { - let itemURL = url.appendingPathComponent(item) - var isDirectory: ObjCBool = false - if FileManager.default.fileExists(atPath: itemURL.path, isDirectory: &isDirectory) { - Logger.log(message: "\\(indent)📁 \\(item)\\(isDirectory.boolValue ? "/" : "")") - if isDirectory.boolValue && depth < 2 { - listDirectoryRecursively(at: itemURL, depth: depth + 1) + /// Recursively list directory contents for debugging + func listDirectoryRecursively(at url: URL, depth: Int = 0) { + let indent = String(repeating: " ", count: depth) + guard let contents = try? FileManager.default.contentsOfDirectory(atPath: url.path) else { + return + } + for item in contents { + let itemURL = url.appendingPathComponent(item) + var isDirectory: ObjCBool = false + if FileManager.default.fileExists(atPath: itemURL.path, isDirectory: &isDirectory) { + Logger.log(message: "\\(indent)📁 \\(item)\\(isDirectory.boolValue ? "/" : "")") + if isDirectory.boolValue && depth < 2 { + listDirectoryRecursively(at: itemURL, depth: depth + 1) + } } } } - } /// Find a scene in GameData/Scenes by name, or the first JSON scene if no name is provided. - private func findGameScene(name: String? = nil) -> URL? { + func findFirstScene(name: String? = nil) -> URL? { guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { Logger.log(message: "⚠️ GameData directory not found") return nil @@ -1350,7 +1420,7 @@ import Foundation } /// Load all USC scripts from GameData/Scripts - private func loadBundledScripts() { + func loadBundledScripts() { guard let gameDataURL = Bundle.main.url(forResource: "GameData", withExtension: nil) else { return } @@ -1359,30 +1429,6 @@ import Foundation let count = loadScripts(from: scriptsURL) Logger.log(message: "✅ Loaded \\(count) script(s) from bundle") } - - // MARK: - Game Loop - - /// Called every frame - add custom game logic here - func update(deltaTime _: Float) { - // Skip logic if not in game mode - if gameMode == false { return } - - // Add your custom update logic here - } - - /// Called for input handling - add custom input logic here - func handleInput() { - // Skip logic if not in game mode - if gameMode == false { return } - if isSceneReady() == false { return } - - let state = InputSystem.shared.xrSpatialInputState - if state.spatialTapActive, let entityId = state.pickedEntityId { - Logger.log(message: "Tapped entity: \\(entityId)") - } - - // Add your custom input handling here - } } """ @@ -1398,9 +1444,13 @@ import Foundation // import SwiftUI + import Combine + import Foundation import CompositorServices import UntoldEngineXR + + // Simple owner so the XR system doesn't deallocate. final class XRHolder { static let shared = XRHolder() @@ -1491,7 +1541,7 @@ import Foundation .font(.extraLargeTitle) .fontWeight(.bold) - Text("Powered by UntoldEngine") + Text("Powered by Untold Engine") .font(.title) .foregroundColor(.secondary) @@ -1935,6 +1985,7 @@ import Foundation "README.md": readmeMd, "Sources/{{PROJECT_NAME}}/AppDelegate.swift": appDelegateSwift, "Sources/{{PROJECT_NAME}}/GameScene.swift": macOSGameSceneSwift, + "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift": gameSceneUtilsSwift, "Sources/{{PROJECT_NAME}}/GameViewController.swift": gameViewControllerSwift, "Sources/{{PROJECT_NAME}}/Base.lproj/Main.storyboard": mainStoryboard, "Sources/{{PROJECT_NAME}}/Info.plist": infoPlist, @@ -1944,6 +1995,7 @@ import Foundation "README.md": readmeMd, "Sources/{{PROJECT_NAME}}/AppDelegate.swift": iOSAppDelegateSwift, "Sources/{{PROJECT_NAME}}/GameScene.swift": iOSGameSceneSwift, + "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift": gameSceneUtilsSwift, "Sources/{{PROJECT_NAME}}/GameViewController.swift": iOSGameViewControllerSwift, "Sources/{{PROJECT_NAME}}/Base.lproj/Main.storyboard": iOSMainStoryboard, "Sources/{{PROJECT_NAME}}/Info.plist": iOSInfoPlist, @@ -1953,6 +2005,7 @@ import Foundation return [ "README.md": readmeMd, "Sources/{{PROJECT_NAME}}/GameScene.swift": visionOSGameSceneSwift, + "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift": visionOSGameSceneUtilsSwift, "Sources/{{PROJECT_NAME}}/{{PROJECT_NAME}}App.swift": visionOSAppSwift, "Sources/{{PROJECT_NAME}}/Info.plist": visionOSInfoPlist, ] @@ -1968,6 +2021,7 @@ import Foundation "README.md": readmeMd, "Sources/{{PROJECT_NAME}}/AppDelegate.swift": iOSAppDelegateSwift, "Sources/{{PROJECT_NAME}}/GameScene.swift": iOSARGameSceneSwift, + "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift": gameSceneUtilsSwift, "Sources/{{PROJECT_NAME}}/GameViewController.swift": iOSARGameViewControllerSwift, "Sources/{{PROJECT_NAME}}/Base.lproj/Main.storyboard": iOSMainStoryboard, "Sources/{{PROJECT_NAME}}/Info.plist": iOSARInfoPlist, @@ -2004,6 +2058,7 @@ import Foundation // Shared code - in Sources folder (accessible to all targets) "Sources/{{PROJECT_NAME}}/GameScene.swift": macOSGameSceneSwift, + "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift": gameSceneUtilsSwift, ] } } diff --git a/Sources/UntoldEngine/BuildSystem/XcodeGenProjectSpec.swift b/Sources/UntoldEngine/BuildSystem/XcodeGenProjectSpec.swift index f51ba83c1..595157f1b 100644 --- a/Sources/UntoldEngine/BuildSystem/XcodeGenProjectSpec.swift +++ b/Sources/UntoldEngine/BuildSystem/XcodeGenProjectSpec.swift @@ -111,13 +111,17 @@ import Foundation // Packages section let packagesSection: String - if settings.isIOSAR, case .iOS = settings.target { + if case .visionOS = settings.target { packagesSection = """ packages: UntoldEngine: url: https://github.com/untoldengine/UntoldEngine.git branch: develop - UntoldEngineAR: + """ + } else if settings.isIOSAR, case .iOS = settings.target { + packagesSection = """ + packages: + UntoldEngine: url: https://github.com/untoldengine/UntoldEngine.git branch: develop """ @@ -132,16 +136,25 @@ import Foundation // Dependencies section let dependenciesSection: String - if settings.isIOSAR, case .iOS = settings.target { + if case .visionOS = settings.target { + dependenciesSection = """ + dependencies: + - package: UntoldEngine + product: UntoldEngineXR + - package: UntoldEngine + product: UntoldEngineAR + """ + } else if settings.isIOSAR, case .iOS = settings.target { dependenciesSection = """ dependencies: - package: UntoldEngine - - package: UntoldEngineAR + product: UntoldEngineAR """ } else { dependenciesSection = """ dependencies: - package: UntoldEngine + product: UntoldEngine """ } @@ -170,12 +183,6 @@ import Foundation UntoldEngine: url: https://github.com/untoldengine/UntoldEngine.git branch: develop - UntoldEngineXR: - url: https://github.com/untoldengine/UntoldEngine.git - branch: develop - UntoldEngineAR: - url: https://github.com/untoldengine/UntoldEngine.git - branch: develop targets: \(settings.projectName) macOS: @@ -217,6 +224,7 @@ import Foundation buildPhase: resources dependencies: - package: UntoldEngine + product: UntoldEngine settings: base: PRODUCT_BUNDLE_IDENTIFIER: \(settings.bundleIdentifier) @@ -246,7 +254,7 @@ import Foundation buildPhase: resources dependencies: - package: UntoldEngine - - package: UntoldEngineAR + product: UntoldEngineAR settings: base: PRODUCT_BUNDLE_IDENTIFIER: \(settings.bundleIdentifier).ar @@ -276,7 +284,9 @@ import Foundation buildPhase: resources dependencies: - package: UntoldEngine - - package: UntoldEngineXR + product: UntoldEngineXR + - package: UntoldEngine + product: UntoldEngineAR settings: base: PRODUCT_BUNDLE_IDENTIFIER: \(settings.bundleIdentifier) diff --git a/Sources/UntoldEngine/Renderer/RenderInitializer.swift b/Sources/UntoldEngine/Renderer/RenderInitializer.swift index 2f69a0af2..35b0d9343 100644 --- a/Sources/UntoldEngine/Renderer/RenderInitializer.swift +++ b/Sources/UntoldEngine/Renderer/RenderInitializer.swift @@ -1196,12 +1196,14 @@ func initSSAOResources() { /// Reinitialize SSAO textures when quality changes func reinitSSAOTextures() { + let fullWidth = Int(renderInfo.viewPort.x) + let fullHeight = Int(renderInfo.viewPort.y) + if fullWidth == 0 || fullHeight == 0 { return } + let quality = SSAOParams.shared.quality let scale = quality.resolutionScale let format = quality.textureFormat - let fullWidth = Int(renderInfo.viewPort.x) - let fullHeight = Int(renderInfo.viewPort.y) let ssaoWidth = Int(Float(fullWidth) * scale) let ssaoHeight = Int(Float(fullHeight) * scale) diff --git a/Sources/UntoldEngine/Systems/GeometryStreamingSystem.swift b/Sources/UntoldEngine/Systems/GeometryStreamingSystem.swift index 060c6ebe8..168e01fe5 100644 --- a/Sources/UntoldEngine/Systems/GeometryStreamingSystem.swift +++ b/Sources/UntoldEngine/Systems/GeometryStreamingSystem.swift @@ -118,7 +118,7 @@ public class GeometryStreamingSystem: @unchecked Sendable { } private func reserveNearBandLoad(entityId: EntityID) { - withStateLock { activeNearBandLoads.insert(entityId) } + _ = withStateLock { activeNearBandLoads.insert(entityId) } } private func releaseNearBandLoad(entityId: EntityID) { @@ -1225,8 +1225,6 @@ public class GeometryStreamingSystem: @unchecked Sendable { let local = scene.get(component: LocalTransformComponent.self, for: entityId) else { return Float.infinity } - let center = (local.boundingBox.min + local.boundingBox.max) * 0.5 - // Transform the camera into entity-local space so that streamingRadius and // unloadRadius are scale-invariant. Without this, applying scaleTo(0.1) on a // parent compresses all child world-space positions by 10×, collapsing every diff --git a/Tests/UntoldEngineTests/BuildSystemTests.swift b/Tests/UntoldEngineTests/BuildSystemTests.swift index ae6ec462b..58501e14c 100644 --- a/Tests/UntoldEngineTests/BuildSystemTests.swift +++ b/Tests/UntoldEngineTests/BuildSystemTests.swift @@ -219,16 +219,38 @@ final class BuildSystemTests: XCTestCase { "GameScene.swift should import UntoldEngine") XCTAssertTrue(gameSceneContent.contains("import Foundation"), "GameScene.swift should import Foundation") - XCTAssertTrue(gameSceneContent.contains("assetBasePath"), - "GameScene.swift should set assetBasePath") - XCTAssertTrue(gameSceneContent.contains("loadBundledScripts()"), - "GameScene.swift should call loadBundledScripts") - XCTAssertTrue(gameSceneContent.contains("playSceneAt(url: sceneURL)"), - "GameScene.swift should call playSceneAt") + XCTAssertTrue(gameSceneContent.contains("import simd"), + "GameScene.swift should import simd") + XCTAssertTrue(gameSceneContent.contains("setupAssetPaths()"), + "GameScene.swift should call setupAssetPaths in init") + XCTAssertTrue(gameSceneContent.contains("private func loadAndPlayFirstScene"), + "GameScene.swift should define loadAndPlayFirstScene helper") + XCTAssertFalse(gameSceneContent.contains("func setupAssetPaths()"), + "GameScene.swift should keep asset helpers in GameSceneUtils.swift") XCTAssertTrue(gameSceneContent.contains("func update(deltaTime"), "GameScene.swift should contain update method") XCTAssertTrue(gameSceneContent.contains("func handleInput()"), "GameScene.swift should contain handleInput method") + + // Verify GameSceneUtils content for extracted onboarding helpers + let gameSceneUtilsKey = "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift" + XCTAssertNotNil(templateFiles[gameSceneUtilsKey], "GameSceneUtils.swift template should exist") + + guard let gameSceneUtilsContent = templateFiles[gameSceneUtilsKey] else { + XCTFail("GameSceneUtils.swift content should not be nil") + return + } + + XCTAssertTrue(gameSceneUtilsContent.contains("extension GameScene"), + "GameSceneUtils.swift should extend GameScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func setupAssetPaths()"), + "GameSceneUtils.swift should contain setupAssetPaths") + XCTAssertTrue(gameSceneUtilsContent.contains("assetBasePath"), + "GameSceneUtils.swift should set assetBasePath") + XCTAssertTrue(gameSceneUtilsContent.contains("func findFirstScene"), + "GameSceneUtils.swift should contain findFirstScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func loadBundledScripts()"), + "GameSceneUtils.swift should contain loadBundledScripts") } func testMacOSGameViewControllerSwiftGeneratedWithExpectedContent() { @@ -454,14 +476,38 @@ final class BuildSystemTests: XCTestCase { "iOS GameScene.swift should import UntoldEngine") XCTAssertTrue(gameSceneContent.contains("import Foundation"), "iOS GameScene.swift should import Foundation") - XCTAssertTrue(gameSceneContent.contains("assetBasePath"), - "iOS GameScene.swift should set assetBasePath") - XCTAssertTrue(gameSceneContent.contains("loadBundledScripts()"), - "iOS GameScene.swift should call loadBundledScripts") + XCTAssertTrue(gameSceneContent.contains("import simd"), + "iOS GameScene.swift should import simd") + XCTAssertTrue(gameSceneContent.contains("setupAssetPaths()"), + "iOS GameScene.swift should call setupAssetPaths in init") + XCTAssertTrue(gameSceneContent.contains("private func loadAndPlayFirstScene"), + "iOS GameScene.swift should define loadAndPlayFirstScene helper") + XCTAssertFalse(gameSceneContent.contains("func setupAssetPaths()"), + "iOS GameScene.swift should keep asset helpers in GameSceneUtils.swift") XCTAssertTrue(gameSceneContent.contains("func update(deltaTime"), "iOS GameScene.swift should contain update method") XCTAssertTrue(gameSceneContent.contains("func handleInput()"), "iOS GameScene.swift should contain handleInput method") + + // Verify GameSceneUtils content for extracted onboarding helpers + let gameSceneUtilsKey = "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift" + XCTAssertNotNil(templateFiles[gameSceneUtilsKey], "iOS GameSceneUtils.swift should exist") + + guard let gameSceneUtilsContent = templateFiles[gameSceneUtilsKey] else { + XCTFail("iOS GameSceneUtils.swift content should not be nil") + return + } + + XCTAssertTrue(gameSceneUtilsContent.contains("extension GameScene"), + "iOS GameSceneUtils.swift should extend GameScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func setupAssetPaths()"), + "iOS GameSceneUtils.swift should contain setupAssetPaths") + XCTAssertTrue(gameSceneUtilsContent.contains("assetBasePath"), + "iOS GameSceneUtils.swift should set assetBasePath") + XCTAssertTrue(gameSceneUtilsContent.contains("func findFirstScene"), + "iOS GameSceneUtils.swift should contain findFirstScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func loadBundledScripts()"), + "iOS GameSceneUtils.swift should contain loadBundledScripts") } func testIOSGameViewControllerSwiftGeneratedWithExpectedContent() { @@ -702,6 +748,8 @@ final class BuildSystemTests: XCTestCase { "iOS AR GameScene should import Foundation") XCTAssertTrue(gameSceneContent.contains("import UntoldEngine"), "iOS AR GameScene should import UntoldEngine") + XCTAssertTrue(gameSceneContent.contains("import simd"), + "iOS AR GameScene should import simd") XCTAssertTrue(gameSceneContent.contains("class GameScene"), "iOS AR GameScene should contain GameScene class") XCTAssertTrue(gameSceneContent.contains("func update(deltaTime"), @@ -714,6 +762,21 @@ final class BuildSystemTests: XCTestCase { "iOS AR GameScene should NOT import ARKit") XCTAssertFalse(gameSceneContent.contains("import UntoldEngineAR"), "iOS AR GameScene should NOT import UntoldEngineAR") + + let gameSceneUtilsKey = "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift" + XCTAssertNotNil(templateFiles[gameSceneUtilsKey], "iOS AR GameSceneUtils.swift should exist") + + guard let gameSceneUtilsContent = templateFiles[gameSceneUtilsKey] else { + XCTFail("iOS AR GameSceneUtils.swift content should not be nil") + return + } + + XCTAssertTrue(gameSceneUtilsContent.contains("extension GameScene"), + "iOS AR GameSceneUtils.swift should extend GameScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func setupAssetPaths()"), + "iOS AR GameSceneUtils.swift should contain setupAssetPaths") + XCTAssertTrue(gameSceneUtilsContent.contains("func loadBundledScripts()"), + "iOS AR GameSceneUtils.swift should contain loadBundledScripts") } // MARK: - visionOS Template Tests @@ -746,9 +809,8 @@ final class BuildSystemTests: XCTestCase { "visionOS App should import CompositorServices") XCTAssertTrue(appSwiftContent.contains("import UntoldEngineXR"), "visionOS App should import UntoldEngineXR") - // Check that it doesn't import UntoldEngine as a standalone import (not as part of UntoldEngineXR) - XCTAssertFalse(appSwiftContent.contains("import UntoldEngine\n"), - "visionOS App should NOT import UntoldEngine standalone (it's in GameScene)") + XCTAssertTrue(appSwiftContent.contains("import UntoldEngine"), + "visionOS App should import UntoldEngine for engine stats helpers") XCTAssertTrue(appSwiftContent.contains("UntoldEngineXR"), "visionOS App should use UntoldEngineXR class") XCTAssertTrue(appSwiftContent.contains("CompositorLayer"), @@ -872,6 +934,8 @@ final class BuildSystemTests: XCTestCase { "GameScene should import Foundation") XCTAssertTrue(gameSceneContent.contains("import UntoldEngine"), "GameScene should import UntoldEngine") + XCTAssertTrue(gameSceneContent.contains("import simd"), + "GameScene should import simd") XCTAssertTrue(gameSceneContent.contains("class GameScene"), "GameScene should contain GameScene class") XCTAssertTrue(gameSceneContent.contains("func update(deltaTime"), @@ -882,6 +946,82 @@ final class BuildSystemTests: XCTestCase { "GameScene should NOT import SwiftUI") XCTAssertFalse(gameSceneContent.contains("import UntoldEngineXR"), "GameScene should NOT import UntoldEngineXR") + + let gameSceneUtilsKey = "Sources/{{PROJECT_NAME}}/GameSceneUtils.swift" + XCTAssertNotNil(templateFiles[gameSceneUtilsKey], "visionOS GameSceneUtils.swift should exist") + + guard let gameSceneUtilsContent = templateFiles[gameSceneUtilsKey] else { + XCTFail("visionOS GameSceneUtils.swift content should not be nil") + return + } + + XCTAssertTrue(gameSceneUtilsContent.contains("extension GameScene"), + "visionOS GameSceneUtils.swift should extend GameScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func logBundleInfo()"), + "visionOS GameSceneUtils.swift should include logBundleInfo") + XCTAssertTrue(gameSceneUtilsContent.contains("func setupAssetPaths()"), + "visionOS GameSceneUtils.swift should include setupAssetPaths") + XCTAssertTrue(gameSceneUtilsContent.contains("func listDirectoryRecursively"), + "visionOS GameSceneUtils.swift should include directory listing helper") + XCTAssertTrue(gameSceneUtilsContent.contains("func findFirstScene"), + "visionOS GameSceneUtils.swift should include findFirstScene") + XCTAssertTrue(gameSceneUtilsContent.contains("func loadBundledScripts()"), + "visionOS GameSceneUtils.swift should include loadBundledScripts") + } + + func testIOSARSinglePlatformUsesUntoldEngineAROnly() throws { + // Given: Single-platform iOS AR build settings + let settings = BuildSettings( + projectName: "MyIOSARGame", + bundleIdentifier: "com.test.iosar", + outputPath: tempDirectory, + target: .iOS(deployment: .v17), + isIOSAR: true + ) + + // When: Generating XcodeGen YAML spec + let yamlContent = try XcodeGenProjectSpec.generateYAML(settings: settings) + + // Then: iOS AR should use one package and only the UntoldEngineAR product + XCTAssertTrue(yamlContent.contains("UntoldEngine:"), + "iOS AR single-platform should include UntoldEngine package") + XCTAssertFalse(yamlContent.contains("UntoldEngineAR:"), + "iOS AR single-platform should not declare UntoldEngineAR as a separate package") + XCTAssertFalse(yamlContent.contains("UntoldEngineXR:"), + "iOS AR single-platform should not declare UntoldEngineXR as a separate package") + XCTAssertTrue(yamlContent.contains("- package: UntoldEngine"), + "iOS AR single-platform should depend on UntoldEngine package") + XCTAssertTrue(yamlContent.contains("product: UntoldEngineAR"), + "iOS AR single-platform should depend on UntoldEngineAR product") + XCTAssertFalse(yamlContent.contains("product: UntoldEngineXR"), + "iOS AR single-platform should not depend on UntoldEngineXR product") + } + + func testVisionOSSinglePlatformUsesXRandARDependencies() throws { + // Given: Single-platform visionOS build settings + let settings = BuildSettings( + projectName: "MyVisionGame", + bundleIdentifier: "com.test.vision", + outputPath: tempDirectory, + target: .visionOS(deployment: .v2) + ) + + // When: Generating XcodeGen YAML spec + let yamlContent = try XcodeGenProjectSpec.generateYAML(settings: settings) + + // Then: visionOS should use one package and depend on XR + AR products + XCTAssertTrue(yamlContent.contains("UntoldEngine:"), + "visionOS single-platform should include UntoldEngine package") + XCTAssertFalse(yamlContent.contains("UntoldEngineXR:"), + "visionOS single-platform should not declare UntoldEngineXR as a separate package") + XCTAssertFalse(yamlContent.contains("UntoldEngineAR:"), + "visionOS single-platform should not declare UntoldEngineAR as a separate package") + XCTAssertTrue(yamlContent.contains("- package: UntoldEngine"), + "visionOS single-platform should depend on UntoldEngine package") + XCTAssertTrue(yamlContent.contains("product: UntoldEngineXR"), + "visionOS single-platform should depend on UntoldEngineXR product") + XCTAssertTrue(yamlContent.contains("product: UntoldEngineAR"), + "visionOS single-platform should depend on UntoldEngineAR product") } // MARK: - Multi-Platform Tests @@ -965,6 +1105,8 @@ final class BuildSystemTests: XCTestCase { // Should contain shared GameScene XCTAssertNotNil(templateFiles["Sources/{{PROJECT_NAME}}/GameScene.swift"], "Should have shared GameScene in Sources folder") + XCTAssertNotNil(templateFiles["Sources/{{PROJECT_NAME}}/GameSceneUtils.swift"], + "Should have shared GameSceneUtils in Sources folder") // Should NOT contain Package.swift (uses XcodeGen) XCTAssertNil(templateFiles["Package.swift"], @@ -1012,10 +1154,14 @@ final class BuildSystemTests: XCTestCase { // Should include all necessary packages XCTAssertTrue(yamlContent.contains("UntoldEngine:"), "Should include UntoldEngine package") - XCTAssertTrue(yamlContent.contains("UntoldEngineXR:"), - "Should include UntoldEngineXR package for visionOS") - XCTAssertTrue(yamlContent.contains("UntoldEngineAR:"), - "Should include UntoldEngineAR package for iOS AR") + XCTAssertFalse(yamlContent.contains("UntoldEngineXR:"), + "Should not include duplicate package entries for UntoldEngineXR") + XCTAssertFalse(yamlContent.contains("UntoldEngineAR:"), + "Should not include duplicate package entries for UntoldEngineAR") + XCTAssertTrue(yamlContent.contains("product: UntoldEngineXR"), + "Should include UntoldEngineXR product dependency for visionOS") + XCTAssertTrue(yamlContent.contains("product: UntoldEngineAR"), + "Should include UntoldEngineAR product dependency for iOS AR/visionOS") // Should specify correct source paths for each target XCTAssertTrue(yamlContent.contains("path: MyMultiPlatformGame macOS"), @@ -1089,15 +1235,21 @@ final class BuildSystemTests: XCTestCase { // When: Generating XcodeGen YAML spec let yamlContent = try XcodeGenProjectSpec.generateYAML(settings: settings) - // Then: Each target should have appropriate dependencies - // macOS and iOS (non-AR) should have UntoldEngine + // Then: Each target should have appropriate product dependencies let lines = yamlContent.components(separatedBy: "\n") var inMacOSTarget = false var inIOSTarget = false var inIOSARTarget = false var inVisionOSTarget = false + var foundIOSARAR = false + var foundVisionAR = false + var foundVisionXR = false + var foundMacOSEngine = false + var foundIOSEngine = false for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if line.contains("MyGame macOS:") { inMacOSTarget = true inIOSTarget = false @@ -1121,17 +1273,33 @@ final class BuildSystemTests: XCTestCase { } // Check that dependencies appear in the right sections - if line.contains("- package: UntoldEngine") { + if trimmedLine == "- package: UntoldEngine" { XCTAssertTrue(inMacOSTarget || inIOSTarget || inIOSARTarget || inVisionOSTarget, - "UntoldEngine should be in a target section") + "UntoldEngine package references should be inside target dependency sections") } - if line.contains("- package: UntoldEngineAR") { - XCTAssertTrue(inIOSARTarget, "UntoldEngineAR should only be in iOS AR target") + if trimmedLine == "product: UntoldEngine" { + XCTAssertTrue(inMacOSTarget || inIOSTarget, + "UntoldEngine product should only be in macOS or iOS targets") + if inMacOSTarget { foundMacOSEngine = true } + if inIOSTarget { foundIOSEngine = true } } - if line.contains("- package: UntoldEngineXR") { + if trimmedLine == "product: UntoldEngineAR" { + XCTAssertTrue(inIOSARTarget || inVisionOSTarget, + "UntoldEngineAR product should only be in iOS AR or visionOS targets") + if inIOSARTarget { foundIOSARAR = true } + if inVisionOSTarget { foundVisionAR = true } + } + if trimmedLine == "product: UntoldEngineXR" { XCTAssertTrue(inVisionOSTarget, "UntoldEngineXR should only be in visionOS target") + foundVisionXR = true } } + + XCTAssertTrue(foundMacOSEngine, "macOS target should include UntoldEngine product") + XCTAssertTrue(foundIOSEngine, "iOS target should include UntoldEngine product") + XCTAssertTrue(foundIOSARAR, "iOS AR target should include UntoldEngineAR") + XCTAssertTrue(foundVisionXR, "visionOS target should include UntoldEngineXR") + XCTAssertTrue(foundVisionAR, "visionOS target should include UntoldEngineAR") } func testMultiPlatformOptimizationSettings() throws { diff --git a/Tools/UntoldEngineCLI/README.md b/Tools/UntoldEngineCLI/README.md index 0dcac9141..ac4987ed4 100644 --- a/Tools/UntoldEngineCLI/README.md +++ b/Tools/UntoldEngineCLI/README.md @@ -24,7 +24,7 @@ git clone https://github.com/untoldengine/UntoldEngine.git cd UntoldEngine # Run the install script -./scripts/install-create.sh +./scripts/install-untoldengine-create.sh ``` This will build the CLI in release mode and install it to `/usr/local/bin`, making it available globally. diff --git a/docs/01-Intro.md b/docs/01-Intro.md index c1eff3f3a..403250a83 100644 --- a/docs/01-Intro.md +++ b/docs/01-Intro.md @@ -38,7 +38,7 @@ Clone the repository, run the engine and load a USDZ file: ```bash git clone https://github.com/untoldengine/UntoldEngine.git cd UntoldEngine -swift run DemoGame +swift run untoldsandbox ``` This will: diff --git a/docs/images/engine-highlight-1.png b/docs/images/engine-highlight-1.png new file mode 100644 index 000000000..3a469dcc6 Binary files /dev/null and b/docs/images/engine-highlight-1.png differ diff --git a/scripts/install-create.sh b/scripts/install-untoldengine-create.sh similarity index 98% rename from scripts/install-create.sh rename to scripts/install-untoldengine-create.sh index 68277ad46..1a2ffb016 100755 --- a/scripts/install-create.sh +++ b/scripts/install-untoldengine-create.sh @@ -1,6 +1,6 @@ #!/bin/bash -# install-create.sh +# install-untoldengine-create.sh # Installation script for untoldengine-create CLI tool set -e # Exit on error diff --git a/website/.gitignore b/website/.gitignore deleted file mode 100644 index b2d6de306..000000000 --- a/website/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -# Dependencies -/node_modules - -# Production -/build - -# Generated files -.docusaurus -.cache-loader - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/website/README.md b/website/README.md deleted file mode 100644 index b28211a9b..000000000 --- a/website/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Website - -This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. - -## Installation - -```bash -yarn -``` - -## Local Development - -```bash -yarn start -``` - -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. - -## Build - -```bash -yarn build -``` - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - -## Deployment - -Using SSH: - -```bash -USE_SSH=true yarn deploy -``` - -Not using SSH: - -```bash -GIT_USER= yarn deploy -``` - -If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/website/_bak/index._bak.js b/website/_bak/index._bak.js deleted file mode 100644 index a8c61f2ba..000000000 --- a/website/_bak/index._bak.js +++ /dev/null @@ -1,43 +0,0 @@ -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Layout from '@theme/Layout'; -import HomepageFeatures from '@site/src/components/HomepageFeatures'; - -import Heading from '@theme/Heading'; -import styles from './index.module.css'; - -function HomepageHeader() { - const {siteConfig} = useDocusaurusContext(); - return ( -
-
- - {siteConfig.title} - -

{siteConfig.tagline}

-
- - Docusaurus Tutorial - 5min ⏱️ - -
-
-
- ); -} - -export default function Home() { - const {siteConfig} = useDocusaurusContext(); - return ( - - -
- -
-
- ); -} diff --git a/website/docs/intro.md b/website/docs/intro.md deleted file mode 100644 index f613a98ca..000000000 --- a/website/docs/intro.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -id: Intro -slug: /Intro -sidebar_position: 1 ---- - -# Tutorial Intro - -Let's discover **Docusaurus in less than 5 minutes**. - -## Getting Started - -Get started by **creating a new site**. - -Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. - -### What you'll need - -- [Node.js](https://nodejs.org/en/download/) version 18.0 or above: - - When installing Node.js, you are recommended to check all checkboxes related to dependencies. - -## Generate a new site - -Generate a new Docusaurus site using the **classic template**. - -The classic template will automatically be added to your project after you run the command: - -```bash -npm init docusaurus@latest my-website classic -``` - -You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. - -The command also installs all necessary dependencies you need to run Docusaurus. - -## Start your site - -Run the development server: - -```bash -cd my-website -npm run start -``` - -The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. - -The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. - -Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/website/docs/tutorial-extras/img/localeDropdown.png b/website/docs/tutorial-extras/img/localeDropdown.png deleted file mode 100644 index e257edc1f..000000000 Binary files a/website/docs/tutorial-extras/img/localeDropdown.png and /dev/null differ diff --git a/website/docs/tutorial-extras/manage-docs-versions.md b/website/docs/tutorial-extras/manage-docs-versions.md deleted file mode 100644 index ccda0b907..000000000 --- a/website/docs/tutorial-extras/manage-docs-versions.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Manage Docs Versions - -Docusaurus can manage multiple versions of your docs. - -## Create a docs version - -Release a version 1.0 of your project: - -```bash -npm run docusaurus docs:version 1.0 -``` - -The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. - -Your docs now have 2 versions: - -- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs -- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** - -## Add a Version Dropdown - -To navigate seamlessly across versions, add a version dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -export default { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'docsVersionDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The docs version dropdown appears in your navbar: - -![Docs Version Dropdown](./img/docsVersionDropdown.png) - -## Update an existing version - -It is possible to edit versioned docs in their respective folder: - -- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` -- `docs/hello.md` updates `http://localhost:3000/docs/next/hello` diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js deleted file mode 100644 index 846b55660..000000000 --- a/website/docusaurus.config.js +++ /dev/null @@ -1,57 +0,0 @@ -// @ts-check -const path = require('path'); - -const ORG = 'untoldengine'; -const REPO = 'UntoldEngine'; -const isDev = process.env.NODE_ENV !== 'production'; - -/** @type {import('@docusaurus/types').Config} */ -module.exports = { - title: 'Untold Engine Docs', - - // Use dev/prod URLs correctly - url: isDev ? 'http://localhost:3000' : `https://${ORG}.github.io`, - baseUrl: isDev ? '/' : `/${REPO}/`, - baseUrlIssueBanner: false, - favicon: 'img/favicon.ico', - - organizationName: ORG, - projectName: REPO, - deploymentBranch: 'gh-pages', - trailingSlash: false, - - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', - - i18n: { defaultLocale: 'en', locales: ['en'] }, - - presets: [ - [ - 'classic', - ({ - docs: { - path: path.resolve(__dirname, '..', 'docs'), - routeBasePath: 'docs', - sidebarPath: require.resolve('./sidebars.js'), - editUrl: `https://github.com/${ORG}/${REPO}/edit/master/docs/`, - showLastUpdateTime: true, - showLastUpdateAuthor: true, - onlyIncludeVersions: ['current'], - }, - blog: false, - theme: { customCss: require.resolve('./src/css/custom.css') }, - }), - ], - ], - - themeConfig: { - navbar: { - title: 'Untold Engine', - items: [ - { type: 'doc', docId: 'Intro', label: 'Docs', position: 'left' }, - { href: `https://github.com/${ORG}/${REPO}`, label: 'GitHub', position: 'right' }, - ], - }, - prism: { additionalLanguages: ['swift', 'c', 'cpp', 'hlsl', 'glsl'] }, - }, -}; diff --git a/website/package.json b/website/package.json deleted file mode 100644 index 8ade62461..000000000 --- a/website/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "website", - "version": "0.0.0", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "3.8.1", - "@docusaurus/plugin-client-redirects": "^3.8.1", - "@docusaurus/preset-classic": "3.8.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "3.8.1", - "@docusaurus/types": "3.8.1" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 3 chrome version", - "last 3 firefox version", - "last 5 safari version" - ] - }, - "engines": { - "node": ">=18.0" - } -} diff --git a/website/src/components/HomepageFeatures/styles.module.css b/website/src/components/HomepageFeatures/styles.module.css deleted file mode 100644 index b248eb2e5..000000000 --- a/website/src/components/HomepageFeatures/styles.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; -} - -.featureSvg { - height: 200px; - width: 200px; -} diff --git a/website/src/css/custom.css b/website/src/css/custom.css deleted file mode 100644 index 2bc6a4cfd..000000000 --- a/website/src/css/custom.css +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -/* You can override the default Infima variables here. */ -:root { - --ifm-color-primary: #2e8555; - --ifm-color-primary-dark: #29784c; - --ifm-color-primary-darker: #277148; - --ifm-color-primary-darkest: #205d3b; - --ifm-color-primary-light: #33925d; - --ifm-color-primary-lighter: #359962; - --ifm-color-primary-lightest: #3cad6e; - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -/* For readability concerns, you should choose a lighter palette in dark mode. */ -[data-theme='dark'] { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); -} diff --git a/website/src/pages/index.js b/website/src/pages/index.js deleted file mode 100644 index 668a869b1..000000000 --- a/website/src/pages/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import {Redirect} from '@docusaurus/router'; -import useBaseUrl from '@docusaurus/useBaseUrl'; - -export default function Home() { - return ; -} - diff --git a/website/static/.nojekyll b/website/static/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/static/img/docusaurus-social-card.jpg b/website/static/img/docusaurus-social-card.jpg deleted file mode 100644 index ffcb44821..000000000 Binary files a/website/static/img/docusaurus-social-card.jpg and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/000_HelloEditor.md b/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/000_HelloEditor.md deleted file mode 100644 index 02c658707..000000000 --- a/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/000_HelloEditor.md +++ /dev/null @@ -1,170 +0,0 @@ -# Getting Started with Untold Engine Studio - -This guide will walk you through the basics of getting up and running with **Untold Engine Studio**, from installation to loading your first model. - ---- - -## Downloading Untold Engine Studio - -To start developing with the Untold Engine, download the latest version of **Untold Engine Studio** from the official GitHub releases page: - -👉 https://github.com/untoldengine/UntoldEditor/releases - -Once downloaded: - -1. Drag **Untold Engine Studio** into your **Applications** folder -2. Double-click the app to launch it - ---- - -## Creating a New Project - -When the editor launches, you will be presented with the main startup screen. - -![editor_empty_view](../../images/Editor/Editor_scene_empty.png) - -At this point, create a new project: - -1. Click **New** -2. A new window will appear asking for: - - **Project name** - - **Target platform** - - **Project location** -3. Fill in the details and click **Create** - -Once completed, the engine will generate a fully configured **Xcode game project** for you. - -You will then be prompted to open the project in Xcode. I recommend doing so. - -![editor_scene_xcode](../../images/Editor/Editor_scene_xcode.png) - ---- - -## Exploring the Generated Project - -After opening the project in Xcode, take a moment to explore the file structure. This will help you navigate the project later. - -### Project Structure - -``` -MyGame/ # Your working directory -└── MyGame/ # Generated project - ├── MyGame.xcodeproj # Open this in Xcode - ├── project.yml # XcodeGen configuration - └── Sources/ - └── MyGame/ - ├── GameData/ # ← Put your assets here - │ ├── Models/ # 3D models - │ ├── Scenes/ # Scene files - │ ├── Scripts/ # USC scripts - │ ├── Textures/ # Images - │ └── ... - ├── GameScene.swift # Your game logic - ├── GameViewController.swift # View controller - └── AppDelegate.swift # App entry point -``` - -The most important file to look for is: - -```GameScene.swift``` - -This is where you will do most of your coding. -It contains the core lifecycle functions, including: - -- `init()` – scene setup -- `update()` – per-frame logic - -You’ll be spending most of your time here when writing game logic. - ---- - -## Back to the Editor - -Return to **Untold Engine Studio**. - -If you look at the editor toolbar, you’ll notice the name of your newly created project displayed on the right side of the window. - -At this point, you can already start adding content to your scene. - ---- - -## Adding Entities to the Scene - -You can quickly add a primitive by clicking the **“+”** button in the **Scenegraph View** and selecting a cube or other built-in shapes. - -However, it’s more fun to load real models. - ---- - -## Downloading Sample Models - -I’ve provided a small set of [sample models](https://haroldserrano.gumroad.com/l/iqjlac) you can use to get started: - -- A soccer player -- A soccer ball -- A stadium -- Idle and running animations - -All assets are provided as **`.usdz`** files. - -> **Note** -> Untold Engine currently accepts **USDZ** files only. - -Download the sample assets from the provided link. - ---- - -## Importing Models into Your Project - -Once the models are downloaded, it’s time to import them into your project. - -1. Open the **Asset Browser** -2. Select the **Model** category -3. Click **Import** -4. Navigate to the folder containing your downloaded `.usdz` file -5. Select a model and confirm - -After importing, the engine will display a feedback message confirming the import. -You will also see the `.usdz` file listed under the **Model** category. - ---- - -## Loading a Model into the Scene - -Now for the fun part. - -- **Double-click** the imported `.usdz` file in the Asset Browser - -The model will immediately appear in the editor viewport. - -![Editor_scene_model_viewport](../../images/Editor/Editor_scene_model_viewport.png) - -Behind the scenes, two things just happened: - -1. An **entity** was created -2. The `.usdz` asset was linked to that entity - ---- - -## Importing and Linking Animations - -The same workflow applies to animations. - -1. Select the **Animation** category in the Asset Browser -2. Click **Import** -3. Locate and import an animation `.usdz` file -4. Double-click the animation asset - -The animation will automatically be linked to the currently selected entity. - -If you open the **Inspector** tab, you’ll see the animation listed as part of the entity’s components. - ---- - -## What’s Next? - -Now that you have a basic understanding of the editor, asset workflow, and project structure, you’re ready to start coding with the Untold Engine. - -👉 Continue with the **Hello World** tutorial to write your first gameplay logic. - - diff --git a/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/01_Transform/02_RotateAnEntity.md b/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/01_Transform/02_RotateAnEntity.md deleted file mode 100644 index 1fc40aaaf..000000000 --- a/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/01_Transform/02_RotateAnEntity.md +++ /dev/null @@ -1,246 +0,0 @@ -# Rotate an Entity - -Learn how to rotate entities using the Transform System. - ---- - -## Overview - -This tutorial shows you how to: -- Rotate an entity to an absolute angle -- Rotate an entity incrementally -- Create smooth rotation using `deltaTime` - ---- - -## Prerequisites - -This tutorial assumes you have: -- A project with `GameScene.swift` open -- **A scene loaded** with at least one entity -- The entity has a name set in the editor (e.g., "Propeller") - -For complete API documentation: - -➡️ **[Transform System API](../../../04-Engine%20Development/03-Engine%20Systems/UsingTransformSystem.md)** - ---- - -## Step 1: Find the Entity from Your Scene - -In `GameScene.swift`, add a property to store the entity reference: - -```swift path=null start=null -class GameScene { - var propeller: EntityID! - - init() { - // ... setup code (setupAssetPaths, loadScene, etc.) ... - startGameSystems() - - // Find the entity by name (set in the editor) - propeller = findEntity(name: "Propeller") - - if propeller == nil { - Logger.logWarning(message: "Propeller entity not found in scene") - } - } -} -``` - ---- - -## Step 2: Rotate to an Absolute Angle - -Use `rotateTo()` to set an entity to a specific rotation: - -```swift path=null start=null -class GameScene { - var propeller: EntityID! - - init() { - // ... setup code ... - - propeller = findEntity(name: "Propeller") - - // Rotate 45 degrees around the Y-axis (up) - rotateTo(entityId: propeller, angle: 45.0, axis: SIMD3(0, 1, 0)) - } -} -``` - -**Result**: The entity immediately rotates to 45 degrees around the Y-axis. - ---- - -## Step 3: Rotate Incrementally - -Use `rotateBy()` to add rotation to the current orientation: - -```swift path=null start=null -class GameScene { - var propeller: EntityID! - - init() { - // ... setup code ... - - propeller = findEntity(name: "Propeller") - - // Rotate 15 degrees from current rotation - rotateBy(entityId: propeller, angle: 15.0, axis: SIMD3(0, 1, 0)) - } -} -``` - -**Result**: The entity rotates an additional 15 degrees around the Y-axis. - ---- - -## Step 4: Continuous Rotation in Update Loop - -For smooth spinning, use `rotateBy()` in `update(deltaTime:)`: - -```swift path=null start=null -class GameScene { - var propeller: EntityID! - let rotationSpeed: Float = 90.0 // Degrees per second - - init() { - // ... setup code ... - propeller = findEntity(name: "Propeller") - } - - func update(deltaTime: Float) { - if gameMode == false { return } - - // Rotate continuously around Y-axis - let angleThisFrame = rotationSpeed * deltaTime - rotateBy(entityId: propeller, angle: angleThisFrame, axis: SIMD3(0, 1, 0)) - } -} -``` - -**Result**: The propeller spins smoothly at 90 degrees per second. - ---- - -## Understanding Rotation Axes - -### Common Rotation Axes - -```swift path=null start=null -// Rotate around Y-axis (up) - typical yaw rotation -rotateBy(entityId: entity, angle: 45.0, axis: SIMD3(0, 1, 0)) - -// Rotate around X-axis (right) - pitch rotation -rotateBy(entityId: entity, angle: 45.0, axis: SIMD3(1, 0, 0)) - -// Rotate around Z-axis (forward) - roll rotation -rotateBy(entityId: entity, angle: 45.0, axis: SIMD3(0, 0, 1)) -``` - -### Using Entity's Local Axes - -You can also rotate around an entity's own forward/right/up vectors: - -```swift path=null start=null -// Rotate around entity's own up direction -let up = getUpAxisVector(entityId: entity) -rotateBy(entityId: entity, angle: 45.0, axis: up) - -// Rotate around entity's own right direction -let right = getRightAxisVector(entityId: entity) -rotateBy(entityId: entity, angle: 45.0, axis: right) -``` - ---- - -## Rotation Examples - -### Spin Clockwise (Y-axis) - -```swift path=null start=null -let angle = rotationSpeed * deltaTime -rotateBy(entityId: entity, angle: angle, axis: SIMD3(0, 1, 0)) -``` - -### Spin Counter-Clockwise (Y-axis) - -```swift path=null start=null -let angle = -rotationSpeed * deltaTime // Negative for opposite direction -rotateBy(entityId: entity, angle: angle, axis: SIMD3(0, 1, 0)) -``` - -### Tumble (X-axis) - -```swift path=null start=null -let angle = rotationSpeed * deltaTime -rotateBy(entityId: entity, angle: angle, axis: SIMD3(1, 0, 0)) -``` - -### Face a Direction - -To face a target, you typically calculate the direction and convert to rotation. This is more advanced, but here's a simple Y-axis example: - -```swift path=null start=null -let targetPos = SIMD3(10, 0, 5) -let currentPos = getPosition(entityId: entity) -let direction = normalize(targetPos - currentPos) - -// Calculate angle to target (simplified for Y-axis only) -let angle = atan2(direction.x, direction.z) * (180.0 / .pi) -rotateTo(entityId: entity, angle: angle, axis: SIMD3(0, 1, 0)) -``` - ---- - -## Checking Current Rotation - -To read an entity's current orientation: - -```swift path=null start=null -// World orientation matrix -let worldOrientation = getOrientation(entityId: entity) -Logger.log(message: "World orientation: \(worldOrientation)") - -// Local orientation matrix (relative to parent) -let localOrientation = getLocalOrientation(entityId: entity) -Logger.log(message: "Local orientation: \(localOrientation)") -``` - ---- - -## Combining Translation and Rotation - -You can move and rotate in the same frame: - -```swift path=null start=null -func update(deltaTime: Float) { - if gameMode == false { return } - - // Move forward - let forward = getForwardAxisVector(entityId: player) - let movement = forward * moveSpeed * deltaTime - translateBy(entityId: player, delta: movement) - - // Rotate based on input - let turnAngle = turnSpeed * deltaTime - rotateBy(entityId: player, angle: turnAngle, axis: SIMD3(0, 1, 0)) -} -``` - ---- - -## Summary - -You've learned: - -✅ `rotateTo()` - Set absolute rotation angle -✅ `rotateBy()` - Rotate incrementally from current orientation -✅ `deltaTime` - Make rotation frame-rate independent -✅ Rotation axes - Control rotation direction -✅ `getOrientation()` - Read current rotation - ---- - - diff --git a/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md b/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md deleted file mode 100644 index 8c903a355..000000000 --- a/website/versioned_docs/version-0.10.10/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md +++ /dev/null @@ -1,252 +0,0 @@ -# Play Animation - -Learn how to load and play animations on entities. - ---- - -## Overview - -This tutorial shows you how to: -- Load animation data from a `.usdc` file -- Play an animation on an entity -- Pause animations - ---- - -## Prerequisites - -This tutorial assumes you have: -- A project with `GameScene.swift` open -- **A scene loaded** with at least one entity that has an animation skeleton -- An animation file (e.g., `running.usdc`) in `GameData/Models/` or `GameData/Animations/` -- The entity has a name set in the editor (e.g., "Player") -- The entity has animations linked in the editor - -For complete API documentation: - -➡️ **[Animation System API](../../../04-Engine%20Development/03-Engine%20Systems/UsingAnimationSystem.md)** - ---- - -## Step 1: Find the Entity - -In `GameScene.swift`, add a property to store the entity reference: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - startGameSystems() - - // Find the entity by name (set in the editor) - player = findEntity(name: "Player") - } -} -``` - ---- - -## Step 2: Load the Animation - ->>> Skip this section if you linked an animation to the entity through the editor. - -Use `setEntityAnimations()` to load animation data: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load the running animation - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - } -} -``` - -**Parameters**: -- `filename`: The animation file name (without extension) -- `withExtension`: File extension (usually `"usdc"`) -- `name`: A label to reference this animation later - ---- - -## Step 3: Play the Animation - -Use `changeAnimation()` to start playing the animation: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load animation -- Ignore if you linked an animation through the editor - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - - // Play the animation - we assume that the animation linked is called running - changeAnimation(entityId: player, name: "running") - } -} -``` - -**Result**: The entity plays the "running" animation in a loop. - ---- - -## Step 4: Pause the Animation (Optional) - -To pause or resume an animation: - -```swift path=null start=null -// Pause the animation -pauseAnimationComponent(entityId: player, isPaused: true) - -// Resume the animation -pauseAnimationComponent(entityId: player, isPaused: false) -``` - ---- - -## Loading Multiple Animations - -You can load multiple animations for the same entity: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load multiple animations -- Ignore if you linked all three animations through the editor - setEntityAnimations( - entityId: player, - filename: "idle", - withExtension: "usdc", - name: "idle" - ) - - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - - setEntityAnimations( - entityId: player, - filename: "jumping", - withExtension: "usdc", - name: "jumping" - ) - - // Start with idle animation - changeAnimation(entityId: player, name: "idle") - } -} -``` - ---- - -## Switching Animations at Runtime - -Switch between animations based on game state: - -```swift path=null start=null -class GameScene { - var player: EntityID! - var isRunning: Bool = false - - init() { - // ... setup code ... - startGameSystems() - - // Register input keyboard events - InputSystem.shared.registerKeyboardEvents() - - } - - func update(deltaTime: Float) { - if gameMode == false { return } - - // Check if player is moving - let wasRunning = isRunning - isRunning = inputSystem.keyState.wPressed || - inputSystem.keyState.aPressed || - inputSystem.keyState.sPressed || - inputSystem.keyState.dPressed - - // Switch animation when state changes - if isRunning && !wasRunning { - changeAnimation(entityId: player, name: "running") - } else if !isRunning && wasRunning { - changeAnimation(entityId: player, name: "idle") - } - } -} -``` - ---- - -## File Organization - -Animation files should be placed in your project's `GameData` directory: - - -``` -Sources/YourProject/GameData/ -├── Models/ -│ └── player.usdz -├── Animations/ -│ ├── idle.usdc -│ ├── running.usdc -│ └── jumping.usdc -``` - ---- - -## Troubleshooting - -### Animation doesn't play - -1. **Check the entity has a skeleton**: Not all models support animation. The model must have a skeletal rig. -2. **Verify file path**: Make sure the animation file exists in `GameData/Models/` or `GameData/Animations/`. -3. **Check animation name**: The `name` parameter must match when loading and playing. - -### Animation plays too fast/slow - -Animation playback speed is controlled by the animation file itself. To adjust speed, you'll need to modify the animation in your 3D software (e.g., Blender, Maya) or use animation blending (advanced topic). - ---- - -## Summary - -You've learned: - -✅ `setEntityAnimations()` - Load animation data -✅ `changeAnimation()` - Play a specific animation -✅ `pauseAnimationComponent()` - Pause/resume animations -✅ Load multiple animations per entity -✅ Switch animations based on game state - ---- - - diff --git a/website/versioned_docs/version-0.10.10/03-Game Development/04-Scripting-Experimental/03-Tutorials/01_Transform/02_RotateAnEntity.md b/website/versioned_docs/version-0.10.10/03-Game Development/04-Scripting-Experimental/03-Tutorials/01_Transform/02_RotateAnEntity.md deleted file mode 100644 index 7e92ccbb5..000000000 --- a/website/versioned_docs/version-0.10.10/03-Game Development/04-Scripting-Experimental/03-Tutorials/01_Transform/02_RotateAnEntity.md +++ /dev/null @@ -1,239 +0,0 @@ -# Rotate an Entity - Continuous Spin - -**What you'll learn:** -- Rotating entities with `rotateTo()` inside `onUpdate()` -- Tracking angles with script variables -- Choosing rotation axes with `simd_float3` -- Building USC scripts entirely in Xcode - -**Time:** ~7 minutes - -**Prerequisites:** -- Untold Engine Studio open with any entity selected (a cube makes rotation easy to see) -- Access to Xcode via **Scripts: Open in Xcode** (toolbar button) - ---- - -## What We're Building - -A rotation script that: -1. Spins an entity around a chosen axis -2. Uses a variable to track the current angle -3. Lets you adjust spin speed without rewriting the script - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `RotateAnEntity` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateRotateAnEntity(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateRotateAnEntity(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateRotateAnEntity) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateRotateAnEntity(to dir: URL) { - let script = buildScript(name: "RotateAnEntity") { s in - // Runs once when the entity starts - s.onStart() - .setVariable("rotationSpeed", to: 2.0) // degrees per frame - .setVariable("currentAngle", to: 0.0) - .setVariable("axis", to: simd_float3(x: 0, y: 1, z: 0)) - .log("RotateAnEntity ready") - - // Runs every frame - s.onUpdate() - .addFloat("currentAngle", "rotationSpeed", as: "nextAngle") - .setVariable("currentAngle", to: .variableRef("nextAngle")) - .rotateTo( - degrees: .variableRef("currentAngle"), - axis: simd_float3(x:0,y:1,z:0) - ) - } - - let outputPath = dir.appendingPathComponent("RotateAnEntity.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ RotateAnEntity.uscript") - } -} -``` - -### Understanding the Code - -**`rotationSpeed`** - Controls how much to rotate each frame -- Higher values spin faster -- Change this without touching the rest of the script - -**`currentAngle`** - Tracks total rotation -- Prevents drift and keeps rotation smooth - -**`axis`** - A `simd_float3` defining which way to spin -- `(0, 1, 0)` spins around Y (like a turntable) -- `(1, 0, 0)` spins forward/backward -- `(0, 0, 1)` spins left/right - -**`rotateTo()`** - Sets an absolute rotation -- Uses the stored angle each frame -- Cleanly resets if you set `currentAngle` back to `0` - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ RotateAnEntity.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Select the entity you want to rotate -3. In the Inspector panel, click **Add Component** → **Script Component** -4. In the Asset Browser, find `RotateAnEntity.uscript` under Scripts/Generated -5. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 6: Test It! - -1. Click **Play** in the toolbar -2. Watch the entity spin around the Y-axis -3. Open the **Console** view to confirm: - ``` - RotateAnEntity ready - ``` -4. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- The entity rotates smoothly using `rotationSpeed` degrees per frame -- Changing `axis` changes the spin direction without new code -- Resetting `currentAngle` to `0` realigns the entity instantly - -⚠️ **Consistency Note:** Large `rotationSpeed` values can cause visible jumps. Increase gradually for smoother motion. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Rotate with `rotateBy()` -```swift -s.onUpdate() - .rotateBy( - degrees: 1.0, - axis: simd_float3(x: 0, y: 1, z: 0) - ) -``` - -### Spin on Multiple Axes -```swift -.setVariable("axis", to: simd_float3(x: 1, y: 1, z: 0)) // diagonal spin -``` - -### Start Facing a Specific Direction -```swift -s.onStart() - .rotateTo( - degrees: 45.0, - axis: simd_float3(x: 0, y: 1, z: 0) - ) - .setVariable("currentAngle", to: 45.0) -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ Creating a rotation script in Untold Engine Studio -✅ Tracking angles with script variables -✅ Rotating entities around any axis -✅ Building and attaching scripts to entities -✅ Testing rotations in Play mode - ---- - diff --git a/website/versioned_docs/version-0.10.10/04-Engine Development/02-Architecture/Overview.md b/website/versioned_docs/version-0.10.10/04-Engine Development/02-Architecture/Overview.md deleted file mode 100644 index ae33b9cd6..000000000 --- a/website/versioned_docs/version-0.10.10/04-Engine Development/02-Architecture/Overview.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -id: engine-architecture overview -title: Architecture Overview -sidebar_position: 1 ---- - -# Engine Architecture - -This document describes the **high-level architecture** of Untold Engine. - -It is intended for developers who want to understand how the engine is structured *before* working on individual subsystems such as rendering, physics, or scripting. - -This page focuses on **concepts and responsibilities**, not file layouts or implementation details. - ---- - -## Architectural Philosophy - -Untold Engine is designed with a small set of guiding principles: - -- **Clarity over abstraction** - Systems should be understandable without deep indirection. - -- **Explicit execution order** - Engine behavior is deterministic and visible. - -- **Data-oriented design** - Data and behavior are separated cleanly. - -- **Composable systems** - New systems can be added without destabilizing existing ones. - -The architecture favors *learnability and debuggability* over convenience shortcuts. - ---- - -## High-Level Structure - -At a conceptual level, Untold Engine is organized into the following layers: - -Gameplay (USC Scripts) -↓ -Engine Systems -↓ -Core Runtime -↓ -Platform & Rendering Backends - -Each layer has a single responsibility and communicates downward through well-defined interfaces. - ---- - -## Core Runtime - -The **core runtime** is responsible for: - -- Entity and component storage -- System registration and ordering -- Scene lifecycle management -- Global engine state -- Frame orchestration - -The runtime does **not** contain gameplay logic, editor logic, or platform-specific behavior. - -Its role is to provide a **stable execution environment** for all systems. - ---- - -## Entity–Component–System (ECS) - -Untold Engine uses an **Entity–Component–System (ECS)** architecture. - -### Entities -- Lightweight identifiers -- Contain no data or behavior -- Used to associate components - -### Components -- Plain data containers -- Represent state (transform, physics, rendering, scripting, etc.) -- Do not contain logic - -### Systems -- Contain all behavior -- Operate on entities matching specific component sets -- Are executed in an explicit order - -This separation keeps systems focused and minimizes hidden dependencies. - ---- - -## System Execution Model - -Systems are executed as part of a well-defined update flow. - -A simplified frame looks like this: - -1. Input collection -2. Simulation and gameplay systems -3. USC script evaluation -4. Physics integration -5. Culling and render preparation -6. Rendering and presentation - -There is no implicit or callback-driven execution. - -Each system declares: -- When it runs -- What data it reads -- What data it writes - ---- - -## Rendering Architecture - -Rendering in Untold Engine is **explicit and staged**. - -Key characteristics: -- Clear separation between simulation and rendering -- Explicit render passes -- Minimal global render state -- Platform-specific backends hidden behind a thin abstraction - -Rendering is treated as a system, not a special case. - ---- - -## Platform Abstraction - -Untold Engine supports multiple platforms through **thin platform layers**. - -These layers handle: -- Window and surface management -- Input sources -- Graphics API integration -- Platform lifecycle events - -The core engine remains platform-agnostic. - ---- - -## USC Integration - -USC (Untold Script Core) is layered on top of the engine. - -- The engine owns the update loop -- USC expresses gameplay intent -- Scripts interact with engine state through a constrained API - -USC does not bypass engine systems. - -This ensures predictable behavior and keeps ownership of state clear. - ---- - -## Editor Relationship - -The Untold Editor is built **on top of the same engine runtime** used by games. - -- Editor features are implemented as engine clients -- Editor-only systems are isolated -- Play mode uses the same execution path as runtime builds - -This minimizes editor-only behavior and keeps debugging consistent. - ---- - -## Design Tradeoffs - -Untold Engine intentionally avoids: - -- Hidden execution order -- Large global managers -- Overly generic abstractions -- Implicit engine magic - -These tradeoffs prioritize: -- Predictability -- Debuggability -- Long-term maintainability - ---- - -## Architecture in Practice - -This document provides the conceptual overview. - -For implementation-level details, see: - -Engine Development → Architecture → Engine internals - diff --git a/website/versioned_docs/version-0.10.10/04-Engine Development/02-Architecture/_category.json b/website/versioned_docs/version-0.10.10/04-Engine Development/02-Architecture/_category.json deleted file mode 100644 index b32af10ff..000000000 --- a/website/versioned_docs/version-0.10.10/04-Engine Development/02-Architecture/_category.json +++ /dev/null @@ -1 +0,0 @@ -{ "label": "Architecture", "position": 2, "collapsed": false } diff --git a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingLightingSystem.md b/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingLightingSystem.md deleted file mode 100644 index 51ad9176f..000000000 --- a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingLightingSystem.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: lightinsystem -title: Lighting System -sidebar_position: 3 ---- - -# Enabling the Lighting System in Untold Engine - -The Lighting System lets you add illumination to your scenes using common real-time light types. Under the hood it wires up the required ECS components, provides an editor-friendly visual handle, and tags the light so the renderer can pick it up. - ---- - -## Creating Each Light Type -### Directional Light - -Use for sunlight or distant key lights. Orientation (rotation) defines its direction. - -```swift -let sun = createEntity() -createDirLight(entityId: sun) -``` -### Point Light - -Omni light that radiates equally in all directions from a position. - -```swift -let bulb = createEntity() -createPointLight(entityId: bulb) -``` - -### Spot Light - -Cone-shaped light with a position and direction. - -```swift -let spot = createEntity() -createSpotLight(entityId: spot) -``` - -### Area Light - -Rect/area emitter used to mimic panels/windows; position and orientation matter. - -```swift -let panel = createEntity() -createAreaLight(entityId: panel) -``` - diff --git a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingMaterials.md b/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingMaterials.md deleted file mode 100644 index 34fc98a62..000000000 --- a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingMaterials.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -id: materials -title: Materials -sidebar_position: 14 ---- - -# Using Materials in Untold Engine - -The engine uses a PBR (Physically Based Rendering) material model. Each entity's mesh can contain one or more submeshes, and each submesh holds its own `Material`. You can read and write individual material properties at runtime using the functions below. - -All material functions accept optional `meshIndex` and `submeshIndex` parameters (both default to `0`) so you can target a specific submesh when an entity contains more than one. - -> **Note:** Every update function automatically refreshes static batching for the affected entity, so you do not need to do this manually. - ---- - -## Base Color - -The base color is stored as a `simd_float4` (RGBA). The `.w` component doubles as the opacity channel. - -### Get Base Color - -```swift -let color = getMaterialBaseColor(entityId: entity) -// color.x = red, color.y = green, color.z = blue, color.w = alpha -``` - -### Set Base Color via SwiftUI Color - -```swift -updateMaterialColor(entityId: entity, color: .red) -``` - -This converts the SwiftUI `Color` to RGBA internally. If the alpha is below `1.0`, the material automatically switches to `.blend` alpha mode. - ---- - -## Roughness - -Controls how rough or smooth a surface appears. A value of `0.0` is perfectly smooth (mirror-like reflections) and `1.0` is fully rough (diffuse). - -### Get Roughness - -```swift -let roughness = getMaterialRoughness(entityId: entity) -``` - -### Set Roughness - -```swift -updateMaterialRoughness(entityId: entity, roughness: 0.3) -``` - -> When a roughness **texture** is present, the scalar value acts as a modulator (multiplied with the texture sample in the shader). If you remove the texture, the scalar value is used directly. - ---- - -## Metallic - -Controls how metallic a surface appears. `0.0` is fully dielectric (plastic, wood, etc.) and `1.0` is fully metallic. - -### Get Metallic - -```swift -let metallic = getMaterialMetallic(entityId: entity) -``` - -### Set Metallic - -```swift -updateMaterialMetallic(entityId: entity, metallic: 1.0) -``` - -> Like roughness, the scalar value modulates the metallic texture when one is present. - ---- - -## Emissive - -Controls self-illumination. The value is a `simd_float3` (RGB) representing the emitted light color and intensity. A value of `.zero` means no emission. - -### Get Emissive - -```swift -let emissive = getMaterialEmmissive(entityId: entity) -``` - -### Set Emissive - -```swift -updateMaterialEmmisive(entityId: entity, emmissive: simd_float3(1.0, 0.5, 0.0)) -``` - -> **Spelling note:** The API currently uses `getMaterialEmmissive` / `updateMaterialEmmisive` (with double-m). Use these exact names when calling the functions. - ---- - -## Alpha Mode - -Determines how the renderer handles transparency for this material. - -### Available Modes (`MaterialAlphaMode`) - -- **`.opaque`** — Fully opaque. Alpha channel is ignored. -- **`.mask`** — Binary transparency. Pixels with alpha below the cutoff are discarded; the rest are fully opaque. Useful for foliage, fences, etc. -- **`.blend`** — Smooth alpha blending. Pixels are composited based on their alpha value. - -### Get Alpha Mode - -```swift -let mode = getMaterialAlphaMode(entityId: entity) // returns MaterialAlphaMode -``` - -### Set Alpha Mode - -```swift -updateMaterialAlphaMode(entityId: entity, mode: .blend) -``` - ---- - -## Alpha Cutoff - -Used only when the alpha mode is `.mask`. Pixels with alpha below this threshold are discarded. The value is clamped to `0.0 ... 1.0`. Default is `0.5`. - -### Get Alpha Cutoff - -```swift -let cutoff = getMaterialAlphaCutoff(entityId: entity) -``` - -### Set Alpha Cutoff - -```swift -updateMaterialAlphaCutoff(entityId: entity, cutoff: 0.3) -``` - ---- - -## Opacity - -A convenience layer over the base color's alpha channel (`.w`). The value is clamped to `0.0 ... 1.0`. Setting opacity below `1.0` automatically switches the alpha mode to `.blend`. - -### Get Opacity - -```swift -let opacity = getMaterialOpacity(entityId: entity) -``` - -### Set Opacity (all submeshes) - -```swift -updateMaterialOpacity(entityId: entity, opacity: 0.5) -``` - -By default this applies to **every submesh** on the entity. To target a single submesh instead: - -```swift -updateMaterialOpacity(entityId: entity, opacity: 0.5, applyToAllSubmeshes: false) -``` - -Or specify exact indices: - -```swift -updateMaterialOpacity(entityId: entity, opacity: 0.5, meshIndex: 0, submeshIndex: 1) -``` - ---- - -## Quick Reference - -- `getMaterialBaseColor(entityId:meshIndex:submeshIndex:)` → `simd_float4` -- `updateMaterialColor(entityId:color:meshIndex:submeshIndex:)` — sets base color from SwiftUI `Color` -- `getMaterialRoughness(entityId:meshIndex:submeshIndex:)` → `Float` -- `updateMaterialRoughness(entityId:roughness:meshIndex:submeshIndex:)` -- `getMaterialMetallic(entityId:meshIndex:submeshIndex:)` → `Float` -- `updateMaterialMetallic(entityId:metallic:meshIndex:submeshIndex:)` -- `getMaterialEmmissive(entityId:meshIndex:submeshIndex:)` → `simd_float3` -- `updateMaterialEmmisive(entityId:emmissive:meshIndex:submeshIndex:)` -- `getMaterialAlphaMode(entityId:meshIndex:submeshIndex:)` → `MaterialAlphaMode` -- `updateMaterialAlphaMode(entityId:mode:meshIndex:submeshIndex:)` -- `getMaterialAlphaCutoff(entityId:meshIndex:submeshIndex:)` → `Float` -- `updateMaterialAlphaCutoff(entityId:cutoff:meshIndex:submeshIndex:)` -- `getMaterialOpacity(entityId:meshIndex:submeshIndex:)` → `Float` -- `updateMaterialOpacity(entityId:opacity:applyToAllSubmeshes:)` -- `updateMaterialOpacity(entityId:opacity:meshIndex:submeshIndex:)` diff --git a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingSpatialInput.md b/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingSpatialInput.md deleted file mode 100644 index db5f50c1f..000000000 --- a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingSpatialInput.md +++ /dev/null @@ -1,399 +0,0 @@ ---- -id: spatialinput -title: Spatial Input VisionPro -sidebar_position: 13 ---- - -# Spatial Input (vision Pro) - -Spatial input in Untold Engine follows a simple pipeline: - -1. visionOS emits raw spatial events. -2. UntoldEngineXR converts each event into an XRSpatialInputSnapshot. -3. Snapshots are queued in InputSystem. -4. XRSpatialGestureRecognizer processes snapshots each frame. -5. The engine publishes a single XRSpatialInputState your game reads in handleInput(). - -That separation keeps the system flexible: the OS-facing code stays in UntoldEngineXR, while gesture classification stays in -the recognizer. - -## What You Get in Game Code - -From XRSpatialInputState, you can read: - -- spatialTapActive -- spatialDragActive -- spatialPinchActive -- spatialPinchDragDelta -- spatialZoomActive + spatialZoomDelta -- spatialRotateActive + spatialRotateDeltaRadians -- pickedEntityId - -So your game logic can stay focused on behavior (select, move, rotate, scale), not event parsing. - -## Important Setup Step - -You must enable XR event ingestion: - -InputSystem.shared.registerXREvents() - -If you skip this, the callback still receives OS events, but the engine ignores them. - -## Typical Frame Usage - -In your handleInput(): - -- Poll InputSystem.shared.xrSpatialInputState. -- React to edge-triggered gestures like tap. -- Apply continuous updates for drag/zoom/rotate while active. - -For object manipulation, use SpatialManipulationSystem for robust pinch-driven transforms, then layer custom behavior on top -when needed. - -## Quick Example - -This example shows how to drag and rotate a mesh using the engine: - -``` swift -func handleInput() { - if gameMode == false { return } - - let state = InputSystem.shared.xrSpatialInputState - - if state.spatialTapActive, let entityId = state.pickedEntityId { - Logger.log(message: "Tapped entity: \(entityId)") - } - - // Handles drag-based translate + twist rotation on picked entity - SpatialManipulationSystem.shared.processPinchTransformLifecycle(from: state) -} -``` - -### What This Does - -- **Tap** → selects entity (via raycast picking) -- **Pinch + Drag** → translates entity in world space -- **Pinch + Twist** → rotates entity around a computed axis - -`processPinchTransformLifecycle` handles: - -- Begin -- Update -- End -- Cancel - -This lifecycle model prevents stuck manipulation sessions. - ------------------------------------------------------------------------- - -### Manipulate Parent Instead Of Picked Child - -If ray picking hits a child mesh and you want to manipulate the parent -actor: - -``` swift -var state = InputSystem.shared.xrSpatialInputState - -if let picked = state.pickedEntityId, - let parent = getEntityParent(entityId: picked) { - state.pickedEntityId = parent -} - -SpatialManipulationSystem.shared.processPinchTransformLifecycle(from: state) -``` - -This is useful when: - -- A character has multiple meshes -- A building has sub-meshes -- You want to move the root actor instead of individual geometry - pieces - ------------------------------------------------------------------------- - -### Important Note - -Do not early-return only because `pickedEntityId == nil` before calling -lifecycle processing. - -End/cancel phases must still propagate to properly close manipulation -sessions.\ -Failing to do so can leave the engine in an inconsistent transform -state. - ------------------------------------------------------------------------- - -# Raw Gesture Examples - -It is strongly recommended to use the Spatial Helper functions instead -of raw gesture access. - -Raw access is useful when: - -- You want custom manipulation behavior -- You are building a custom editor -- You want non-standard gesture responses - ------------------------------------------------------------------------- - -## Tap (Selection) - -Vision Pro air-tap gesture. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.spatialTapActive, let entityId = state.pickedEntityId { - // selectEntity(entityId) -} -``` - -Use this to: - -- Select objects -- Trigger UI -- Activate gameplay logic - ------------------------------------------------------------------------- - -## Pinch Active - -Single-hand pinch detected. - -``` swift -if InputSystem.shared.hasSpatialPinch() { - // pinch is active -} -``` - -This does **not** imply dragging yet --- only that a pinch is currently -held. - ------------------------------------------------------------------------- - -## Pinch Position - -World-space position of pinch. - -``` swift -if let pinchPosition = InputSystem.shared.getPinchPosition() { - // use pinchPosition -} -``` - -Useful for: - -- Placing objects -- Spawning actors -- Visual debugging - ------------------------------------------------------------------------- - -## Pinch Drag Delta - -Drag delta while pinch is active. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.spatialPinchActive { - let dragDelta = InputSystem.shared.getPinchDragDelta() - // app-defined translation/scaling response -} -``` - -Common use cases: - -- Translate object along plane -- Move UI panels -- Drag actors in world space - ------------------------------------------------------------------------- - -## Anchored Pinch Drag Helper - -For stable translation (no per-frame delta accumulation), use the -anchored lifecycle helper: - -``` swift -func handleInput() { - let state = InputSystem.shared.xrSpatialInputState - - SpatialManipulationSystem.shared.processAnchoredPinchDragLifecycle( - from: state, - entityId: sceneRootEntity - ) -} -``` - -This helper: - -- Captures initial hand + entity world positions -- Applies absolute displacement from gesture start -- Cleans up session state on end/cancel - -Use this when moving large roots (buildings/scenes) where incremental -delta jitter can become visible. - ------------------------------------------------------------------------- - -## Anchored Scene Drag Helper - -For translating the **entire scene root** (rather than a single entity), use the anchored scene drag lifecycle: - -``` swift -func handleInput() { - let state = InputSystem.shared.xrSpatialInputState - - SpatialManipulationSystem.shared.processAnchoredSceneDragLifecycle(from: state) -} -``` - -This helper: - -- Captures initial hand + scene root world positions on drag start -- Applies absolute displacement from gesture start via `translateSceneTo`, keeping static batches intact -- Cleans up session state on end/cancel - -You can adjust movement speed with the `sensitivity` parameter (defaults to `1.0`): - -``` swift -SpatialManipulationSystem.shared.processAnchoredSceneDragLifecycle(from: state, sensitivity: 0.5) -``` - -To manually end the drag (e.g. on a mode change), call: - -``` swift -SpatialManipulationSystem.shared.endAnchoredSceneDrag() -``` - -Use this when panning an entire scene — for example, sliding a map, architectural model, or level layout in world space. - ------------------------------------------------------------------------- - -## Two-Hand Zoom Signal (Coming soon) - -Two hands pinching and moving closer/farther. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.leftHandPinching, state.rightHandPinching, state.spatialZoomActive { - let zoomDelta = InputSystem.shared.getSpatialZoomDelta() - // app-defined zoom response -} -``` - -### Typical Behavior Options - -You decide what zoom means: - -- Scale selected object -- Move object closer/farther in world space -- Adjust camera rig distance -- Modify FOV (if using custom projection control) - -Untold Engine does not automatically change camera FOV.\ -You define the semantic meaning of zoom. - ------------------------------------------------------------------------- - -## Two-Hand Rotate Signal (Coming soon) - -Two hands pinching and rotating around each other. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.leftHandPinching, state.rightHandPinching, state.spatialRotateActive { - let deltaRadians = InputSystem.shared.getSpatialRotateDelta() - let axisWorld = InputSystem.shared.getSpatialRotateAxisWorld() - // app-defined rotate response -} -``` - -Typical usage: - -- Rotate object in world space -- Rotate parent actor -- Rotate UI panel in 3D - -`axisWorld` allows you to apply physically intuitive rotations rather -than arbitrary axes. - -## Get distance to hit-entity - -To get the distance to an entity use the following: - -```swift -// Get distance to hit-entity -let state = InputSystem.shared.xrSpatialInputState -if state.spatialTapActive, let entityId = state.pickedEntityId { - // get distance - let distance = state.pickedEntityDistance - print("Object distance: \(distance) meters") -} -``` - ------------------------------------------------------------------------- - -## Get Ground Hit Position - -To retrieve the exact world-space position where the user taps on the ground, use `pickGroundPosition`. This is useful for calibration workflows where you need to anchor a point on the ground and scale a model relative to it. - -```swift -let state = InputSystem.shared.xrSpatialInputState -if state.spatialTapActive { - if let groundHit = pickGroundPosition( - rayOrigin: state.rayOriginWorld, - rayDirection: state.rayDirectionWorld - ) { - // groundHit.worldPosition is the exact 3D point on the ground - // groundHit.distance is the ray travel distance to that point - anchorCalibrationPoint(at: groundHit.worldPosition) // pseudo-code to anchor entity - } -} -``` - -By default the ground plane is at y = 0. Pass a custom height with the `planeY` parameter: - -```swift -let groundHit = pickGroundPosition( - rayOrigin: state.rayOriginWorld, - rayDirection: state.rayDirectionWorld, - planeY: 1.5 // ground at y = 1.5 -) -``` - -For arbitrary planes (walls, ramps, etc.), use `pickPlanePosition`: - -```swift -let wallHit = pickPlanePosition( - rayOrigin: state.rayOriginWorld, - rayDirection: state.rayDirectionWorld, - planePoint: simd_float3(0, 0, 5), // a point on the wall - planeNormal: simd_float3(0, 0, -1) // wall faces toward camera -) -``` - -Both functions return `nil` when the ray is parallel to the plane or pointing away from it. - ------------------------------------------------------------------------- - -# Spatial Helper Functions - -Use these helpers from `SpatialManipulationSystem.shared`: - -- `processPinchTransformLifecycle(from:)`\ - Recommended default. Handles translation + twist rotation lifecycle - safely. - -- `applyPinchDragIfNeeded(from:entityId:sensitivity:)`\ - Lower-level translation helper if you want full control. - -- `processAnchoredSceneDragLifecycle(from:sensitivity:)`\ - Anchored drag for the entire scene root. Applies absolute - displacement via `translateSceneTo`. - -- `endAnchoredSceneDrag()`\ - Manually ends an in-progress anchored scene drag session. - -- `applyTwoHandZoomIfNeeded(from:sensitivity:)`\ - Provides zoom delta signal. You must define what zoom means in your - app. diff --git a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingSteeringSystem.md b/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingSteeringSystem.md deleted file mode 100644 index 05c8465be..000000000 --- a/website/versioned_docs/version-0.10.10/04-Engine Development/03-Engine Systems/UsingSteeringSystem.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -id: steeringsystem -title: Steering System -sidebar_position: 7 ---- - -# Using the Steering System in Untold Engine - -The Steering System in the Untold Engine enables entities to move dynamically and intelligently within the scene. It provides both low-level steering behaviors (e.g., seek, flee, arrive) for granular control and high-level behaviors (e.g., steerTo, steerAway, followPath) that integrate seamlessly with the Physics System. - -## Why Use the Steering System? - -The Steering System is essential for creating dynamic and realistic movement for entities, such as: - -- A character chasing a target. -- An enemy avoiding obstacles. -- A vehicle following a predefined path. - -The high-level behaviors are recommended because they are designed to work closely with the Physics System, simplifying implementation while maintaining smooth motion. - ---- - -## How to Use the Steering System - -Examples: - -1. Steer Toward a Target Position: - -```swift -steerSeek(entityId: entity, targetPosition: targetPosition, maxSpeed: 5.0, deltaTime: 0.016) -``` -2. Steer Away from a Threat: - -```swift -steerFlee(entityId: entity, threatPosition: threatPosition, maxSpeed: 5.0, deltaTime: 0.016) -``` - -3. Follow a Path: Guide an entity along a series of waypoints. - -```swift -steerFollowPath(entityId: entity, path: waypoints, maxSpeed: 5.0, deltaTime: 0.016) -``` -4. Pursue a Moving Target: - -```swift -steerPursuit(entityId: chaserEntity, targetEntity: targetEntity, maxSpeed: 5.0, deltaTime: 0.016) -``` - -5. Avoid Obstacles: - -```swift -steerAvoidObstacles(entityId: entity, obstacles: obstacleEntities, avoidanceRadius: 2.0, maxSpeed: 5.0, deltaTime: 0.016) -``` - -6. Steer Toward a Target Position (with Arrive): - -```swift -steerArrive(entityId: entity, targetPosition: targetPosition, maxSpeed: 5.0, deltaTime: 0.016) -``` - -7. Steer using WASD keys - -```swift -steerWithWASD(entityId: entity, maxSpeed: 5.0, deltaTime: 0.016) -``` - ---- - -## What Happens Behind the Scenes? - -1. Low-Level Behaviors: -- Calculate desired velocity based on the target or threat position. -- Generate steering forces by comparing desired velocity with current velocity. -2. High-Level Behaviors: -- Use low-level behaviors to calculate steering adjustments. -- Apply these forces to the entity’s physics system for smooth, realistic motion. -- Align the entity’s orientation to face its movement direction. -3. Physics Integration: -- Forces are applied through the Physics System, ensuring that movement respects mass, velocity, and acceleration. - ---- - -## Tips and Best Practices -- Prefer High-Level Behaviors: They simplify complex movement patterns and automatically handle integration with the Physics System. -- Use Low-Level Behaviors for Custom Logic: When precise control is required, combine low-level behaviors for unique movement styles. -- Smooth Orientation: Use alignOrientation or integrate orientation alignment directly into high-level functions. -- Tune Parameters: Adjust maxSpeed, turnSpeed, and slowingRadius for different entity types (e.g., fast-moving cars vs. slow-moving enemies). - ---- - -## Common Issues and Fixes - -### Issue: Entity Doesn’t Move - -- Cause: The Physics Component is missing or paused. -- Solution: Ensure the entity has a PhysicsComponents and it’s not paused. - -### Issue: Jittery Movement - -- Cause: Conflicting forces or large delta times. -- Solution: Tune maxSpeed and ensure deltaTime is passed correctly. - -### Issue: Entity Ignores Obstacles - -- Cause: Avoidance radius is too small or obstacles are not registered. -- Solution: Increase the avoidanceRadius and verify obstacle entities. - diff --git a/website/versioned_docs/version-0.10.10/07-Contributor/ContributionGuidelines.md b/website/versioned_docs/version-0.10.10/07-Contributor/ContributionGuidelines.md deleted file mode 100644 index 5f52ebeb9..000000000 --- a/website/versioned_docs/version-0.10.10/07-Contributor/ContributionGuidelines.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -id: contributorguidelines -title: Contributor Guidelines -sidebar_position: 1 ---- - -# Contributing Guidelines - -Thank you for your interest in contributing! - -The vision for Untold Engine is to continually shape a 3D engine that is **stable, performant and developer-friendly**. -As maintainer, my focus is on **performance, testing, quality control, and API design**. -Contributors are encouraged to expand features, fix bugs, improve documentation, and enhance usability — always keeping the vision in mind. - ---- - -## Maintainer Responsibilities - -The Untold Engine is guided by a clear vision: To be a stable, performant, and developer-friendly 3D engine that empowers creativity, removes friction, and makes game development feel effortless. - -## Guiding Principles - -To achieve this vision, we follow these principles: - -- The engine strives to remain stable and crash-free. -- The codebase is backed by unit tests. -- We profile continuously to prevent regressions (visual and performance). -- The API must remain clear and user-friendly. -- We always think about the developer first—removing friction so they can focus on their games. - -As the maintainer, my primary focus is to ensure the project stays true to this vision. - -### What I Focus On -- **Performance** → keeping the renderer and systems lean, efficient, and optimized for Apple hardware. -- **Testing & Stability** → maintaining a reliable codebase with proper testing practices. -- **Quality Control** → reviewing PRs for clarity, readability, and adherence to coding standards. -- **API Design** → ensuring that the engine’s API remains logical, intuitive, and consistent. - -### What Contributors Are Encouraged to Focus On -- **Features** → adding or improving systems, tools, and workflows. -- **Bug Fixes** → addressing open issues and fixing edge cases. -- **Documentation** → clarifying how things work and providing examples. -- **Editor & Usability** → enhancing the UI, workflows, and overall developer experience. - -### Decision Making -All contributions are welcome, but acceptance will be guided by the project’s vision and the priorities above. -PRs that align with clarity, performance, or creativity — while keeping the engine stable and simple — are more likely to be accepted. - -These guidelines aren’t here to block you, but to make sure every contribution keeps the engine stable, clear, and useful for everyone. - -## Pull Request Guidelines - -- **One feature or bug fix per PR** - Each Pull Request should focus on a single feature, bug fix, or documentation improvement. - This keeps the history clean and makes it easier to track down issues later. - -- **Commit hygiene** - - Keep commits meaningful (avoid "misc changes" or "fix stuff"). - - Squash commits if needed, but do not mix unrelated features in the same commit. - - If your PR touches multiple files, make sure they all relate to the same feature or fix. - -✅ Example: -- Good: *“Add PhysicsSystem with gravity integration”* -- Bad: *“Added PhysicsSystem + fixed rendering bug + updated docs”* - ---- - -## Required Contributions for New System Support - -For **new systems or major features**, your PR must include: - -- **Unit Tests** → Validate functionality and cover edge cases. -- **How-To Guide** → A step-by-step markdown guide explaining how to use the system. - -This ensures new features are stable, documented, and accessible to all users. - -👉 Note: For small fixes or incremental features, a How-To is not required. - ---- - -### How-To Guide Format - -Your guide must follow this structure: - -1. Introduction - -- Briefly explain the feature and its purpose. -- Describe what problem it solves or what value it adds. - -2. Why Use It - -- Provide real-world examples or scenarios where the feature is useful. -- Explain the benefits of using the feature in these contexts. - -3. Step-by-Step Implementation - -- Break down the setup process into clear, actionable steps. -- Include well-commented code snippets for each step. - -4. What Happens Behind the Scenes - -- Provide technical insights into how the system works internally (if relevant). -- Explain any significant impacts on performance or functionality. - -5. Tips and Best Practices - -- Share advice for effective usage. -- Highlight common pitfalls and how to avoid them. - -6. Running the Feature - -- Explain how to test or interact with the feature after setup. - ---- - -### Additional Notes - ---- - -## Questions & Discussions - -To keep communication clear and accessible for everyone: - -- 💡 Use **[GitHub Discussions](https://github.com/untoldengine/UntoldEngine/discussions)** for feature proposals, ideas, or general questions. -- 🐞 Use **[GitHub Issues](https://github.com/untoldengine/UntoldEngine/issues)** for bugs or concrete tasks that need tracking. - -This way, conversations stay organized, visible to the community, and future contributors can benefit from past discussions. - ---- - -Thank you for contributing to the Untold Engine! Following these guidelines will ensure that your work aligns with the project's goals and provides value to users. diff --git a/website/versioned_docs/version-0.10.10/images/Editor/EditorEffects.png b/website/versioned_docs/version-0.10.10/images/Editor/EditorEffects.png deleted file mode 100644 index d69f37874..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/EditorEffects.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/EditorSideShotWide-alt.png b/website/versioned_docs/version-0.10.10/images/Editor/EditorSideShotWide-alt.png deleted file mode 100644 index 42a9c804b..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/EditorSideShotWide-alt.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/EditorSideShotWide.png b/website/versioned_docs/version-0.10.10/images/Editor/EditorSideShotWide.png deleted file mode 100644 index 14ea9d471..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/EditorSideShotWide.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_model_import.png b/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_model_import.png deleted file mode 100644 index 310df2be5..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_model_import.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_name.png b/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_name.png deleted file mode 100644 index 09e711f6b..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_name.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_xcode.png b/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_xcode.png deleted file mode 100644 index 65b5c908d..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/Editor_scene_xcode.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/ScriptEditorAdd.png b/website/versioned_docs/version-0.10.10/images/Editor/ScriptEditorAdd.png deleted file mode 100644 index f425394bd..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/ScriptEditorAdd.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/Editor/ScriptReload.png b/website/versioned_docs/version-0.10.10/images/Editor/ScriptReload.png deleted file mode 100644 index 79f698548..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/Editor/ScriptReload.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/add-animation-component.png b/website/versioned_docs/version-0.10.10/images/add-animation-component.png deleted file mode 100644 index 4fb63092e..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/add-animation-component.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/addcomponent.png b/website/versioned_docs/version-0.10.10/images/addcomponent.png deleted file mode 100644 index cc2995fe7..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/addcomponent.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/animation-assign.png b/website/versioned_docs/version-0.10.10/images/animation-assign.png deleted file mode 100644 index 9a36e09ba..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/animation-assign.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/asset-browser-model.png b/website/versioned_docs/version-0.10.10/images/asset-browser-model.png deleted file mode 100644 index 7b293ec8c..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/asset-browser-model.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/camera.png b/website/versioned_docs/version-0.10.10/images/camera.png deleted file mode 100644 index 4fdc106c2..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/camera.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/engine-consolelog.png b/website/versioned_docs/version-0.10.10/images/engine-consolelog.png deleted file mode 100644 index 767e792c4..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/engine-consolelog.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/engine-editor-startup.png b/website/versioned_docs/version-0.10.10/images/engine-editor-startup.png deleted file mode 100644 index 52519a1ed..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/engine-editor-startup.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/engine-hdr.png b/website/versioned_docs/version-0.10.10/images/engine-hdr.png deleted file mode 100644 index 273b38545..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/engine-hdr.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/engine-materials.png b/website/versioned_docs/version-0.10.10/images/engine-materials.png deleted file mode 100644 index 1f813dfc8..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/engine-materials.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/gamescene1.png b/website/versioned_docs/version-0.10.10/images/gamescene1.png deleted file mode 100644 index 512513157..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/gamescene1.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/script_component_selection.png b/website/versioned_docs/version-0.10.10/images/script_component_selection.png deleted file mode 100644 index a3a65bcb4..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/script_component_selection.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/script_open_in_xcode.png b/website/versioned_docs/version-0.10.10/images/script_open_in_xcode.png deleted file mode 100644 index 529766292..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/script_open_in_xcode.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.10/images/untoldenginewhite.png b/website/versioned_docs/version-0.10.10/images/untoldenginewhite.png deleted file mode 100644 index b7440b8c1..000000000 Binary files a/website/versioned_docs/version-0.10.10/images/untoldenginewhite.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/03-Game Development/02-Tutorials/02_Input/01_KeyboardMovement.md b/website/versioned_docs/version-0.10.6/03-Game Development/02-Tutorials/02_Input/01_KeyboardMovement.md deleted file mode 100644 index 988a49707..000000000 --- a/website/versioned_docs/version-0.10.6/03-Game Development/02-Tutorials/02_Input/01_KeyboardMovement.md +++ /dev/null @@ -1,261 +0,0 @@ -# Keyboard Movement - -Learn how to move entities using keyboard input. - ---- - -## Overview - -This tutorial shows you how to: -- Detect keyboard input using the Input System -- Move an entity based on WASD keys -- Combine input with Transform System - ---- - -## Prerequisites - -This tutorial assumes you have: -- A project with `GameScene.swift` open -- **A scene loaded** with at least one entity -- The entity has a name set in the editor (e.g., "Player") - -For complete API documentation: - -➡️ **[Input System API](../../../04-Engine%20Development/03-Engine%20Systems/UsingInputSystem.md)** - ---- - -## Step 1: Enable Input System - -Before detecting input, you need to register keyboard events. Add this to your `startGameSystems()` helper or in `init()`: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - startGameSystems() - - // Register input keyboard events - InputSystem.shared.registerKeyboardEvents() - - // Find the entity by name (set in the editor) - player = findEntity(name: "Player") - } -} -``` - -**Important**: Call `InputSystem.shared.registerKeyboardEvents()` once during initialization to enable keyboard input detection. - ---- - -## Step 2: Detect Keyboard Input - -The Input System provides a `keyState` object to check if keys are pressed: - -```swift path=null start=null -func update(deltaTime: Float) { - if gameMode == false { return } - - // Check if W key is pressed - if inputSystem.keyState.wPressed == true { - Logger.log(message: "W key pressed!") - } -} -``` - -**Available Keys**: -- `inputSystem.keyState.wPressed` - W key -- `inputSystem.keyState.aPressed` - A key -- `inputSystem.keyState.sPressed` - S key -- `inputSystem.keyState.dPressed` - D key - ---- - -## Step 3: Move Entity with WASD - -Combine input detection with movement: - -```swift path=null start=null -class GameScene { - var player: EntityID! - let moveSpeed: Float = 5.0 - - init() { - // ... setup code ... - player = findEntity(name: "Player") - } - - func update(deltaTime: Float) { - if gameMode == false { return } - - var movement = SIMD3(0, 0, 0) - - // Forward (W) - if inputSystem.keyState.wPressed == true { - movement.z += moveSpeed * deltaTime - } - - // Backward (S) - if inputSystem.keyState.sPressed == true { - movement.z -= moveSpeed * deltaTime - } - - // Left (A) - if inputSystem.keyState.aPressed == true { - movement.x -= moveSpeed * deltaTime - } - - // Right (D) - if inputSystem.keyState.dPressed == true { - movement.x += moveSpeed * deltaTime - } - - // Apply movement - if movement != SIMD3(0, 0, 0) { - translateBy(entityId: player, delta: movement) - } - } -} -``` - -**Result**: The player moves based on WASD input at 5 units per second. - ---- - -## Understanding the Code - -### Input Detection - -```swift path=null start=null -if inputSystem.keyState.wPressed == true { - // Key is currently pressed -} -``` - -The Input System automatically tracks key states. You don't need to register listeners or handle events. - -### Accumulating Movement - -```swift path=null start=null -var movement = SIMD3(0, 0, 0) - -if inputSystem.keyState.wPressed == true { - movement.z += moveSpeed * deltaTime -} - -if inputSystem.keyState.dPressed == true { - movement.x += moveSpeed * deltaTime -} -``` - -By accumulating input into a `movement` vector, diagonal movement (W+D) works correctly. - -### Delta Time - -Multiplying by `deltaTime` ensures consistent speed regardless of frame rate: - -```swift path=null start=null -movement.z += moveSpeed * deltaTime // ✅ Frame-rate independent -movement.z += 0.1 // ❌ Varies with frame rate -``` - ---- - -## Advanced: Normalized Diagonal Movement - -Diagonal movement (W+D) is currently faster than single-direction movement. To fix this, normalize the movement vector: - -```swift path=null start=null -func update(deltaTime: Float) { - if gameMode == false { return } - - var movement = SIMD3(0, 0, 0) - - // Accumulate input - if inputSystem.keyState.wPressed == true { - movement.z += 1.0 - } - if inputSystem.keyState.sPressed == true { - movement.z -= 1.0 - } - if inputSystem.keyState.aPressed == true { - movement.x -= 1.0 - } - if inputSystem.keyState.dPressed == true { - movement.x += 1.0 - } - - // Normalize and apply speed - if movement != SIMD3(0, 0, 0) { - movement = normalize(movement) * moveSpeed * deltaTime - translateBy(entityId: player, delta: movement) - } -} -``` - -**Result**: Diagonal movement is now the same speed as cardinal movement. - ---- - -## Example: Tank Controls (Forward + Rotation) - -For tank-style controls where forward moves along the entity's facing direction: - -```swift path=null start=null -class GameScene { - var player: EntityID! - let moveSpeed: Float = 5.0 - let turnSpeed: Float = 90.0 // Degrees per second - - func update(deltaTime: Float) { - if gameMode == false { return } - - // Forward/backward movement - var forwardMovement: Float = 0.0 - if inputSystem.keyState.wPressed == true { - forwardMovement = moveSpeed * deltaTime - } - if inputSystem.keyState.sPressed == true { - forwardMovement = -moveSpeed * deltaTime - } - - // Apply forward movement along entity's forward direction - if forwardMovement != 0.0 { - let forward = getForwardAxisVector(entityId: player) - translateBy(entityId: player, delta: forward * forwardMovement) - } - - // Left/right rotation - var turnAngle: Float = 0.0 - if inputSystem.keyState.aPressed == true { - turnAngle = -turnSpeed * deltaTime - } - if inputSystem.keyState.dPressed == true { - turnAngle = turnSpeed * deltaTime - } - - // Apply rotation - if turnAngle != 0.0 { - rotateBy(entityId: player, angle: turnAngle, axis: SIMD3(0, 1, 0)) - } - } -} -``` - ---- - -## Summary - -You've learned: - -✅ `inputSystem.keyState` - Check keyboard input -✅ Detect W, A, S, D keys -✅ Combine input with `translateBy()` -✅ Normalize diagonal movement -✅ Tank-style controls with rotation - ---- - diff --git a/website/versioned_docs/version-0.10.6/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md b/website/versioned_docs/version-0.10.6/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md deleted file mode 100644 index 8c903a355..000000000 --- a/website/versioned_docs/version-0.10.6/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md +++ /dev/null @@ -1,252 +0,0 @@ -# Play Animation - -Learn how to load and play animations on entities. - ---- - -## Overview - -This tutorial shows you how to: -- Load animation data from a `.usdc` file -- Play an animation on an entity -- Pause animations - ---- - -## Prerequisites - -This tutorial assumes you have: -- A project with `GameScene.swift` open -- **A scene loaded** with at least one entity that has an animation skeleton -- An animation file (e.g., `running.usdc`) in `GameData/Models/` or `GameData/Animations/` -- The entity has a name set in the editor (e.g., "Player") -- The entity has animations linked in the editor - -For complete API documentation: - -➡️ **[Animation System API](../../../04-Engine%20Development/03-Engine%20Systems/UsingAnimationSystem.md)** - ---- - -## Step 1: Find the Entity - -In `GameScene.swift`, add a property to store the entity reference: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - startGameSystems() - - // Find the entity by name (set in the editor) - player = findEntity(name: "Player") - } -} -``` - ---- - -## Step 2: Load the Animation - ->>> Skip this section if you linked an animation to the entity through the editor. - -Use `setEntityAnimations()` to load animation data: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load the running animation - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - } -} -``` - -**Parameters**: -- `filename`: The animation file name (without extension) -- `withExtension`: File extension (usually `"usdc"`) -- `name`: A label to reference this animation later - ---- - -## Step 3: Play the Animation - -Use `changeAnimation()` to start playing the animation: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load animation -- Ignore if you linked an animation through the editor - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - - // Play the animation - we assume that the animation linked is called running - changeAnimation(entityId: player, name: "running") - } -} -``` - -**Result**: The entity plays the "running" animation in a loop. - ---- - -## Step 4: Pause the Animation (Optional) - -To pause or resume an animation: - -```swift path=null start=null -// Pause the animation -pauseAnimationComponent(entityId: player, isPaused: true) - -// Resume the animation -pauseAnimationComponent(entityId: player, isPaused: false) -``` - ---- - -## Loading Multiple Animations - -You can load multiple animations for the same entity: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load multiple animations -- Ignore if you linked all three animations through the editor - setEntityAnimations( - entityId: player, - filename: "idle", - withExtension: "usdc", - name: "idle" - ) - - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - - setEntityAnimations( - entityId: player, - filename: "jumping", - withExtension: "usdc", - name: "jumping" - ) - - // Start with idle animation - changeAnimation(entityId: player, name: "idle") - } -} -``` - ---- - -## Switching Animations at Runtime - -Switch between animations based on game state: - -```swift path=null start=null -class GameScene { - var player: EntityID! - var isRunning: Bool = false - - init() { - // ... setup code ... - startGameSystems() - - // Register input keyboard events - InputSystem.shared.registerKeyboardEvents() - - } - - func update(deltaTime: Float) { - if gameMode == false { return } - - // Check if player is moving - let wasRunning = isRunning - isRunning = inputSystem.keyState.wPressed || - inputSystem.keyState.aPressed || - inputSystem.keyState.sPressed || - inputSystem.keyState.dPressed - - // Switch animation when state changes - if isRunning && !wasRunning { - changeAnimation(entityId: player, name: "running") - } else if !isRunning && wasRunning { - changeAnimation(entityId: player, name: "idle") - } - } -} -``` - ---- - -## File Organization - -Animation files should be placed in your project's `GameData` directory: - - -``` -Sources/YourProject/GameData/ -├── Models/ -│ └── player.usdz -├── Animations/ -│ ├── idle.usdc -│ ├── running.usdc -│ └── jumping.usdc -``` - ---- - -## Troubleshooting - -### Animation doesn't play - -1. **Check the entity has a skeleton**: Not all models support animation. The model must have a skeletal rig. -2. **Verify file path**: Make sure the animation file exists in `GameData/Models/` or `GameData/Animations/`. -3. **Check animation name**: The `name` parameter must match when loading and playing. - -### Animation plays too fast/slow - -Animation playback speed is controlled by the animation file itself. To adjust speed, you'll need to modify the animation in your 3D software (e.g., Blender, Maya) or use animation blending (advanced topic). - ---- - -## Summary - -You've learned: - -✅ `setEntityAnimations()` - Load animation data -✅ `changeAnimation()` - Play a specific animation -✅ `pauseAnimationComponent()` - Pause/resume animations -✅ Load multiple animations per entity -✅ Switch animations based on game state - ---- - - diff --git a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/01-Overview.md b/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/01-Overview.md deleted file mode 100644 index e2ab16284..000000000 --- a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/01-Overview.md +++ /dev/null @@ -1,100 +0,0 @@ -# Overview - -⚠️ **EXPERIMENTAL FEATURE** - -This section documents the **UntoldEngine Scripting (USC)** language - an experimental scripting system that is **currently in development**. - ---- - -## Current Status - -USC scripting is in **early experimental phases** and is **not recommended for production use**. - -For game development, we strongly recommend using **Swift** and **Xcode** (see parent section: [Game Development Overview](../01-Overview.md)). - ---- - -## What is USC? - -USC (Untold Scripting Language) is a custom scripting language designed for the UntoldEngine. It aims to provide: - -- Simple C-like syntax -- Entity-component scripting -- Scene-based game logic - -However, the language is still evolving, and **its future direction is under evaluation**. - ---- - -## Why USC? - -Originally, USC was designed to provide: - -1. **Lower barrier to entry** - Simpler syntax than Swift -2. **Scene-embedded scripts** - Scripts attached to entities in the editor -3. **Hot-reload** - Faster iteration for non-compiled changes - -However, **Swift + Xcode** has proven to be more practical for most use cases: - -- Mature tooling (debugging, profiling, autocomplete) -- Type safety and compile-time errors -- Familiar to Apple platform developers -- Better performance - ---- - -## Should You Use USC? - -**No, not yet.** Here's why: - -❌ **Incomplete** - Many features are missing or unstable -❌ **Underdocumented** - Documentation is sparse and may be outdated -❌ **Limited tooling** - No debugger, no IDE support -❌ **Breaking changes** - Syntax and behavior may change without notice -❌ **Unoptimized** - Performance is not production-ready - -Use **Swift** for all game development until USC reaches a stable release. - ---- - -## When Will USC Be Ready? - -USC is in **exploratory development**. We are evaluating: - -- Whether USC provides enough value over Swift -- What the ideal syntax and feature set should be -- How to integrate it seamlessly with Xcode workflows - -There is **no timeline** for USC becoming production-ready. - ---- - -## Exploring USC Anyway? - -If you want to experiment with USC despite the warnings: - -1. Read the **[USC Language Reference](./02-USC/01_Introduction.md)** -2. Try the **[USC Tutorials](./03-Tutorials/00_HelloWorld.md)** -3. Understand that **nothing is guaranteed to work** - -Do NOT use USC for: -- Production games -- Serious projects -- Teaching materials -- Anything you want to maintain long-term - ---- - -## Feedback - -If you experiment with USC, we welcome feedback: - -- What do you like about it? -- What frustrates you? -- Does it solve problems Swift doesn't? -- Would you use it if it were stable? - -Your feedback helps us decide USC's future direction. - ---- - diff --git a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/02-USC/05_APIReference.md b/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/02-USC/05_APIReference.md deleted file mode 100644 index ac00431a7..000000000 --- a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/02-USC/05_APIReference.md +++ /dev/null @@ -1,624 +0,0 @@ ---- -id: usc-scripting-api -title: USC Scripting API Reference -sidebar_label: API Reference -sidebar_position: 10 ---- - -# Untold Engine – USC Scripting API Reference - -USC (Untold Script Core) is the scripting system inside the Untold Engine. -You write scripts in Swift using a fluent DSL, and the engine executes them at runtime. - -This reference provides the complete API surface for building gameplay scripts. - ---- - -## 1. Script Lifecycle - -### Building and Exporting Scripts - -USC provides two ways to create scripts: - -**buildScript()** - Creates a script in memory: -```swift -let script = buildScript(name: "MyScript") { s in - s.onUpdate() - .log("Running every frame") -} -``` - -**saveUSCScript()** - Saves a script to a `.uscript` file: -```swift -let outputPath = dir.appendingPathComponent("MyScript.uscript") -try? saveUSCScript(script, to: outputPath) -``` - -**Typical Pattern** - Build then save: -```swift -extension GenerateScripts { - static func generateMyScript(to dir: URL) { - let script = buildScript(name: "MyScript") { s in - s.onUpdate() - .log("Running every frame") - } - - let outputPath = dir.appendingPathComponent("MyScript.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ MyScript.uscript") - } -} -``` - -### TriggerType (Optional) - -**Default:** `.perFrame` (runs every frame) - -You only need to specify `triggerType` if you want something other than the default: - -**When to override:** - -- **`.event`** - For event-driven scripts (collision handlers, triggers) - ```swift - let script = buildScript(name: "DoorTrigger", triggerType: .event) { s in - s.onCollision(tag: "Player") // Coming soon - collision system not yet implemented - .log("Door opened!") - } - ``` - -- **`.manual`** - For manually controlled scripts (cutscenes, special sequences) - ```swift - let script = buildScript(name: "Cutscene", triggerType: .manual) { s in - s.onEvent("StartCutscene") - .log("Cutscene playing...") - } - ``` - -**Most scripts don't need to specify this** - the default `.perFrame` works for continuous behaviors like movement and AI. - -### ExecutionMode (Optional) - -**Default:** `.auto` (engine manages execution) - -You rarely need to override this. Only specify `executionMode` for advanced scenarios: - -- **`.interpreted`** - Force interpreter-based execution (debugging, special cases) - ```swift - let script = buildScript(name: "DebugScript", executionMode: .interpreted) { s in - s.onUpdate() - .log("Debug mode") - } - ``` - -**Most scripts should use the default** `.auto` mode. - ---- - -## 2. Events (Entry Points) - -Events define when code blocks execute. Chain commands after each event: - -**onStart()** - Runs once when the entity starts (like Awake/Start in Unity): -```swift -s.onStart() - .setVariable("health", to: 100.0) - .setVariable("speed", to: 5.0) - .log("Entity initialized") -``` - -**onUpdate()** - Runs every frame (like Update in Unity): -```swift -s.onUpdate() - .getProperty(.position, as: "pos") - .log("Current position") -``` - -**onCollision(tag:)** - Runs when colliding with tagged entities: -> ⚠️ **Coming Soon** - The collision system is not yet implemented. This API is planned for a future release. - -```swift -s.onCollision(tag: "Enemy") - .log("Hit an enemy!") - .setVariable("health", to: 0.0) -``` - -**onEvent(_:)** - Runs when a custom event is fired: -```swift -s.onEvent("PowerUpCollected") - .setVariable("speed", to: 10.0) - .log("Speed boost activated") -``` - -### Multiple Event Handlers - -You can define multiple event handlers in one script: -```swift -let script = buildScript(name: "Player") { s in - s.onStart() - .setVariable("score", to: 0.0) - - s.onUpdate() - .getProperty(.position, as: "pos") - - // Coming soon - collision system not yet implemented - s.onCollision(tag: "Coin") - .addFloat("score", 1.0, as: "score") -} - -let outputPath = dir.appendingPathComponent("Player.uscript") -try? saveUSCScript(script, to: outputPath) -``` - -### Interpreter Execution (Advanced) - -For `.interpreted` execution mode: -```swift -interpreter.execute(script: script, context: context, forEvent: "OnStart") -interpreter.execute(script: script, context: context, forEvent: nil) // onUpdate -``` - ---- - -## 3. Script Context - -Every script runs with a **context** that provides access to: -- **Entity properties** (position, scale, velocity, acceleration, lights) -- **Script variables** (custom data you store) -- **Engine state** (delta time, input, etc.) - -You access the entity's properties using `.getProperty()` and `.setProperty()`. - -**Available at Runtime:** -- Current entity's transform (position, scale) -- Physics properties (velocity, acceleration, mass) -- Rendering properties (color, intensity for lights) -- All script variables you've defined - -**Example:** -```swift -s.onUpdate() - .getProperty(.position, as: "currentPos") // Read from entity - .getProperty(.velocity, as: "currentVel") // Read physics - .setVariable("myCustomData", to: 42.0) // Store in script - .setProperty(.position, toVariable: "newPos") // Write to entity -``` - ---- - -## 4. Flow Control - -**Conditionals** - Execute code based on comparisons: -```swift -s.ifCondition( - lhs: .variableRef("speed"), - .greater, - rhs: .float(10.0) -) { nested in - nested.log("Too fast!") - nested.setVariable("speed", to: 10.0) -} -``` - -**Available operators:** -- `.greater`, `.less` -- `.equal`, `.notEqual` -- `.lessOrEqual`, `.greaterOrEqual` - -**Convenience conditionals:** -```swift -s.ifGreater("speed", than: 10.0) { nested in - nested.log("Too fast!") -} - -s.ifLess("health", than: 20.0) { nested in - nested.log("Low health!") -} - -s.ifEqual("state", to: 1.0) { nested in - nested.log("State is 1") -} -``` - -**Organizing math-heavy code with `.math { ... }`:** -```swift -s.onUpdate() - .math { m in - m.getProperty(.velocity, as: "vel") - m.lengthVec3("vel", as: "speed") - m.ifGreater("speed", than: 10) { n in - n.normalizeVec3("vel", as: "dir") - n.scaleVec3("dir", literal: 10, as: "clampedVel") - n.setProperty(.velocity, toVariable: "clampedVel") - } - } - .log("Velocity clamped if above 10") -``` - ---- - -## 5. Values & Variables - -**Value Types** - USC supports these data types: -```swift -enum Value { - case float(Float) // Single number - case vec3(x: Float, y: Float, z: Float) // 3D vector - case string(String) // Text - case bool(Bool) // True/false - case variableRef(String) // Reference to a variable -} -``` - -**Setting Variables:** -```swift -s.setVariable("speed", to: 5.0) -s.setVariable("direction", to: simd_float3(x: 1, y: 0, z: 0)) -s.setVariable("isActive", to: true) -s.setVariable("playerName", to: "Hero") -``` - -**Using Variable References:** -```swift -s.setVariable("maxSpeed", to: 10.0) -s.setVariable("currentSpeed", to: .variableRef("maxSpeed")) // Copy value -``` - ---- - -## 6. Engine Properties - -**Available Properties** - Read/write entity properties: -```swift -enum ScriptProperty: String { - // Transform - case position, scale - - // Physics - case velocity, acceleration, mass, angularVelocity - - // Rendering (lights) - case intensity, color - - // Engine time - case deltaTime -} -``` - -**Reading Properties:** -```swift -s.getProperty(.position, as: "pos") // Store position in "pos" variable -s.getProperty(.velocity, as: "vel") // Store velocity in "vel" variable -s.getProperty(.deltaTime, as: "dt") // Store frame delta time -``` - -**Writing Properties:** -```swift -s.setProperty(.position, toVariable: "newPos") // Set from variable -s.setProperty(.velocity, to: simd_float3(x: 0, y: 5, z: 0)) // Set from literal -s.setProperty(.angularVelocity, to: simd_float3(x: 0, y: 1, z: 0)) // Set spin (write-only today) -``` - -> Note: Rotation is controlled through `rotateTo` / `rotateBy` instructions. Reading rotation via `getProperty(.rotation, ...)` is not yet supported. - -**Complete Example:** -```swift -s.onUpdate() - .getProperty(.position, as: "currentPos") - .setVariable("offset", to: simd_float3(x: 0, y: 0.1, z: 0)) - .addVec3("currentPos", "offset", as: "newPos") - .setProperty(.position, toVariable: "newPos") // Move entity up -``` - ---- - -## 7. Math Operations - -**Float Math:** -```swift -s.addFloat("a", "b", as: "sum") // sum = a + b (two variables) -s.addFloat("a", literal: 5.0, as: "sum") // sum = a + 5 (variable + literal) -s.mulFloat("a", "b", as: "product") // product = a * b (two variables) -s.mulFloat("a", literal: 2.0, as: "product") // product = a * 2 (variable * literal) -``` - -**Vector Math:** -```swift -s.addVec3("v1", "v2", as: "sum") // sum = v1 + v2 -s.scaleVec3("dir", literal: 2.0, as: "scaled") // scaled = dir * 2.0 -s.scaleVec3("dir", by: "scale", as: "scaled") // scaled = dir * scale -s.lengthVec3("vec", as: "length") // length = magnitude of vec -s.normalizeVec3("vec", as: "unitVec") // normalized vec (zero-safe) -s.dotVec3("a", "b", as: "dot") // dot product -> float -s.crossVec3("a", "b", as: "cross") // cross product -> vec3 -s.lerpVec3(from: "a", to: "b", t: "t", as: "lerped") // linear interpolation -s.lerpFloat(from: "a", to: "b", t: "t", as: "out") // scalar lerp -s.reflectVec3("v", normal: "n", as: "reflected") // reflect v about normal -s.projectVec3("v", onto: "axis", as: "proj") // project v onto axis -s.angleBetweenVec3("a", "b", as: "angleDeg") // angle in degrees -s.clampFloat("speed", min: "minSpeed", max: "maxSpeed", as: "clampedSpeed") // bounds via vars -s.clampVec3("velocity", min: "minVel", max: "maxVel", as: "clampedVel") // component-wise -``` - -**Example - Calculate velocity:** -```swift -s.onUpdate() - .setVariable("direction", to: simd_float3(x: 1, y: 0, z: 0)) - .setVariable("speed", to: 5.0) - .scaleVec3("direction", by: "speed", as: "velocity") - .setProperty(.velocity, toVariable: "velocity") -``` - ---- - -## 8. Built-in Behaviors (Steering, Camera, Physics) - -All behaviors are instruction helpers—no `callAction` or `ScriptArgKey`. - -**Steering** -```swift -// Seek toward a target and store the steering force -s.seek(targetPosition: .vec3(x: 10, y: 0, z: 0), - maxSpeed: .float(5.0), - result: "seekForce") - -s.steerSeek(targetPosition: .variableRef("targetPos"), - maxSpeed: .variableRef("maxSpeed"), - deltaTime: .variableRef("dt"), - turnSpeed: .variableRef("turnSpeed")) - -// Arrive with slowing radius -s.steerArrive(targetPosition: .variableRef("targetPos"), - maxSpeed: .variableRef("maxSpeed"), - slowingRadius: .variableRef("slowingRadius"), - deltaTime: .variableRef("dt"), - turnSpeed: .variableRef("turnSpeed")) - -// Evade a threat: compute force into result or apply directly -s.steerEvade(threatEntity: .string("Enemy"), - maxSpeed: .float(6.0), - result: "evadeForce") // omit result to apply immediately - -// Pursuit -s.steerPursuit(targetEntity: .variableRef("targetName"), - maxSpeed: .variableRef("maxSpeed"), - deltaTime: .variableRef("dt"), - turnSpeed: .variableRef("turnSpeed")) - -// Align orientation to current velocity (smooth) -s.alignOrientation(deltaTime: .float(0.016), - turnSpeed: .float(1.0)) -``` - -**Camera** -```swift -// Snap camera to a position -s.cameraMoveTo(.vec3(x: 0, y: 3, z: -10)) - -// Look at a target -s.cameraLookAt(eye: .vec3(x: 0, y: 3, z: -8), - target: .variableRef("lookTarget"), - up: .vec3(x: 0, y: 1, z: 0)) - -// Follow a target with smoothing -s.cameraFollow(target: .string("Player"), - offset: .vec3(x: 0, y: 3, z: -6), - smoothFactor: .float(5.0), - deltaTime: .float(0.016)) - -// WASDQE fly camera -s.cameraMoveWithInput(speedVar: "moveSpeed", - deltaTimeVar: "dt", - wVar: "wPressed", - aVar: "aPressed", - sVar: "sPressed", - dVar: "dPressed", - qVar: "qPressed", - eVar: "ePressed") - -// Orbit a target entity (auto look-at) -s.cameraOrbitTarget(target: .string("Boss"), - radius: .float(12.0), - speed: .float(1.5), - deltaTime: .float(0.016), - offsetY: .float(1.5)) -``` - -**Physics** -```swift -// Impulse -s.applyLinearImpulse(direction: .vec3(x: 1, y: 0, z: 0), - magnitude: .float(5.0)) - -// Continuous world force -s.applyWorldForce(direction: .vec3(x: 0, y: 1, z: 0), - magnitude: .float(3.0)) - -// Velocity control -s.setLinearVelocity(.vec3(x: 0, y: 0, z: 5)) -s.addLinearVelocity(.variableRef("deltaVel")) -s.clampLinearSpeed(min: .float(2.0), max: .float(8.0)) - -// Angular control -s.applyAngularImpulse(axis: .vec3(x: 0, y: 1, z: 0), magnitude: .float(2.0)) -s.clampAngularSpeed(max: .float(5.0)) -s.applyAngularDamping(damping: .float(0.6), deltaTime: .float(0.016)) -``` - ---- - -## 9. Transform & Physics Helpers - -**Transform:** -```swift -s.translateTo(x: 1, y: 2, z: 3) // Set absolute position -s.translateTo(simd_float3(x: 1, y: 2, z: 3)) // Alternative syntax -s.translateBy(x: 0.1, y: 0, z: 0) // Move relative -s.translateBy(simd_float3(x: 0.1, y: 0, z: 0)) // Alternative syntax -s.rotateTo(degrees: 45, axis: simd_float3(x: 0, y: 1, z: 0)) // Set absolute rotation -s.rotateBy(degrees: 45, axis: simd_float3(x: 0, y: 1, z: 0)) // Rotate relative -s.lookAt("targetEntityName") // Face another entity -``` - -**Physics - Force & Torque:** -```swift -s.applyForce(force: simd_float3(x: 0, y: 10, z: 0)) // Apply linear force -s.applyMoment(force: simd_float3(x: 5, y: 0, z: 0), at: simd_float3(x: 1, y: 0, z: 0)) // Apply torque at point -``` - -**Physics - Velocity Control:** -```swift -s.clearVelocity() // Stop linear movement instantly -s.clearAngularVelocity() // Stop rotation instantly -s.clearForces() // Clear accumulated forces -``` - -**Physics - Gravity & Pause:** -```swift -s.setGravityScale(0.5) // Half gravity (0 = no gravity, 1 = normal, 2 = double) -s.pausePhysicsComponent(isPaused: true) // Pause/unpause physics simulation -``` - -**Example - Jump mechanic:** -```swift -s.onEvent("Jump") - .getProperty(.velocity, as: "currentVel") - .setVariable("jumpForce", to: simd_float3(x: 0, y: 15, z: 0)) - .addVec3("currentVel", "jumpForce", as: "newVel") - .setProperty(.velocity, toVariable: "newVel") -``` - -**Example - Reset physics:** -```swift -s.onEvent("Respawn") - .clearVelocity() // Stop all movement - .clearAngularVelocity() // Stop all rotation - .clearForces() // Clear force accumulation - .translateTo(simd_float3(x: 0, y: 5, z: 0)) // Move to spawn point -``` - -**Example - Apply torque to spin:** -```swift -s.onUpdate() - .ifKeyPressed("R") { n in - // Apply torque at the right edge to spin left - n.applyMoment(force: simd_float3(x: 0, y: 10, z: 0), at: simd_float3(x: 1, y: 0, z: 0)) - } -``` - -**Animation:** -```swift -s.playAnimation("Walk", loop: true) // Play looping animation -s.playAnimation("Jump", loop: false) // Play once -s.stopAnimation() // Stop current animation -``` - ---- - -## 10. Input Conditions - -**Keyboard Input:** -```swift -s.ifKeyPressed("W") { nested in - nested.log("Forward") - nested.applyForce(force: simd_float3(x: 0, y: 0, z: -1)) -} - -s.ifKeyPressed("Space") { nested in - nested.log("Jump!") - nested.applyForce(force: simd_float3(x: 0, y: 10, z: 0)) -} -``` - -**Example - WASD movement:** -```swift -s.onUpdate() - .setVariable("moveSpeed", to: 5.0) - .ifKeyPressed("W") { n in - n.applyForce(force: simd_float3(x: 0, y: 0, z: -5)) - } - .ifKeyPressed("S") { n in - n.applyForce(force: simd_float3(x: 0, y: 0, z: 5)) - } - .ifKeyPressed("A") { n in - n.applyForce(force: simd_float3(x: -5, y: 0, z: 0)) - } - .ifKeyPressed("D") { n in - n.applyForce(force: simd_float3(x: 5, y: 0, z: 0)) - } -``` - ---- - -## 11. Logging & Debugging - -**Log Messages:** -```swift -s.log("Debug message") // Simple message -s.log("Player health: 100") // Can include values -s.logValue("velocity", value: .variableRef("vel")) // Log a labeled variable -s.logValue("spawnPoint", value: .vec3(x: 0, y: 1, z: 2)) // Log a literal with a label -``` - -**Debug Variables:** -```swift -s.onUpdate() - .getProperty(.position, as: "pos") - .log("Position updated") // Track when events occur -``` - ---- - -## 12. Best Practices - -### Use enums instead of raw strings -✅ **Good:** -```swift -s.getProperty(.position, as: "pos") -s.setProperty(.velocity, toVariable: "vel") -``` - -❌ **Avoid:** -```swift -s.getProperty("position", as: "pos") // String-based, no autocomplete -``` - -### Variable Naming -- Use descriptive names: `"playerHealth"` not `"h"` -- Consistent naming: `"currentPos"`, `"targetPos"`, `"newPos"` -- Avoid conflicts with property names - -### Performance -- Use `.perFrame` for continuous behaviors (movement, AI) -- Use `.event` for one-time triggers (collision, pickups) -- Minimize operations in `onUpdate()` when possible - -### Script Organization -```swift -let script = buildScript(name: "Enemy") { s in - // Initialization - s.onStart() - .setVariable("health", to: 100.0) - .setVariable("speed", to: 3.0) - - // Main loop - s.onUpdate() - .setVariable("maxSpeed", to: 5.0) - .seek(targetPosition: .string("Player"), - maxSpeed: .variableRef("maxSpeed"), - result: "steer") - .applyForce(force: .variableRef("steer")) - - // Event handlers (collision system coming soon) - s.onCollision(tag: "Bullet") - .subtractFloat("health", 10.0, as: "health") -} - -let outputPath = dir.appendingPathComponent("Enemy.uscript") -try? saveUSCScript(script, to: outputPath) -``` - -### Debugging Tips -- Add `.log()` statements to trace execution -- Use meaningful variable names for debugging -- Test scripts incrementally -- Check console output in Play mode diff --git a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/03-Tutorials/02_Input/01_KeyboardMovement.md b/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/03-Tutorials/02_Input/01_KeyboardMovement.md deleted file mode 100644 index 20a9f5800..000000000 --- a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/03-Tutorials/02_Input/01_KeyboardMovement.md +++ /dev/null @@ -1,268 +0,0 @@ -# Keyboard Movement - WASD Motion - -**What you'll learn:** -- Reading keyboard input with `getKeyState()` -- Building movement vectors from multiple keys -- Combining input with `setProperty(.position)` -- Iterating quickly with Xcode builds - -**Time:** ~10 minutes - -**Prerequisites:** -- Untold Engine Studio open with an entity you want to move -- Access to Xcode via **Scripts: Open in Xcode** (toolbar button) - ---- - -## What We're Building - -A simple controller that: -1. Moves with **WASD** -2. Supports diagonal movement by combining keys -3. Uses a configurable speed variable - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `KeyboardMovement` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateKeyboardMovement(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateKeyboardMovement(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateKeyboardMovement) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateKeyboardMovement(to dir: URL) { - let script = buildScript(name: "KeyboardMovement") { s in - // Runs once when the entity starts - s.onStart() - .setVariable("moveSpeed", to: 0.2) // units per frame - .setVariable("forward", to: simd_float3(x: 0, y: 0, z: 1)) - .setVariable("right", to: simd_float3(x: 1, y: 0, z: 0)) - .log("KeyboardMovement ready") - - // Runs every frame - s.onUpdate() - .getProperty(.position, as: "currentPos") - .setVariable("offset", to: simd_float3(x: 0, y: 0, z: 0)) - - // Read keys - .getKeyState("w", as: "wPressed") - .getKeyState("s", as: "sPressed") - .getKeyState("a", as: "aPressed") - .getKeyState("d", as: "dPressed") - - // Forward (W) - .ifEqual("wPressed", to: true) { n in - n.scaleVec3("forward", by: "moveSpeed", as: "step") - n.addVec3("offset", "step", as: "offset") - } - // Backward (S) - .ifEqual("sPressed", to: true) { n in - n.scaleVec3("forward", by: "moveSpeed", as: "stepForward") - n.scaleVec3("stepForward", literal: -1.0, as: "stepBack") - n.addVec3("offset", "stepBack", as: "offset") - } - // Left (A) - .ifEqual("aPressed", to: true) { n in - n.scaleVec3("right", by: "moveSpeed", as: "stepRight") - n.scaleVec3("stepRight", literal: -1.0, as: "stepLeft") - n.addVec3("offset", "stepLeft", as: "offset") - } - // Right (D) - .ifEqual("dPressed", to: true) { n in - n.scaleVec3("right", by: "moveSpeed", as: "stepRight") - n.addVec3("offset", "stepRight", as: "offset") - } - - // Apply movement - .addVec3("currentPos", "offset", as: "nextPos") - .setProperty(.position, toVariable: "nextPos") - } - - let outputPath = dir.appendingPathComponent("KeyboardMovement.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ KeyboardMovement.uscript") - } -} -``` - -### Understanding the Code - -**`getKeyState()`** - Reads whether a key is pressed this frame -- Lowercase strings like `"w"` match keyboard keys - -**Offset pattern** - Build movement from multiple keys -- Start with `offset = (0,0,0)` -- Add forward/back and left/right contributions -- Apply once per frame - -**`moveSpeed` variable** - Single place to tune speed -- Scale any direction by this value (or its negative) - -**Position-based control** - Directly updates `.position` -- No physics required for this example -- Works consistently every frame - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ KeyboardMovement.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Select the entity you want to move -3. In the Inspector panel, click **Add Component** → **Script Component** -4. In the Asset Browser, find `KeyboardMovement.uscript` under Scripts/Generated -5. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 5: Test It! - -1. Click **Play** in the toolbar -2. Press `W`, `A`, `S`, `D` to move the entity -3. Hold multiple keys (e.g., `W` + `D`) to move diagonally -4. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- Movement updates every frame based on current key states -- Diagonal movement combines offset vectors naturally -- Changing `moveSpeed` updates all directions at once - -⚠️ **Scene Scale Note:** If motion is too fast for your scene scale, drop `moveSpeed` to `0.05` and retest. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Adjustable Sprint -```swift -.getKeyState("lshift", as: "shiftPressed") -.setVariable("currentSpeed", to: 0.15) -.ifEqual("shiftPressed", to: true) { n in - n.setVariable("currentSpeed", to: 0.4) -} -// Use currentSpeed instead of moveSpeed when scaling direction -``` - -### Vertical Movement -```swift -.getKeyState("space", as: "upPressed") -.getKeyState("c", as: "downPressed") -.ifEqual("upPressed", to: true) { n in - n.addVec3("offset", simd_float3(x: 0, y: 0.2, z: 0), as: "offset") -} -.ifEqual("downPressed", to: true) { n in - n.addVec3("offset", simd_float3(x: 0, y: -0.2, z: 0), as: "offset") -} -``` - -### Normalize Diagonals -```swift -.normalizeVec3("offset", as: "offset") -.scaleVec3("offset", by: "moveSpeed", as: "offset") -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ Reading keyboard input with `getKeyState()` -✅ Building movement vectors from multiple keys -✅ Updating position every frame with `onUpdate()` -✅ Building and attaching scripts to entities -✅ Testing interactive input in Play mode - ---- - diff --git a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/03-Tutorials/04_Physics/01_ApplyForce.md b/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/03-Tutorials/04_Physics/01_ApplyForce.md deleted file mode 100644 index ca5ba9cc7..000000000 --- a/website/versioned_docs/version-0.10.6/03-Game Development/04-Scripting-Experimental/03-Tutorials/04_Physics/01_ApplyForce.md +++ /dev/null @@ -1,232 +0,0 @@ -# Apply Force - Physics Push - -**What you'll learn:** -- Applying world-space forces with `applyWorldForce()` -- Controlling force direction and magnitude with variables -- Testing physics-driven motion from Xcode builds -- Building scripts in Xcode before playtesting - -**Time:** ~8 minutes - -**Prerequisites:** -- Untold Engine Studio open with an entity that has a Physics/Kinetic component -- Access to Xcode via **Scripts: Open in Xcode** (toolbar button) - ---- - -## What We're Building - -A physics push that: -1. Applies a continuous upward force -2. Uses variables for direction and magnitude -3. Shows how to tweak force strength between builds - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `ApplyForce` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateApplyForce(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateApplyForce(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateApplyForce) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateApplyForce(to dir: URL) { - let script = buildScript(name: "ApplyForce") { s in - // Runs once when the entity starts - s.onStart() - .setVariable("forceDir", to: simd_float3(x: 0, y: 1, z: 0)) - .setVariable("forceMagnitude", to: 0.2) - .log("ApplyForce ready") - - // Runs every frame - s.onUpdate() - .applyWorldForce( - direction: .variableRef("forceDir"), - magnitude: .variableRef("forceMagnitude") - ) - } - - let outputPath = dir.appendingPathComponent("ApplyForce.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ ApplyForce.uscript") - } -} -``` - -### Understanding the Code - -**`forceDir` + `forceMagnitude`** - Set the push direction and strength -- `(0, 1, 0)` pushes upward -- Reduce magnitude for gentle movement; increase for stronger lifts - -**`applyWorldForce()`** - Applies a force in world space -- Requires a Physics/Kinetic component on the entity -- Accumulates over time, so the object accelerates while the force runs - -**`onUpdate()`** - Applies force every frame -- Stop the push by setting magnitude to 0 or disabling the Script Component - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ ApplyForce.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Add an entity to the scene. -3. In the Inspector panel, click **Add Component** → **Kinetic Component** -4. In the Inspector panel, click **Add Component** → **Script Component** -5. In the Asset Browser, find `ApplyForce.uscript` under Scripts/Generated -6. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 6: Test It! - -1. Click **Play** in the toolbar -2. Watch the entity accelerate upward as the force is applied each frame -3. Open the **Console** view to confirm: - ``` - ApplyForce ready - ``` -4. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- The entity accelerates while the force is active -- Direction is world-based; change `forceDir` to push sideways -- Lower gravity or heavier masses will change how fast it moves - -⚠️ **Physics Note:** Forces accumulate; if the object moves too fast, lower `forceMagnitude` or apply the force only on certain frames. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Short Burst Instead of Continuous Force -```swift -s.onStart() - .setVariable("framesLeft", to: 60) // apply for 1 second at ~60 FPS - -s.onUpdate() - .ifGreater("framesLeft", than: 0) { n in - n.applyWorldForce(direction: .variableRef("forceDir"), - magnitude: .variableRef("forceMagnitude")) - n.addFloat("framesLeft", literal: -1, as: "framesLeft") - } -``` - -### Sideways Push -```swift -.setVariable("forceDir", to: simd_float3(x: 1, y: 0, z: 0)) // push along +X -``` - -### Switch to Impulse -```swift -s.onUpdate() - .applyLinearImpulse(direction: .variableRef("forceDir"), - magnitude: .variableRef("forceMagnitude")) -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ Applying forces to physics-enabled entities -✅ Controlling direction and magnitude with variables -✅ Building and attaching physics scripts in the editor -✅ Testing physics responses in Play mode - ---- - diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingAnimationSystem.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingAnimationSystem.md deleted file mode 100644 index e0530058e..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingAnimationSystem.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -id: animationsystem -title: Animation System -sidebar_position: 4 ---- - -# Enabling Animation in Untold Engine - -The Untold Engine simplifies adding animations to your rigged models, allowing for lifelike movement and dynamic interactions. This guide will show you how to set up and play animations for a rigged model. - - -## How to Enable Animation - -### Step 1: Create an Entity - -Start by creating an entity to represent your animated model. - -```swift -let redPlayer = createEntity() -``` - ---- - -### Step 2: Link the Mesh to the Entity - -Load your rigged model’s .usdc file and link it to the entity. This step ensures the entity is visually represented in the scene. - -```swift -setEntityMesh(entityId: redPlayer, filename: "redplayer", withExtension: "usdc", flip: false) -``` ->>> Note: If your model renders with the wrong orientation, set the flip parameter to false. - ---- - -### Step 3: Load the Animation -Load the animation data for your model by providing the animation .usdc file and a name to reference the animation later. - -```swift -setEntityAnimations(entityId: redPlayer, filename: "running", withExtension: "usdc", name: "running") -``` - ---- - -### Step 4: Set the Animation to play - -Trigger the animation by referencing its name. This will set the animation to play on the entity. - -```swift -changeAnimation(entityId: redPlayer, name: "running") -``` - ---- - -### Step 5. Pause the animation (Optional) - -To pause the current animation, simply call the following function. The animation component will be paused for the current entity. - -```swift -pauseAnimationComponent(entityId: redPlayer, isPaused: true) -``` - ---- - -### Running the Animation - -Once the animation is set up: - -1. Run the project: Your model will appear in the game window. -2. Click on "Play" to enter Game Mode: -- The model will play the assigned animation in real time. - ---- - -## Tips and Best Practices - -- Name Animations Clearly: Use descriptive names like "running" or "jumping" to make it easier to manage multiple animations. -- Debug Orientation Issues: If the model’s animation appears misaligned, revisit the flip parameter or check the model’s export settings. -- Combine Animations: For complex behaviors, load multiple animations (e.g., walking, idle, jumping) and switch between them dynamically. diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingCameraSystem.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingCameraSystem.md deleted file mode 100644 index 3f4b480de..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingCameraSystem.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -id: camerasystem -title: Camera System -sidebar_position: 10 ---- - -# Using the Camera System - -This document explains how to move, rotate, and control cameras using the APIs in `CameraSystem.swift`. - -## Get the Game Camera - -For gameplay, always use the game camera (not the editor/scene camera). Call `findGameCamera()` and make it active: - -```swift -let camera = findGameCamera() -CameraSystem.shared.activeCamera = camera -``` - -If no game camera exists, `findGameCamera()` creates one and sets it up with default values. - -## Translate (Move) the Camera - -Use absolute or relative movement: - -```swift -// Absolute position -moveCameraTo(entityId: camera, 0.0, 3.0, 7.0) - -// Relative movement in camera local space -cameraMoveBy(entityId: camera, delta: simd_float3(0.0, 0.0, -1.0), space: .local) - -// Relative movement in world space -cameraMoveBy(entityId: camera, delta: simd_float3(1.0, 0.0, 0.0), space: .world) -``` - -## Rotate the Camera - -Use `rotateCamera` for pitch/yaw rotation, or `cameraLookAt` to aim at a target. - -```swift -// Rotate by pitch/yaw (radians), with optional sensitivity -rotateCamera(entityId: camera, pitch: 0.02, yaw: 0.01, sensitivity: 1.0) - -// Look-at orientation -cameraLookAt( - entityId: camera, - eye: simd_float3(0.0, 3.0, 7.0), - target: simd_float3(0.0, 0.0, 0.0), - up: simd_float3(0.0, 1.0, 0.0) -) -``` - -## Camera Follow - -Follow a target entity with a fixed offset. You can optionally smooth the motion. - -```swift -let target = findEntity(name: "player") ?? createEntity() -let offset = simd_float3(0.0, 2.0, 6.0) - -// Instant follow -cameraFollow(entityId: camera, targetEntity: target, offset: offset) - -// Smoothed follow -cameraFollow(entityId: camera, targetEntity: target, offset: offset, smoothFactor: 6.0, deltaTime: deltaTime) -``` - -### Dead-Zone Follow - -`cameraFollowDeadZone` only moves the camera when the target leaves a box around it. This is useful for platformers and shoulder cameras. - -```swift -let deadZone = simd_float3(1.0, 0.5, 1.0) -cameraFollowDeadZone( - entityId: camera, - targetEntity: target, - offset: offset, - deadZoneExtents: deadZone, - smoothFactor: 6.0, - deltaTime: deltaTime -) -``` - -## Camera Path Following - -The camera path system moves the active camera through a sequence of waypoints with smooth interpolation. - -### Start a Path - -```swift -let waypoints = [ - CameraWaypoint( - position: simd_float3(0, 5, 10), - rotation: simd_quatf(angle: 0, axis: simd_float3(0, 1, 0)), - segmentDuration: 2.0 - ), - CameraWaypoint( - position: simd_float3(10, 5, 10), - rotation: simd_quatf(angle: Float.pi / 4, axis: simd_float3(0, 1, 0)), - segmentDuration: 2.0 - ) -] - -startCameraPath(waypoints: waypoints, mode: .once) -``` - -You can also build waypoints that look at a target: - -```swift -let waypoint = CameraWaypoint( - position: simd_float3(0, 5, 10), - lookAt: simd_float3(0, 0, 0), - up: simd_float3(0, 1, 0), - segmentDuration: 2.0 -) -``` - -### Update Every Frame - -Call `updateCameraPath(deltaTime:)` from your main update loop: - -```swift -func update(deltaTime: Float) { - updateCameraPath(deltaTime: deltaTime) -} -``` - -### Looping and Completion - -```swift -startCameraPath(waypoints: waypoints, mode: .loop) - -let settings = CameraPathSettings(startImmediately: true) { - print("Camera path completed") -} -startCameraPath(waypoints: waypoints, mode: .once, settings: settings) -``` - -## Notes - -- `startCameraPath` and `updateCameraPath` operate on `CameraSystem.shared.activeCamera`. -- `segmentDuration` is the time to move from the current waypoint to the next. -- For gameplay, always acquire the camera with `findGameCamera()` and set it active before path playback or follow logic. diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingGaussianSystem.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingGaussianSystem.md deleted file mode 100644 index 63d87e5a8..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingGaussianSystem.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: gaussiansystem -title: Gaussian System -sidebar_position: 3 ---- - -# Enabling Gaussian System in Untold Engine - -The Gaussian System in the Untold Engine is responsible for rendering Gaussian Splatting models. It enables you to visualize high-quality 3D reconstructions created from photogrammetry or neural rendering techniques, providing a modern approach to displaying complex 3D scenes. - -## How to Enable the Gaussian System - -### Step 1: Create an Entity - -Start by creating an entity that represents your Gaussian Splat object. - -```swift -let myEntity = createEntity() -``` ---- - -### Step 2: Link a Gaussian Splat to the Entity - -To display a Gaussian Splat model, load its .ply file and link it to the entity using setEntityGaussian. - -```swift -setEntityGaussian(entityId: myEntity, filename: "splat", withExtension: "ply") -``` - -Parameters: - -- entityId: The ID of the entity created earlier. -- filename: The name of the .ply file (without the extension). -- withExtension: The file extension, typically "ply". - -> Note: The Gaussian System renders point cloud data stored in the .ply format. Ensure your Gaussian Splat file is properly formatted and contains the necessary attributes (position, color, opacity, scale, rotation). - ---- - -### Running the Gaussian System - -Once everything is set up: - -1. Run the project. -2. Your Gaussian Splat model will appear in the game window. -3. If the model is not visible or appears incorrect, revisit the file path and format to ensure everything is loaded correctly. - diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingPhysicsSystem.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingPhysicsSystem.md deleted file mode 100644 index fd8e53d91..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingPhysicsSystem.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -id: physicssystem -title: Physics System -sidebar_position: 5 ---- - -# Enabling Physics System in Untold Engine - -The physics system in the Untold Engine enables realistic simulations such as gravity, forces, and dynamic interactions. While collision support is still under development, this guide will walk you through adding physics to your entities. - -## How to Enable Physics - -### Step 1: Create an Entity - -Start by creating an entity that represents the object you want to add physics to. - -```swift -let redPlayer = createEntity() -``` ---- - -### Step 2: Link a Mesh to the Entity -Next, load your model’s mesh file and link it to the entity. This step visually represents your entity in the scene. - -```swift -setEntityMesh(entityId: redPlayer, filename: "redplayer", withExtension: "usdc") -``` ---- - -### Step 3: Enable Physics on the Entity -Activate the physics simulation for your entity using the setEntityKinetics function. This function prepares the entity for movement and dynamic interaction. - -```swift -setEntityKinetics(entityId: redPlayer) -``` ---- - -#### Step 4: Configure Physics Properties -You can customize the entity’s physics behavior by defining its mass and gravity scale: - -- Mass: Determines the force needed to move the object. Heavier objects require more force. -- Gravity Scale: Controls how strongly gravity affects the entity (default is 0.0). - -```swift -setMass(entityId: redPlayer, mass: 0.5) -setGravityScale(entityId: redPlayer, gravityScale: 1.0) -``` ---- - -#### Step 5: Apply Forces (Optional) -You can apply a custom force to the entity for dynamic movement. This is useful for simulating actions like jumps or pushes. - -```swift -applyForce(entityId: redPlayer, force: simd_float3(0.0, 0.0, 5.0)) -``` - -> Note: Forces are applied per frame. To avoid unintended behavior, only apply forces when necessary. - ---- - -#### Step 6: Use the Steering System -For advanced movement behaviors, leverage the Steering System to steer entities toward or away from targets. This system automatically calculates the required forces. - -Example: Steering Toward a Position - -```swift -steerTo(entityId: redPlayer, targetPosition: simd_float3(0.0, 0.0, 5.0), maxSpeed: 2.0, deltaTime: deltaTime) -``` - ---- - -#### Additional Steering Functions - -The Steering System includes other useful behaviors, such as: - -- steerAway() -- steerPursuit() -- followPath() - -These functions simplify complex movement patterns, making them easy to implement. - ---- - -### What Happens Behind the Scenes? - -1. Physics Simulation: -- Entities with physics enabled are updated each frame to account for forces, gravity, and other dynamic factors. -- Transformations are recalculated based on velocity, acceleration, and forces applied. -2. Realistic Motion: -- The system ensures consistent, physics-based movement without manual updates to the transform. - ---- - -### Running the Simulation -Once you've set up physics, run the project to see it in action: - -1. Launch the project: Your model will appear in the game window. -2. Press "P" to enter Game Mode: -- Gravity and forces will affect the entity. -- If forces are applied, you’ll see dynamic motion in real time. diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingRenderingSystem.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingRenderingSystem.md deleted file mode 100644 index c42d5ae1a..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingRenderingSystem.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: renderingsystem -title: Rendering System -sidebar_position: 2 ---- - -# Enabling Rendering System in Untold Engine - -The Rendering System in the Untold Engine is responsible for displaying your models on the screen. It supports advanced features such as Physically Based Rendering (PBR) for realistic visuals and multiple types of lights to illuminate your scenes. - -## How to Enable the Rendering System - -### Step 1: Create an Entity - -Start by creating an entity that represents your 3D object. - -```swift -let entity = createEntity() -``` ---- - -### Step 2: Link a Mesh to the Entity - -To display a model, load its .usdc file and link it to the entity using setEntityMesh. - -```swift -setEntityMesh(entityId: entity, filename: "entity", withExtension: "usdz") -``` - -Parameters: - -- entityId: The ID of the entity created earlier. -- filename: The name of the .usdc file (without the extension). -- withExtension: The file extension, typically "usdz". - -> Note: If PBR textures (e.g., albedo, normal, roughness, metallic maps) are included, the rendering system will automatically use the appropriate PBR shader to render the model with realistic lighting and material properties. - ---- - -### Running the Rendering System - -Once everything is set up: - -1. Run the project. -2. Your model will appear in the game window, illuminated by the configured lights. -3. If the model is not visible or appears flat, revisit the lighting and texture setup to ensure everything is loaded correctly. - ---- - -## Common Issues and Fixes - -#### Issue: My Model Isn’t Visible! - -- Cause: The scene lacks a light source. -- Solution: Add a directional or point light as shown above. Lighting is required to render objects visibly. - -#### Issue: Model Appears Flat or Dull - -- Cause: PBR textures are missing or not linked properly. -- Solution: Ensure the .usdc file includes the correct PBR textures, and verify their paths during the loading process. - -#### Debugging Tip: - -- Log the addition of lights and entities to verify the scene setup. -- Ensure the position of the point light is within the visible range of the camera and the objects it is meant to illuminate. - ---- - -### Tips and Best Practices - -- Combine Light Types: Use directional lights for overall scene lighting and point lights for localized effects. -- Use PBR Materials: Provide high-quality PBR textures for realistic rendering. -- Position Lights Intelligently: Place point lights strategically to highlight key areas without excessive overlap. - ---- - - diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingSpatialInput.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingSpatialInput.md deleted file mode 100644 index 5d7aaa10c..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingSpatialInput.md +++ /dev/null @@ -1,275 +0,0 @@ ---- -id: spatialinput -title: Spatial Input VisionPro -sidebar_position: 13 ---- - -# Spatial Input (vision Pro) - -Spatial input in Untold Engine follows a simple pipeline: - -1. visionOS emits raw spatial events. -2. UntoldEngineXR converts each event into an XRSpatialInputSnapshot. -3. Snapshots are queued in InputSystem. -4. XRSpatialGestureRecognizer processes snapshots each frame. -5. The engine publishes a single XRSpatialInputState your game reads in handleInput(). - -That separation keeps the system flexible: the OS-facing code stays in UntoldEngineXR, while gesture classification stays in -the recognizer. - -## What You Get in Game Code - -From XRSpatialInputState, you can read: - -- spatialTapActive -- spatialDragActive -- spatialPinchActive -- spatialPinchDragDelta -- spatialZoomActive + spatialZoomDelta -- spatialRotateActive + spatialRotateDeltaRadians -- pickedEntityId - -So your game logic can stay focused on behavior (select, move, rotate, scale), not event parsing. - -## Important Setup Step - -You must enable XR event ingestion: - -InputSystem.shared.registerXREvents() - -If you skip this, the callback still receives OS events, but the engine ignores them. - -## Typical Frame Usage - -In your handleInput(): - -- Poll InputSystem.shared.xrSpatialInputState. -- React to edge-triggered gestures like tap. -- Apply continuous updates for drag/zoom/rotate while active. - -For object manipulation, use SpatialManipulationSystem for robust pinch-driven transforms, then layer custom behavior on top -when needed. - -## Quick Example - -This example shows how to drag and rotate a mesh using the engine: - -``` swift -func handleInput() { - if gameMode == false { return } - - let state = InputSystem.shared.xrSpatialInputState - - if state.spatialTapActive, let entityId = state.pickedEntityId { - Logger.log(message: "Tapped entity: \(entityId)") - } - - // Handles drag-based translate + twist rotation on picked entity - SpatialManipulationSystem.shared.processPinchTransformLifecycle(from: state) -} -``` - -### What This Does - -- **Tap** → selects entity (via raycast picking) -- **Pinch + Drag** → translates entity in world space -- **Pinch + Twist** → rotates entity around a computed axis - -`processPinchTransformLifecycle` handles: - -- Begin -- Update -- End -- Cancel - -This lifecycle model prevents stuck manipulation sessions. - ------------------------------------------------------------------------- - -### Manipulate Parent Instead Of Picked Child - -If ray picking hits a child mesh and you want to manipulate the parent -actor: - -``` swift -var state = InputSystem.shared.xrSpatialInputState - -if let picked = state.pickedEntityId, - let parent = getEntityParent(entityId: picked) { - state.pickedEntityId = parent -} - -SpatialManipulationSystem.shared.processPinchTransformLifecycle(from: state) -``` - -This is useful when: - -- A character has multiple meshes -- A building has sub-meshes -- You want to move the root actor instead of individual geometry - pieces - ------------------------------------------------------------------------- - -### Important Note - -Do not early-return only because `pickedEntityId == nil` before calling -lifecycle processing. - -End/cancel phases must still propagate to properly close manipulation -sessions.\ -Failing to do so can leave the engine in an inconsistent transform -state. - ------------------------------------------------------------------------- - -# Raw Gesture Examples - -It is strongly recommended to use the Spatial Helper functions instead -of raw gesture access. - -Raw access is useful when: - -- You want custom manipulation behavior -- You are building a custom editor -- You want non-standard gesture responses - ------------------------------------------------------------------------- - -## Tap (Selection) - -Vision Pro air-tap gesture. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.spatialTapActive, let entityId = state.pickedEntityId { - // selectEntity(entityId) -} -``` - -Use this to: - -- Select objects -- Trigger UI -- Activate gameplay logic - ------------------------------------------------------------------------- - -## Pinch Active - -Single-hand pinch detected. - -``` swift -if InputSystem.shared.hasSpatialPinch() { - // pinch is active -} -``` - -This does **not** imply dragging yet --- only that a pinch is currently -held. - ------------------------------------------------------------------------- - -## Pinch Position - -World-space position of pinch. - -``` swift -if let pinchPosition = InputSystem.shared.getPinchPosition() { - // use pinchPosition -} -``` - -Useful for: - -- Placing objects -- Spawning actors -- Visual debugging - ------------------------------------------------------------------------- - -## Pinch Drag Delta - -Drag delta while pinch is active. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.spatialPinchActive { - let dragDelta = InputSystem.shared.getPinchDragDelta() - // app-defined translation/scaling response -} -``` - -Common use cases: - -- Translate object along plane -- Move UI panels -- Drag actors in world space - ------------------------------------------------------------------------- - -## Two-Hand Zoom Signal (Coming soon) - -Two hands pinching and moving closer/farther. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.leftHandPinching, state.rightHandPinching, state.spatialZoomActive { - let zoomDelta = InputSystem.shared.getSpatialZoomDelta() - // app-defined zoom response -} -``` - -### Typical Behavior Options - -You decide what zoom means: - -- Scale selected object -- Move object closer/farther in world space -- Adjust camera rig distance -- Modify FOV (if using custom projection control) - -Untold Engine does not automatically change camera FOV.\ -You define the semantic meaning of zoom. - ------------------------------------------------------------------------- - -## Two-Hand Rotate Signal (Coming soon) - -Two hands pinching and rotating around each other. - -``` swift -let state = InputSystem.shared.xrSpatialInputState -if state.leftHandPinching, state.rightHandPinching, state.spatialRotateActive { - let deltaRadians = InputSystem.shared.getSpatialRotateDelta() - let axisWorld = InputSystem.shared.getSpatialRotateAxisWorld() - // app-defined rotate response -} -``` - -Typical usage: - -- Rotate object in world space -- Rotate parent actor -- Rotate UI panel in 3D - -`axisWorld` allows you to apply physically intuitive rotations rather -than arbitrary axes. - ------------------------------------------------------------------------- - -# Spatial Helper Functions - -Use these helpers from `SpatialManipulationSystem.shared`: - -- `processPinchTransformLifecycle(from:)`\ - Recommended default. Handles translation + twist rotation lifecycle - safely. - -- `applyPinchDragIfNeeded(from:entityId:sensitivity:)`\ - Lower-level translation helper if you want full control. - -- `applyTwoHandZoomIfNeeded(from:sensitivity:)`\ - Provides zoom delta signal. You must define what zoom means in your - app. - diff --git a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingTransformSystem.md b/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingTransformSystem.md deleted file mode 100644 index fb91bfdd3..000000000 --- a/website/versioned_docs/version-0.10.6/04-Engine Development/03-Engine Systems/UsingTransformSystem.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -id: transformsystem -title: Transform System -sidebar_position: 3 ---- - -# Using the Transform System in Untold Engine - -The Transform System is a core part of the Untold Engine, responsible for managing the position, rotation, and scale of entities. It provides both local transformations (relative to a parent entity) and world transformations (absolute in the scene). - -## How to Use the Transform System - -### Step 1: Retrieve Transform Data -You can retrieve an entity’s position, orientation, or axis vectors using the provided functions. - -#### Get Local Position - -Retrieves the entity’s position relative to its parent. - -```swift -let localPosition = getLocalPosition(entityId: entity) -``` - -#### Get World Position - -Retrieves the entity’s absolute position in the scene. - -```swift -let worldPosition = getPosition(entityId: entity) -``` - -#### Get Local Orientation - -Retrieves the entity’s orientation matrix relative to its parent. - -```swift -let localOrientation = getLocalOrientation(entityId: entity) -``` - -#### Get World Orientation - -Retrieves the entity’s absolute orientation matrix. - -```swift -let worldOrientation = getOrientation(entityId: entity) -``` - -#### Get Axis Vectors - -Retrieve the entity’s forward, right, or up axis: - -```swift -let forward = getForwardAxisVector(entityId: entity) -let right = getRightAxisVector(entityId: entity) -let up = getUpAxisVector(entityId: entity) -``` - ---- - -### Step 2: Update Transform Data - -Modify an entity’s transform by translating or rotating it. - -#### Translate the Entity - -Move the entity to a new position: - -```swift -translateTo(entityId: entity, position: simd_float3(5.0, 0.0, 3.0)) -``` - -Move the entity by an offset relative to its current position: - -```swift -translateBy(entityId: entity, position: simd_float3(1.0, 0.0, 0.0)) -``` - -#### Rotate the Entity - -Rotate the entity to a specific angle around an axis: - -```swift -rotateTo(entityId: entity, angle: 45.0, axis: simd_float3(0.0, 1.0, 0.0)) -``` - -Apply an incremental rotation to the entity: - -```swift -rotateBy(entityId: entity, angle: 15.0, axis: simd_float3(0.0, 1.0, 0.0)) -``` - -Directly set the entity’s rotation matrix: - -```swift -rotateTo(entityId: entity, rotation: simd_float4x4( /* matrix values */ )) -``` - ---- - -## What Happens Behind the Scenes? - -1. Local and World Transform Components: -- Each entity has a LocalTransformComponent for transformations relative to its parent. -- The WorldTransformComponent calculates the absolute transform by combining the local transform with the parent’s world transform. -2. Transform Matrices: -- Transformations are stored in 4x4 matrices that include position, rotation, and scale. -- These matrices are updated whenever you translate or rotate an entity. -3. Scene Graph Integration: -- Changes to a parent entity automatically propagate to its children through the scene graph. - ---- - -## Tips and Best Practices -- Use Local Transformations for Hierarchies: - - For example, a car’s wheels (children) should use local transforms relative to the car body (parent). -- Combine Translations and Rotations: - - Use translateTo or rotateTo to set an entity’s absolute position or rotation. - - Use translateBy or rotateBy for incremental adjustments. diff --git a/website/versioned_docs/version-0.10.6/05-Editor Development/01-Overview.md b/website/versioned_docs/version-0.10.6/05-Editor Development/01-Overview.md deleted file mode 100644 index 9abb30205..000000000 --- a/website/versioned_docs/version-0.10.6/05-Editor Development/01-Overview.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -id: editoroverview -title: Overview -sidebar_position: 1 ---- - -# Editor Development Overview - -This section is for developers who want to **improve or extend the Untold Editor**. - -The editor is a separate application built on top of Untold Engine. - ---- - -## What You’ll Work On - -In this section, you’ll learn how to: -- Understand the editor architecture -- Add or modify editor views -- Improve workflows and tooling -- Integrate editor features with the engine - -Editor development focuses on **usability and tooling**, not gameplay. - ---- - -## Installing the Editor for Development - -Working on the editor requires installing Untold Editor via the command line and running it from source. - -For editor development, clone this repository: - -```bash -git clone https://github.com/untoldengine/UntoldEditor.git -cd UntoldEditor -``` -The Editor is a Swift Package with an executable target named UntoldEditor. -It declares a dependency on the Untold Engine package; Xcode/SwiftPM will resolve it automatically. - -### Open in Xcode - -1. Open Xcode → File ▸ Open → select the Package.swift in this repo -2. Xcode will create a workspace view for the package -3. Choose the UntoldEditor scheme → Run - -### Build & run via CLI -If you prefer working with the terminal, you can build and run the Editor as follows: - -```bash -swift build -swift run UntoldEditor -``` - -### Pinning the Engine Dependency - -By default, this repo pins Untold Engine to a released version. -If you want the latest engine changes: - -#### Option A — Xcode UI -- In the project navigator: Package Dependencies → UntoldEngine -- Set Dependency Rule to Branch and type develop - -#### Option B — Edit Package.swift - -```swift -.dependencies = [ - .package(url: "https://github.com/untoldengine/UntoldEngine.git", branch: "develop") -] -``` - -Then reload packages: - -```bash -xcodebuild -resolvePackageDependencies -# or in Xcode: File ▸ Packages ▸ Resolve Package Versions -``` - - -The editor can be developed independently of game projects. - ---- - -## 🕹 Using the Editor - -1. **Create / Open a Project** – Use the start screen or File menu -2. **Set Asset Folder** – Choose an **external** directory for your project’s assets -3. **Import Assets** – Drag files in or use the Asset Browser “+” -4. **Build Your Scene** – Create entities, add components, use gizmos to position -5. **Play Mode** – Toggle to simulate and validate behavior -6. **Save / Load** – Save project and scene files; reopen later to continue - -> 💡 Why an *external* asset folder? -> It enables **runtime importing** and iteration without copying everything into the app bundle. - - -## Editor Architecture - -The Untold Editor is structured around: -- Independent views -- Shared engine state -- Explicit data flow between UI and engine -- Minimal magic - -Understanding the editor architecture is key before adding features. - -You can start with: - -> **Editor Development → Architecture** - ---- - -## Editor Views and Interaction - -The editor is composed of views such as: -- Scene View -- Inspector -- Asset Browser -- Scene Hierarchy -- Code Editor (note: USC scripts are authored in Xcode; the editor does not include a built-in USC script editor) - -Each view is designed to be modular and replaceable. - ---- - -## Who This Section Is Not For - -This section is **not** intended for: -- Game developers building gameplay -- Engine developers working on core systems -- Users looking to learn the editor UI - -If you want to *use* the editor, see: - -> **Game Development → Using the Editor** - diff --git a/website/versioned_docs/version-0.10.6/05-Editor Development/03-Views/AssetBrowserView.md b/website/versioned_docs/version-0.10.6/05-Editor Development/03-Views/AssetBrowserView.md deleted file mode 100644 index 5f4a73fa3..000000000 --- a/website/versioned_docs/version-0.10.6/05-Editor Development/03-Views/AssetBrowserView.md +++ /dev/null @@ -1,128 +0,0 @@ -# Asset Browser View - -The **Asset Browser View** is the discovery and selection surface for project assets within the Untold Editor. - -It presents filtered asset metadata, supports browsing and search, and emits asset selection or assignment intents through the coordination layer. - -This document describes the **architectural role** of the Asset Browser View, not how to use it as an end user. - ---- - -## Purpose - -The Asset Browser View exists to: - -- Surface the project’s asset library for discovery -- Enable filtering, searching, and grouping of assets -- Provide a control point for selecting assets -- Initiate asset assignment intents (e.g., “assign mesh,” “assign script”) without owning import or load logic - -It is the bridge between asset metadata and editor interactions that consume assets. - ---- - -## Responsibilities - -The Asset Browser View is responsible for: - -- Displaying asset entries with relevant metadata and previews -- Providing search, filter, and organization affordances -- Reflecting shared selection state for assets -- Emitting asset selection changes -- Emitting asset assignment intents to other systems (e.g., drag-and-drop targets) via commands -- Respecting editor mode (Edit vs Play) where assignment is allowed - -The Asset Browser View does **not** own asset data or selection state. - ---- - -## What This View Does NOT Do - -The Asset Browser View intentionally does **not**: - -- Own or manage asset import, processing, or versioning -- Load GPU resources or instantiate runtime objects -- Apply asset assignments directly; it emits intents through the coordination layer -- Manage project file I/O beyond reading metadata exposed by asset services -- Execute scripts or run simulation logic -- Decide selection globally; it only requests changes through shared state - -If asset browser behavior appears to require import, loading, or state ownership, that logic belongs elsewhere. - ---- - -## Data Flow - -The Asset Browser View participates in the editor data flow as follows: - -### Reads -- Asset catalog metadata (names, types, tags, thumbnails, availability) -- Asset selection state -- Editor mode state -- Search/filter parameters provided by shared state or local UI - -### Emits -- Asset selection change requests -- Asset assignment intents (e.g., assign to component field, instantiate into scene) -- Folder or collection navigation commands (if supported by asset services) -- Mode-aware warnings when an assignment is not allowed - -All emitted actions flow through the editor coordination layer. - ---- - -## Interaction With Other Views - -The Asset Browser View interacts indirectly with other views via shared editor state: - -- **Scene View / Scene Hierarchy** - Receives asset drops or assignment intents to create or replace scene content - -- **Inspector** - Accepts asset assignments into component fields - -- **Code Editor** - May reference scripts as assets; USC authoring itself happens in Xcode, not inside the editor - -The Asset Browser View does not directly communicate with other views. - ---- - -## Edit Mode vs Play Mode Behavior - -### Edit Mode -- Full asset browsing, selection, and assignment intents are available -- Assignments update authoring data through commands - -### Play Mode -- Asset browsing remains available; assignments that mutate authoring data may be blocked or limited -- Runtime-safe assignments (if supported) are routed through the same command pathways - -Mode transitions are handled externally and reflected in the view. - ---- - -## Extension Points - -Contributors may extend the Asset Browser View by: - -- Adding advanced filters (tags, dependencies, usage frequency) -- Introducing custom previews or inspectors for specific asset types -- Extending drag-and-drop behaviors for assignment targets -- Adding collection/saved search features backed by shared state - -Extensions should integrate through existing asset metadata sources and command pathways. - ---- - -## Design Constraints - -The Asset Browser View is intentionally constrained to: - -- Visualization of asset metadata and selection -- Command emission for selection and assignment intents -- Stateless or minimal local UI state driven by shared data -- No direct import, loading, or mutation of asset storage - -Keeping these boundaries strict ensures predictable asset workflows and debuggability. - diff --git a/website/versioned_docs/version-0.10.6/05-Editor Development/03-Views/InspectorView.md b/website/versioned_docs/version-0.10.6/05-Editor Development/03-Views/InspectorView.md deleted file mode 100644 index 6745678a4..000000000 --- a/website/versioned_docs/version-0.10.6/05-Editor Development/03-Views/InspectorView.md +++ /dev/null @@ -1,135 +0,0 @@ -# Inspector View - -The **Inspector View** is the focused property panel for examining and editing the selected entities and components in the Untold Editor. - -It presents authoritative component data for the current selection and emits structured changes back through the editor coordination layer. - -This document describes the **architectural role** of the Inspector View, not how to use it as an end user. - ---- - -## Purpose - -The Inspector View exists to: - -- Surface component data for the current selection -- Provide structured editors for component properties -- Validate edits against component rules and modes -- Emit explicit change requests to the coordination layer - -It is the bridge between selection state and component-level editing. - ---- - -## Responsibilities - -The Inspector View is responsible for: - -- Displaying the current selection (single or multi-entity) -- Rendering component sections and property editors -- Showing validation and state (read-only, overridden, default) -- Emitting add/remove component requests -- Emitting property change commands with full context (target entity, component, field, value) -- Reflecting editor mode (Edit vs Play) in allowed operations - -The Inspector View does **not** own component data or selection state. - ---- - -## What This View Does NOT Do - -The Inspector View intentionally does **not**: - -- Manage or decide selection; it only observes it -- Apply engine changes directly; all edits flow through commands -- Run simulation, scripting, or component lifecycle logic -- Own component storage or serialization; it only issues edits -- Own schemas or define component types (it consumes published definitions) -- Manage undo/redo stacks (it emits commands compatible with them) -- Provide asset authoring workflows beyond property-level references - -If Inspector behavior appears to require simulation or data ownership, that logic belongs elsewhere. - ---- - -## Data Flow - -The Inspector View participates in the editor data flow as follows: - -### Reads -- Selection state -- Component definitions and metadata (including editability rules) -- Component instances for selected entities -- Editor mode state -- Validation results and command feedback - -### Emits -- Property change commands -- Component add/remove requests -- Component reorder or enable/disable requests (if supported by schema) -- Mode-aware warnings (e.g., attempted edit in Play mode) - -All emitted actions flow through the editor coordination layer. - ---- - -## Interaction With Other Views - -The Inspector View interacts indirectly with other views via shared editor state: - -- **Scene Hierarchy** - Mirrors selection changes driven by hierarchy navigation - -- **Scene View** - Reflects selection changes initiated in world space - -- **Asset Browser** - Accepts drag-and-drop asset references into component fields - -- **Code Editor** - Consumes component schemas defined in code; USC authoring occurs in Xcode, not inside the editor; does not depend on editor code directly - -The Inspector View does not directly communicate with other views. - ---- - -## Edit Mode vs Play Mode Behavior - -### Edit Mode -- Full component editing is enabled -- Changes persist to the authoring state -- Validation prevents invalid component configurations - -### Play Mode -- Inspector prioritizes observation; many edits are read-only or limited -- Runtime values may be displayed distinctly from authoring values -- Commands that would mutate persistent data may be blocked or deferred - -Mode transitions are handled externally and reflected in the view. - ---- - -## Extension Points - -Contributors may extend the Inspector View by: - -- Adding custom property drawers for specific field types -- Providing component-specific inspectors with bespoke UI/validation -- Introducing contextual warnings or hints based on selection -- Enhancing multi-edit behavior across heterogeneous selections - -Extensions should integrate through existing data binding and command pathways. - ---- - -## Design Constraints - -The Inspector View is intentionally constrained to: - -- Observation of selection and component state -- Stateless or minimal local UI state -- Deterministic rendering based on shared data -- Command-only mutations; no direct state writes - -Keeping these boundaries strict ensures predictable editing and debuggability. - diff --git a/website/versioned_docs/version-0.10.6/08-CLI/CLI.md b/website/versioned_docs/version-0.10.6/08-CLI/CLI.md deleted file mode 100644 index 0ff5498a8..000000000 --- a/website/versioned_docs/version-0.10.6/08-CLI/CLI.md +++ /dev/null @@ -1,505 +0,0 @@ -# UntoldEngine CLI Tool - -> **Note:** The CLI tool source code is located in the `Tools/UntoldEngineCLI/` directory of the repository. -> -> For the most up-to-date documentation, see `Tools/UntoldEngineCLI/README.md` in the repository. - -The `untoldengine-create` command-line tool allows you to create and manage UntoldEngine game projects without launching the UntoldEditor. - -## Quick Start - -```bash -# Clone the repository -git clone https://github.com/untoldengine/UntoldEngine.git -cd UntoldEngine - -# Install the CLI globally -./scripts/install-create.sh - -# Create a project from anywhere -cd ~/anywhere -mkdir MyGame && cd MyGame -untoldengine-create create MyGame -``` - -## Complete Documentation - -For complete CLI documentation including: - -- Installation instructions -- Command reference -- Platform options -- Usage examples -- Troubleshooting - -Please refer to `Tools/UntoldEngineCLI/README.md` in the repository. - -## Quick Start - -Create a new game project in three simple steps: - -```bash -# 1. Create and enter your project directory -mkdir MyGame && cd MyGame - -# 2. Create the project (default: macOS) -untoldengine-create create MyGame - -# 3. Open in Xcode -open MyGame/MyGame.xcodeproj -``` - -All project files and assets will be created in the current directory. - -## Commands - -### create - -Creates a new UntoldEngine game project with the specified platform configuration. - -**Usage:** -```bash -untoldengine-create create [--platform ] -``` - -**Arguments:** -- ``: Name of your game project (required) - -**Options:** -- `--platform `: Target platform (default: `macos`) - - `macos` - macOS desktop application - - `ios` - iOS application - - `iosar` - iOS with ARKit support - - `visionos` - visionOS (Apple Vision Pro) - -**Examples:** - -Create a macOS game: -```bash -mkdir SpaceShooter && cd SpaceShooter -untoldengine-create create SpaceShooter -``` - -Create an iOS game: -```bash -mkdir MobileRPG && cd MobileRPG -untoldengine-create create MobileRPG --platform ios -``` - -Create an AR game for iOS: -```bash -mkdir ARAdventure && cd ARAdventure -untoldengine-create create ARAdventure --platform iosar -``` - -Create a visionOS game: -```bash -mkdir VisionGame && cd VisionGame -untoldengine-create create VisionGame --platform visionos -``` - -**What it does:** - -1. Creates the complete project structure in the current directory -2. Generates an Xcode project using XcodeGen -3. Sets up the game asset directories at `/Sources//GameData/` -4. Configures the UntoldEngine to load assets from the GameData directory -5. Creates platform-specific configuration files - -### update - -Updates the game data directory structure for an existing project. - -**Usage:** -```bash -untoldengine-create update -``` - -**Arguments:** -- ``: Name of the existing project (required) - -**What it does:** - -Re-creates the GameData folder structure at `/Sources//GameData/` with all standard asset directories: -- Models/ -- Scenes/ -- Scripts/ -- Animations/ -- Gaussians/ -- Shaders/ -- Textures/ - -**Example:** - -```bash -cd MyGame -untoldengine-create update MyGame -``` - -**When to use:** -- After accidentally deleting asset folders -- When you need to reset the folder structure -- To ensure all standard directories exist - -## Platform Options - -### macOS -**Command:** `--platform macos` (default) - -**Features:** -- Full desktop application -- Window management -- Keyboard and mouse input -- High performance rendering - -**Use for:** -- Desktop games -- Development and testing -- Maximum performance applications - -### iOS -**Command:** `--platform ios` - -**Features:** -- iPhone and iPad support -- Touch input -- Accelerometer support -- App Store distribution - -**Use for:** -- Mobile games -- Touch-based experiences -- Wide distribution via App Store - -### iOS AR (ARKit) -**Command:** `--platform iosar` - -**Features:** -- All iOS features -- ARKit integration -- World tracking -- Plane detection -- Face tracking - -**Use for:** -- Augmented reality games -- AR experiences -- Real-world interactive applications - -### visionOS -**Command:** `--platform visionos` - -**Features:** -- Apple Vision Pro support -- Spatial computing -- 3D UI elements -- Hand tracking -- Eye tracking - -**Use for:** -- Immersive 3D experiences -- Spatial applications -- Next-generation mixed reality - -## Project Structure - -After running `untoldengine-create create MyGame`, your project structure will look like: - -``` -MyGame/ # Your working directory -└── MyGame/ # Generated project - ├── MyGame.xcodeproj # Xcode project (generated) - ├── project.yml # XcodeGen configuration - ├── README.md # Project documentation - └── Sources/ - └── MyGame/ - ├── GameData/ # Game assets location - │ ├── Models/ # 3D models (.usdz, etc.) - │ ├── Scenes/ # Scene files - │ ├── Scripts/ # Game logic scripts - │ ├── Animations/ # Animation data - │ ├── Gaussians/ # Gaussian splatting data - │ ├── Shaders/ # Custom shaders - │ └── Textures/ # Image files - ├── AppDelegate.swift # Application entry point - ├── GameViewController.swift # Main game controller - └── Info.plist # App configuration -``` - -### Important Directories - -**GameData/**: The single location for all game assets -- Path: `MyGame/Sources/MyGame/GameData/` -- The UntoldEngine is configured to load all assets from this location -- Copy your game assets (models, textures, etc.) here -- Subdirectories are organized by asset type - -**Sources/**: Swift source code -- Contains your game's Swift files -- Platform-specific delegates and controllers -- Can add additional Swift files here - -### Adding Assets - -To add assets to your game, copy them into the appropriate GameData subdirectory: - -```bash -# Add a 3D model -cp ~/Downloads/spaceship.usdz MyGame/Sources/MyGame/GameData/Models/ - -# Add textures -cp ~/Downloads/texture_*.png MyGame/Sources/MyGame/GameData/Textures/ - -# Add a scene -cp ~/Downloads/level1.json MyGame/Sources/MyGame/GameData/Scenes/ -``` - -The engine will automatically find assets in these locations. - -## Workflow Examples - -### Typical Development Workflow - -```bash -# 1. Create project directory -mkdir MyAwesomeGame -cd MyAwesomeGame - -# 2. Create the game project -untoldengine-create create MyAwesomeGame --platform ios - -# 3. Add your game assets -cp -r ~/GameAssets/Models/* MyAwesomeGame/Sources/MyAwesomeGame/GameData/Models/ -cp -r ~/GameAssets/Textures/* MyAwesomeGame/Sources/MyAwesomeGame/GameData/Textures/ - -# 4. Open in Xcode and start developing -open MyAwesomeGame/MyAwesomeGame.xcodeproj -``` - -### Starting from Scratch - -```bash -# Create a new game from scratch -mkdir PuzzleGame && cd PuzzleGame -untoldengine-create create PuzzleGame --platform macos - -# The GameData directory is ready for your assets -ls -la PuzzleGame/Sources/PuzzleGame/GameData/ -# Models/ Scenes/ Scripts/ Animations/ Gaussians/ Shaders/ Textures/ -``` - -### Multi-Platform Development - -You might want to test the same game on different platforms: - -```bash -# Create macOS version for development -mkdir MyGame-macOS && cd MyGame-macOS -untoldengine-create create MyGame --platform macos - -# Create iOS version in a separate directory -cd .. -mkdir MyGame-iOS && cd MyGame-iOS -untoldengine-create create MyGame --platform ios - -# Copy assets between projects -cp -r ../MyGame-macOS/MyGame/Sources/MyGame/GameData/* \ - MyGame/Sources/MyGame/GameData/ -``` - -### Resetting Asset Structure - -If you accidentally delete asset folders or need to reset: - -```bash -cd MyGame -untoldengine-create update MyGame -# GameData structure is recreated -``` - -## Troubleshooting - -### Command Not Found - -**Problem:** `untoldengine-create: command not found` - -**Solution:** -1. Ensure you ran the installation script: - ```bash - cd /path/to/UntoldEngine - ./Scripts/install-create.sh - ``` - -2. Check if the tool exists: - ```bash - ls -l /usr/local/bin/untoldengine-create - ``` - -3. If it exists but still not found, add `/usr/local/bin` to your PATH: - ```bash - echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc - source ~/.zshrc - ``` - -### XcodeGen Failed - -**Problem:** Error message about XcodeGen failing - -**Solution:** -1. Ensure XcodeGen is installed: - ```bash - brew install xcodegen - ``` - -2. Verify XcodeGen works: - ```bash - xcodegen --version - ``` - -3. Check the project.yml file for syntax errors: - ```bash - cat path/to/project/project.yml - ``` - -### Build System Errors - -**Problem:** Error message about BuildSystem failure - -**Solution:** -1. Verify the UntoldEngine framework is properly built -2. Check that you're running the command from the correct directory -3. Ensure sufficient disk space for the project - -### Permission Denied - -**Problem:** Permission errors during installation - -**Solution:** -Use sudo for installation: -```bash -sudo ./Scripts/install-create.sh -``` - -Or install to a user directory (advanced): -```bash -# Edit install-create.sh to use a different install location -# Change INSTALL_PATH to ~/bin/untoldengine-create -``` - -### Xcode Project Won't Open - -**Problem:** Generated .xcodeproj file won't open - -**Solution:** -1. Regenerate the project: - ```bash - cd path/to/project - xcodegen generate - ``` - -2. Check Xcode version compatibility: - ```bash - xcodebuild -version - ``` - (Requires Xcode 15.0+) - -3. Verify project.yml is valid: - ```bash - cat project.yml - ``` - -### Assets Not Loading - -**Problem:** Game can't find assets at runtime - -**Solution:** -1. Verify assets are in the correct location: - ```bash - ls -la MyGame/Sources/MyGame/GameData/ - ``` - -2. Ensure assets are in the right subdirectories: - - Models go in `GameData/Models/` - - Textures go in `GameData/Textures/` - - etc. - -3. Check asset file names and extensions are correct - -4. Verify the GameData directory structure by running update: - ```bash - untoldengine-create update MyGame - ``` - -## Advanced Usage - -### Scripting and Automation - -The CLI tool is designed to work well in scripts: - -```bash -#!/bin/bash -# create-all-platforms.sh - -PROJECT_NAME="MyGame" - -for PLATFORM in macos ios iosar visionos; do - DIR="${PROJECT_NAME}-${PLATFORM}" - mkdir -p "$DIR" && cd "$DIR" - untoldengine-create create "$PROJECT_NAME" --platform "$PLATFORM" - cd .. -done -``` - -### CI/CD Integration - -Example GitHub Actions workflow: - -```yaml -name: Create Game Projects -on: [push] - -jobs: - create-projects: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - - name: Install CLI tool - run: | - cd UntoldEngine - ./Scripts/install-create.sh - - - name: Create projects - run: | - mkdir -p builds/MyGame-iOS && cd builds/MyGame-iOS - untoldengine-create create MyGame --platform ios -``` - -### Environment Variables - -While the CLI doesn't currently use environment variables, you can use them in your workflow: - -```bash -export GAME_NAME="MyGame" -export PLATFORM="ios" - -mkdir "$GAME_NAME" && cd "$GAME_NAME" -untoldengine-create create "$GAME_NAME" --platform "$PLATFORM" -``` - -## Getting Help - -For additional help: - -```bash -# General help -untoldengine-create --help - -# Command-specific help -untoldengine-create create --help -untoldengine-create update --help -``` - -## Feedback and Issues - -Found a bug or have a feature request? Please report it on the [UntoldEngine GitHub repository](https://github.com/untoldengine/UntoldEngine/issues). diff --git a/website/versioned_docs/version-0.10.6/images/Editor/EditorCodeScriptView-alt.png b/website/versioned_docs/version-0.10.6/images/Editor/EditorCodeScriptView-alt.png deleted file mode 100644 index 84d7461bc..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/Editor/EditorCodeScriptView-alt.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/Editor/Editor_scene_model_viewport.png b/website/versioned_docs/version-0.10.6/images/Editor/Editor_scene_model_viewport.png deleted file mode 100644 index 321d25529..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/Editor/Editor_scene_model_viewport.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/addcomponent.png b/website/versioned_docs/version-0.10.6/images/addcomponent.png deleted file mode 100644 index cc2995fe7..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/addcomponent.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/animationexportblenderStandalone.png b/website/versioned_docs/version-0.10.6/images/animationexportblenderStandalone.png deleted file mode 100644 index 77666c05e..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/animationexportblenderStandalone.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/demogame-noeditor.png b/website/versioned_docs/version-0.10.6/images/demogame-noeditor.png deleted file mode 100644 index d0a39e9e0..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/demogame-noeditor.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/engine-editor-startup.png b/website/versioned_docs/version-0.10.6/images/engine-editor-startup.png deleted file mode 100644 index 52519a1ed..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/engine-editor-startup.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/engine-gizmo.png b/website/versioned_docs/version-0.10.6/images/engine-gizmo.png deleted file mode 100644 index 0faa36698..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/engine-gizmo.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/gamescene1.png b/website/versioned_docs/version-0.10.6/images/gamescene1.png deleted file mode 100644 index 512513157..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/gamescene1.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/howtoexportStandalone.png b/website/versioned_docs/version-0.10.6/images/howtoexportStandalone.png deleted file mode 100644 index 0858b82a5..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/howtoexportStandalone.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/importassetbutton.png b/website/versioned_docs/version-0.10.6/images/importassetbutton.png deleted file mode 100644 index a2ea71d9d..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/importassetbutton.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/launchgame.gif b/website/versioned_docs/version-0.10.6/images/launchgame.gif deleted file mode 100644 index f13e49088..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/launchgame.gif and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/modelineditor.png b/website/versioned_docs/version-0.10.6/images/modelineditor.png deleted file mode 100644 index 951e24a5b..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/modelineditor.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.6/images/scripts_in_asset_browser.png b/website/versioned_docs/version-0.10.6/images/scripts_in_asset_browser.png deleted file mode 100644 index 002e8072b..000000000 Binary files a/website/versioned_docs/version-0.10.6/images/scripts_in_asset_browser.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/01-Overview.md b/website/versioned_docs/version-0.10.7/03-Game Development/01-Overview.md deleted file mode 100644 index a41e30ce9..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/01-Overview.md +++ /dev/null @@ -1,116 +0,0 @@ -# Overview - -This section covers **game development** with the Untold Engine. - -You'll learn how to create games using **Untold Engine Studio**, with two approaches for writing game logic: - -1. **Swift in Xcode** - Full engine API access (recommended) -2. **USC Scripts** - Component-based scripting (experimental) - -![editorsideshotalt](../images/Editor/EditorSideShotWide-alt.png) - ---- - -## Two Approaches to Game Logic - -Untold Engine gives you flexibility in how you write gameplay code: - -### Option 1: Swift in Xcode (Recommended) - -Write game logic in `GameScene.swift` using the full Untold Engine Swift API: -- Complete control over game systems and performance -- Access to all engine features and APIs -- Seamless integration with Xcode debugging and profiling -- Best for complex games and experienced developers - -### Option 2: USC Scripts (Experimental) - -Write component-based scripts that attach to entities: -- Simpler, component-oriented approach -- Good for prototyping and simple game mechanics -- Edit scripts in the integrated editor or Xcode -- API is experimental and subject to change - -**You can use both approaches in the same project.** - ---- - -## The Development Workflow - -A typical development workflow with Untold Engine Studio: - -1. **Create a project** using the "New" button in Untold Engine Studio -2. **Compose scenes visually** using the editor -3. **Write game logic** (Swift in `GameScene.swift` or USC scripts) -4. **Add assets** to the GameData/ directory -5. **Build & run** in Xcode (Cmd+R) -6. **Iterate** quickly with visual feedback - ---- - -## Entry Point 1: GameScene.swift (Swift) - -When you create a project, you get a clean `GameScene.swift` file for writing game logic in Swift: - -```swift -class GameScene { - - init() { - // Configure asset paths - setupAssetPaths() - - // Load game content - loadBundledScripts() - loadAndPlayFirstScene() - - // Start game systems - startGameSystems() - } - - func update(deltaTime: Float) { - // Your game logic goes here - } - - func handleInput() { - // Handle user input here - } -} -``` - -**This is where your game comes to life.** Write Swift code, access the full engine API, and build your game. - ---- - -## Entry Point 2: USC Scripts (Experimental) - -Alternatively, write game logic as USC scripts that attach to entities: - -Create USC scripts from the **Script** menu in Untold Engine Studio, then attach them to entities in the Inspector. - ---- - - -## Project Structure - -Your generated project has everything you need: - -``` -MyGame/ -└── MyGame/ - ├── MyGame.xcodeproj # Open in Xcode - └── Sources/ - └── MyGame/ - ├── GameData/ # Assets location - │ ├── Models/ - │ ├── Scenes/ - │ ├── Scripts/ - │ └── Textures/ - ├── GameScene.swift # Your game logic ⭐ - ├── GameViewController.swift # Renderer setup - └── AppDelegate.swift # App entry -``` - -**Focus on GameScene.swift** - that's where your game lives. - ---- - diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/02-Tutorials/02_Input/01_KeyboardMovement.md b/website/versioned_docs/version-0.10.7/03-Game Development/02-Tutorials/02_Input/01_KeyboardMovement.md deleted file mode 100644 index 988a49707..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/02-Tutorials/02_Input/01_KeyboardMovement.md +++ /dev/null @@ -1,261 +0,0 @@ -# Keyboard Movement - -Learn how to move entities using keyboard input. - ---- - -## Overview - -This tutorial shows you how to: -- Detect keyboard input using the Input System -- Move an entity based on WASD keys -- Combine input with Transform System - ---- - -## Prerequisites - -This tutorial assumes you have: -- A project with `GameScene.swift` open -- **A scene loaded** with at least one entity -- The entity has a name set in the editor (e.g., "Player") - -For complete API documentation: - -➡️ **[Input System API](../../../04-Engine%20Development/03-Engine%20Systems/UsingInputSystem.md)** - ---- - -## Step 1: Enable Input System - -Before detecting input, you need to register keyboard events. Add this to your `startGameSystems()` helper or in `init()`: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - startGameSystems() - - // Register input keyboard events - InputSystem.shared.registerKeyboardEvents() - - // Find the entity by name (set in the editor) - player = findEntity(name: "Player") - } -} -``` - -**Important**: Call `InputSystem.shared.registerKeyboardEvents()` once during initialization to enable keyboard input detection. - ---- - -## Step 2: Detect Keyboard Input - -The Input System provides a `keyState` object to check if keys are pressed: - -```swift path=null start=null -func update(deltaTime: Float) { - if gameMode == false { return } - - // Check if W key is pressed - if inputSystem.keyState.wPressed == true { - Logger.log(message: "W key pressed!") - } -} -``` - -**Available Keys**: -- `inputSystem.keyState.wPressed` - W key -- `inputSystem.keyState.aPressed` - A key -- `inputSystem.keyState.sPressed` - S key -- `inputSystem.keyState.dPressed` - D key - ---- - -## Step 3: Move Entity with WASD - -Combine input detection with movement: - -```swift path=null start=null -class GameScene { - var player: EntityID! - let moveSpeed: Float = 5.0 - - init() { - // ... setup code ... - player = findEntity(name: "Player") - } - - func update(deltaTime: Float) { - if gameMode == false { return } - - var movement = SIMD3(0, 0, 0) - - // Forward (W) - if inputSystem.keyState.wPressed == true { - movement.z += moveSpeed * deltaTime - } - - // Backward (S) - if inputSystem.keyState.sPressed == true { - movement.z -= moveSpeed * deltaTime - } - - // Left (A) - if inputSystem.keyState.aPressed == true { - movement.x -= moveSpeed * deltaTime - } - - // Right (D) - if inputSystem.keyState.dPressed == true { - movement.x += moveSpeed * deltaTime - } - - // Apply movement - if movement != SIMD3(0, 0, 0) { - translateBy(entityId: player, delta: movement) - } - } -} -``` - -**Result**: The player moves based on WASD input at 5 units per second. - ---- - -## Understanding the Code - -### Input Detection - -```swift path=null start=null -if inputSystem.keyState.wPressed == true { - // Key is currently pressed -} -``` - -The Input System automatically tracks key states. You don't need to register listeners or handle events. - -### Accumulating Movement - -```swift path=null start=null -var movement = SIMD3(0, 0, 0) - -if inputSystem.keyState.wPressed == true { - movement.z += moveSpeed * deltaTime -} - -if inputSystem.keyState.dPressed == true { - movement.x += moveSpeed * deltaTime -} -``` - -By accumulating input into a `movement` vector, diagonal movement (W+D) works correctly. - -### Delta Time - -Multiplying by `deltaTime` ensures consistent speed regardless of frame rate: - -```swift path=null start=null -movement.z += moveSpeed * deltaTime // ✅ Frame-rate independent -movement.z += 0.1 // ❌ Varies with frame rate -``` - ---- - -## Advanced: Normalized Diagonal Movement - -Diagonal movement (W+D) is currently faster than single-direction movement. To fix this, normalize the movement vector: - -```swift path=null start=null -func update(deltaTime: Float) { - if gameMode == false { return } - - var movement = SIMD3(0, 0, 0) - - // Accumulate input - if inputSystem.keyState.wPressed == true { - movement.z += 1.0 - } - if inputSystem.keyState.sPressed == true { - movement.z -= 1.0 - } - if inputSystem.keyState.aPressed == true { - movement.x -= 1.0 - } - if inputSystem.keyState.dPressed == true { - movement.x += 1.0 - } - - // Normalize and apply speed - if movement != SIMD3(0, 0, 0) { - movement = normalize(movement) * moveSpeed * deltaTime - translateBy(entityId: player, delta: movement) - } -} -``` - -**Result**: Diagonal movement is now the same speed as cardinal movement. - ---- - -## Example: Tank Controls (Forward + Rotation) - -For tank-style controls where forward moves along the entity's facing direction: - -```swift path=null start=null -class GameScene { - var player: EntityID! - let moveSpeed: Float = 5.0 - let turnSpeed: Float = 90.0 // Degrees per second - - func update(deltaTime: Float) { - if gameMode == false { return } - - // Forward/backward movement - var forwardMovement: Float = 0.0 - if inputSystem.keyState.wPressed == true { - forwardMovement = moveSpeed * deltaTime - } - if inputSystem.keyState.sPressed == true { - forwardMovement = -moveSpeed * deltaTime - } - - // Apply forward movement along entity's forward direction - if forwardMovement != 0.0 { - let forward = getForwardAxisVector(entityId: player) - translateBy(entityId: player, delta: forward * forwardMovement) - } - - // Left/right rotation - var turnAngle: Float = 0.0 - if inputSystem.keyState.aPressed == true { - turnAngle = -turnSpeed * deltaTime - } - if inputSystem.keyState.dPressed == true { - turnAngle = turnSpeed * deltaTime - } - - // Apply rotation - if turnAngle != 0.0 { - rotateBy(entityId: player, angle: turnAngle, axis: SIMD3(0, 1, 0)) - } - } -} -``` - ---- - -## Summary - -You've learned: - -✅ `inputSystem.keyState` - Check keyboard input -✅ Detect W, A, S, D keys -✅ Combine input with `translateBy()` -✅ Normalize diagonal movement -✅ Tank-style controls with rotation - ---- - diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md b/website/versioned_docs/version-0.10.7/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md deleted file mode 100644 index 8c903a355..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/02-Tutorials/03_Animation/01_PlayAnimation.md +++ /dev/null @@ -1,252 +0,0 @@ -# Play Animation - -Learn how to load and play animations on entities. - ---- - -## Overview - -This tutorial shows you how to: -- Load animation data from a `.usdc` file -- Play an animation on an entity -- Pause animations - ---- - -## Prerequisites - -This tutorial assumes you have: -- A project with `GameScene.swift` open -- **A scene loaded** with at least one entity that has an animation skeleton -- An animation file (e.g., `running.usdc`) in `GameData/Models/` or `GameData/Animations/` -- The entity has a name set in the editor (e.g., "Player") -- The entity has animations linked in the editor - -For complete API documentation: - -➡️ **[Animation System API](../../../04-Engine%20Development/03-Engine%20Systems/UsingAnimationSystem.md)** - ---- - -## Step 1: Find the Entity - -In `GameScene.swift`, add a property to store the entity reference: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - startGameSystems() - - // Find the entity by name (set in the editor) - player = findEntity(name: "Player") - } -} -``` - ---- - -## Step 2: Load the Animation - ->>> Skip this section if you linked an animation to the entity through the editor. - -Use `setEntityAnimations()` to load animation data: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load the running animation - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - } -} -``` - -**Parameters**: -- `filename`: The animation file name (without extension) -- `withExtension`: File extension (usually `"usdc"`) -- `name`: A label to reference this animation later - ---- - -## Step 3: Play the Animation - -Use `changeAnimation()` to start playing the animation: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load animation -- Ignore if you linked an animation through the editor - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - - // Play the animation - we assume that the animation linked is called running - changeAnimation(entityId: player, name: "running") - } -} -``` - -**Result**: The entity plays the "running" animation in a loop. - ---- - -## Step 4: Pause the Animation (Optional) - -To pause or resume an animation: - -```swift path=null start=null -// Pause the animation -pauseAnimationComponent(entityId: player, isPaused: true) - -// Resume the animation -pauseAnimationComponent(entityId: player, isPaused: false) -``` - ---- - -## Loading Multiple Animations - -You can load multiple animations for the same entity: - -```swift path=null start=null -class GameScene { - var player: EntityID! - - init() { - // ... setup code ... - player = findEntity(name: "Player") - - // Load multiple animations -- Ignore if you linked all three animations through the editor - setEntityAnimations( - entityId: player, - filename: "idle", - withExtension: "usdc", - name: "idle" - ) - - setEntityAnimations( - entityId: player, - filename: "running", - withExtension: "usdc", - name: "running" - ) - - setEntityAnimations( - entityId: player, - filename: "jumping", - withExtension: "usdc", - name: "jumping" - ) - - // Start with idle animation - changeAnimation(entityId: player, name: "idle") - } -} -``` - ---- - -## Switching Animations at Runtime - -Switch between animations based on game state: - -```swift path=null start=null -class GameScene { - var player: EntityID! - var isRunning: Bool = false - - init() { - // ... setup code ... - startGameSystems() - - // Register input keyboard events - InputSystem.shared.registerKeyboardEvents() - - } - - func update(deltaTime: Float) { - if gameMode == false { return } - - // Check if player is moving - let wasRunning = isRunning - isRunning = inputSystem.keyState.wPressed || - inputSystem.keyState.aPressed || - inputSystem.keyState.sPressed || - inputSystem.keyState.dPressed - - // Switch animation when state changes - if isRunning && !wasRunning { - changeAnimation(entityId: player, name: "running") - } else if !isRunning && wasRunning { - changeAnimation(entityId: player, name: "idle") - } - } -} -``` - ---- - -## File Organization - -Animation files should be placed in your project's `GameData` directory: - - -``` -Sources/YourProject/GameData/ -├── Models/ -│ └── player.usdz -├── Animations/ -│ ├── idle.usdc -│ ├── running.usdc -│ └── jumping.usdc -``` - ---- - -## Troubleshooting - -### Animation doesn't play - -1. **Check the entity has a skeleton**: Not all models support animation. The model must have a skeletal rig. -2. **Verify file path**: Make sure the animation file exists in `GameData/Models/` or `GameData/Animations/`. -3. **Check animation name**: The `name` parameter must match when loading and playing. - -### Animation plays too fast/slow - -Animation playback speed is controlled by the animation file itself. To adjust speed, you'll need to modify the animation in your 3D software (e.g., Blender, Maya) or use animation blending (advanced topic). - ---- - -## Summary - -You've learned: - -✅ `setEntityAnimations()` - Load animation data -✅ `changeAnimation()` - Play a specific animation -✅ `pauseAnimationComponent()` - Pause/resume animations -✅ Load multiple animations per entity -✅ Switch animations based on game state - ---- - - diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/02-USC/02_QuickStart.md b/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/02-USC/02_QuickStart.md deleted file mode 100644 index bb021ce36..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/02-USC/02_QuickStart.md +++ /dev/null @@ -1,197 +0,0 @@ -# USC Scripting – Quick Start - -This tutorial will walk you through creating your first script in the Untold Engine, from setup to testing in Play mode. - -**What you'll build:** A cube that moves upward by changing its position every frame. - -**Time:** ~10 minutes - ---- - -## Step 1: Create Your First Script - -### 1.1 Configure Asset Path - -1. Open Untold Engine. -2. Go to **Asset Library → Set Asset Folder** -3. Create/Select your projects's asset directory. - -This is where your assets will be saved. Including your Scripts. - -![assetlibraryloupe](../../../images/Editor/EditorAssetLibraryLoupe.png) - -### 1.2 Create Your First Script - -1. In the editor toolbar, click **Scripts: Open in Xcode** (blue button). -2. Click the `+` button to create a new script. -3. Enter the script name: `MovingCube` -4. Xcode opens the Scripts package so you can edit the new file. - -When the script is created: -- The source file is added to your project -- Xcode shows a template like this: - -```swift -import Foundation -import UntoldEngine - -extension GenerateScripts { - static func generateMovingCube(to dir: URL) { - // Write your script here - } -} -``` - -You will edit everything in Xcode. - ---- - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateMovingCube(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateMovingCube) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - -## Step 3: Write USC Logic - -Replace the function with this complete script: - -```swift -extension GenerateScripts { - static func generateMovingCube(to dir: URL) { - let script = buildScript(name: "MovingCube") { s in - // Run every frame - s.onUpdate() - .getProperty(.position, as: "pos") - .setVariable("offset", to: simd_float3(x: 0.0, y: 0.1, z: 0.0)) - .addVec3("pos", "offset", as: "newPos") - .setProperty(.position, toVariable: "newPos") - } - - let outputPath = dir.appendingPathComponent("MovingCube.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ MovingCube.uscript") - } -} -``` - ---- - -## Step 4: Build Scripts - -After editing scripts, build and run in Xcode so the engine can use the generated output. - -- In Xcode, press `Cmd+R` to run the GenerateScripts target and produce the `.uscript` files. (Optional: `Cmd+B` to check the build first.) -- Build and run output appears inside Xcode. - -> A successful build makes the script available in the Asset Library to attach to entities. - -**First build?** It may take 30-60 seconds to download dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Add an Entity to the scene - -1. In the editor, click on "+" in the scenegraph -2. Click on "Cube" -3. A cube will show up in the scene. Make sure to select it. - -## Step 6: Link script to entity - -4. Open the **Asset Library** -5. Under the Script category, look for your script `MovingCube` and double click on the `.uscript`. This will automatically link the script to the entity. -6. To verify, the Inspector View will show the newly added script linked to the entity. - -![editorassetbrowserscript](../../../images/Editor/EditorAssetBrowserScripts.png) - -The script is now active and will run according to the engine update loop. - ---- - -## Step 7: Test in Play Mode - -### 7.1 Run the Scene - -1. Click **Play** in the editor -2. Watch your entity move upward continuously! - -### 7.2 Stop Play Mode - -Click **Stop** when done testing. - ---- - -## Step 8: Test Hot Reload - Move Sideways - -Let's modify the script to move the cube sideways instead of upward, and test the hot reload feature. - -### 8.1 Modify the Offset - -Modify your script to change the offset direction: - -```swift -extension GenerateScripts { - static func generateMovingCube(to dir: URL) { - let script = buildScript(name: "MovingCube") { s in - s.onUpdate() - .getProperty(.position, as: "pos") - .setVariable("offset", to: simd_float3(x: 0.1, y: 0.0, z: 0.0)) // Changed to move sideways - .addVec3("pos", "offset", as: "newPos") - .setProperty(.position, toVariable: "newPos") - } - - let outputPath = dir.appendingPathComponent("MovingCube.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ MovingCube.uscript") - } -} -``` - -**What changed:** The offset is now `(0.1, 0.0, 0.0)` instead of `(0.0, 0.1, 0.0)`, so the cube will move along the X-axis (sideways) instead of the Y-axis (upward). - -### 8.2 Rebuild - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target. -2. Wait for "✅ MovingCube.uscript" in the Xcode run output - -### 8.3 Hot Reload - -Back in the editor: -1. With your entity selected -2. Click **"Reload"** (orange button) in the Script Component Inspector -3. Click **Play** to test the changes -4. The cube should now move sideways instead of upward! - -![scriptreload](../../../images/Editor/ScriptReload.png) - diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/02-USC/05_APIReference.md b/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/02-USC/05_APIReference.md deleted file mode 100644 index ac00431a7..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/02-USC/05_APIReference.md +++ /dev/null @@ -1,624 +0,0 @@ ---- -id: usc-scripting-api -title: USC Scripting API Reference -sidebar_label: API Reference -sidebar_position: 10 ---- - -# Untold Engine – USC Scripting API Reference - -USC (Untold Script Core) is the scripting system inside the Untold Engine. -You write scripts in Swift using a fluent DSL, and the engine executes them at runtime. - -This reference provides the complete API surface for building gameplay scripts. - ---- - -## 1. Script Lifecycle - -### Building and Exporting Scripts - -USC provides two ways to create scripts: - -**buildScript()** - Creates a script in memory: -```swift -let script = buildScript(name: "MyScript") { s in - s.onUpdate() - .log("Running every frame") -} -``` - -**saveUSCScript()** - Saves a script to a `.uscript` file: -```swift -let outputPath = dir.appendingPathComponent("MyScript.uscript") -try? saveUSCScript(script, to: outputPath) -``` - -**Typical Pattern** - Build then save: -```swift -extension GenerateScripts { - static func generateMyScript(to dir: URL) { - let script = buildScript(name: "MyScript") { s in - s.onUpdate() - .log("Running every frame") - } - - let outputPath = dir.appendingPathComponent("MyScript.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ MyScript.uscript") - } -} -``` - -### TriggerType (Optional) - -**Default:** `.perFrame` (runs every frame) - -You only need to specify `triggerType` if you want something other than the default: - -**When to override:** - -- **`.event`** - For event-driven scripts (collision handlers, triggers) - ```swift - let script = buildScript(name: "DoorTrigger", triggerType: .event) { s in - s.onCollision(tag: "Player") // Coming soon - collision system not yet implemented - .log("Door opened!") - } - ``` - -- **`.manual`** - For manually controlled scripts (cutscenes, special sequences) - ```swift - let script = buildScript(name: "Cutscene", triggerType: .manual) { s in - s.onEvent("StartCutscene") - .log("Cutscene playing...") - } - ``` - -**Most scripts don't need to specify this** - the default `.perFrame` works for continuous behaviors like movement and AI. - -### ExecutionMode (Optional) - -**Default:** `.auto` (engine manages execution) - -You rarely need to override this. Only specify `executionMode` for advanced scenarios: - -- **`.interpreted`** - Force interpreter-based execution (debugging, special cases) - ```swift - let script = buildScript(name: "DebugScript", executionMode: .interpreted) { s in - s.onUpdate() - .log("Debug mode") - } - ``` - -**Most scripts should use the default** `.auto` mode. - ---- - -## 2. Events (Entry Points) - -Events define when code blocks execute. Chain commands after each event: - -**onStart()** - Runs once when the entity starts (like Awake/Start in Unity): -```swift -s.onStart() - .setVariable("health", to: 100.0) - .setVariable("speed", to: 5.0) - .log("Entity initialized") -``` - -**onUpdate()** - Runs every frame (like Update in Unity): -```swift -s.onUpdate() - .getProperty(.position, as: "pos") - .log("Current position") -``` - -**onCollision(tag:)** - Runs when colliding with tagged entities: -> ⚠️ **Coming Soon** - The collision system is not yet implemented. This API is planned for a future release. - -```swift -s.onCollision(tag: "Enemy") - .log("Hit an enemy!") - .setVariable("health", to: 0.0) -``` - -**onEvent(_:)** - Runs when a custom event is fired: -```swift -s.onEvent("PowerUpCollected") - .setVariable("speed", to: 10.0) - .log("Speed boost activated") -``` - -### Multiple Event Handlers - -You can define multiple event handlers in one script: -```swift -let script = buildScript(name: "Player") { s in - s.onStart() - .setVariable("score", to: 0.0) - - s.onUpdate() - .getProperty(.position, as: "pos") - - // Coming soon - collision system not yet implemented - s.onCollision(tag: "Coin") - .addFloat("score", 1.0, as: "score") -} - -let outputPath = dir.appendingPathComponent("Player.uscript") -try? saveUSCScript(script, to: outputPath) -``` - -### Interpreter Execution (Advanced) - -For `.interpreted` execution mode: -```swift -interpreter.execute(script: script, context: context, forEvent: "OnStart") -interpreter.execute(script: script, context: context, forEvent: nil) // onUpdate -``` - ---- - -## 3. Script Context - -Every script runs with a **context** that provides access to: -- **Entity properties** (position, scale, velocity, acceleration, lights) -- **Script variables** (custom data you store) -- **Engine state** (delta time, input, etc.) - -You access the entity's properties using `.getProperty()` and `.setProperty()`. - -**Available at Runtime:** -- Current entity's transform (position, scale) -- Physics properties (velocity, acceleration, mass) -- Rendering properties (color, intensity for lights) -- All script variables you've defined - -**Example:** -```swift -s.onUpdate() - .getProperty(.position, as: "currentPos") // Read from entity - .getProperty(.velocity, as: "currentVel") // Read physics - .setVariable("myCustomData", to: 42.0) // Store in script - .setProperty(.position, toVariable: "newPos") // Write to entity -``` - ---- - -## 4. Flow Control - -**Conditionals** - Execute code based on comparisons: -```swift -s.ifCondition( - lhs: .variableRef("speed"), - .greater, - rhs: .float(10.0) -) { nested in - nested.log("Too fast!") - nested.setVariable("speed", to: 10.0) -} -``` - -**Available operators:** -- `.greater`, `.less` -- `.equal`, `.notEqual` -- `.lessOrEqual`, `.greaterOrEqual` - -**Convenience conditionals:** -```swift -s.ifGreater("speed", than: 10.0) { nested in - nested.log("Too fast!") -} - -s.ifLess("health", than: 20.0) { nested in - nested.log("Low health!") -} - -s.ifEqual("state", to: 1.0) { nested in - nested.log("State is 1") -} -``` - -**Organizing math-heavy code with `.math { ... }`:** -```swift -s.onUpdate() - .math { m in - m.getProperty(.velocity, as: "vel") - m.lengthVec3("vel", as: "speed") - m.ifGreater("speed", than: 10) { n in - n.normalizeVec3("vel", as: "dir") - n.scaleVec3("dir", literal: 10, as: "clampedVel") - n.setProperty(.velocity, toVariable: "clampedVel") - } - } - .log("Velocity clamped if above 10") -``` - ---- - -## 5. Values & Variables - -**Value Types** - USC supports these data types: -```swift -enum Value { - case float(Float) // Single number - case vec3(x: Float, y: Float, z: Float) // 3D vector - case string(String) // Text - case bool(Bool) // True/false - case variableRef(String) // Reference to a variable -} -``` - -**Setting Variables:** -```swift -s.setVariable("speed", to: 5.0) -s.setVariable("direction", to: simd_float3(x: 1, y: 0, z: 0)) -s.setVariable("isActive", to: true) -s.setVariable("playerName", to: "Hero") -``` - -**Using Variable References:** -```swift -s.setVariable("maxSpeed", to: 10.0) -s.setVariable("currentSpeed", to: .variableRef("maxSpeed")) // Copy value -``` - ---- - -## 6. Engine Properties - -**Available Properties** - Read/write entity properties: -```swift -enum ScriptProperty: String { - // Transform - case position, scale - - // Physics - case velocity, acceleration, mass, angularVelocity - - // Rendering (lights) - case intensity, color - - // Engine time - case deltaTime -} -``` - -**Reading Properties:** -```swift -s.getProperty(.position, as: "pos") // Store position in "pos" variable -s.getProperty(.velocity, as: "vel") // Store velocity in "vel" variable -s.getProperty(.deltaTime, as: "dt") // Store frame delta time -``` - -**Writing Properties:** -```swift -s.setProperty(.position, toVariable: "newPos") // Set from variable -s.setProperty(.velocity, to: simd_float3(x: 0, y: 5, z: 0)) // Set from literal -s.setProperty(.angularVelocity, to: simd_float3(x: 0, y: 1, z: 0)) // Set spin (write-only today) -``` - -> Note: Rotation is controlled through `rotateTo` / `rotateBy` instructions. Reading rotation via `getProperty(.rotation, ...)` is not yet supported. - -**Complete Example:** -```swift -s.onUpdate() - .getProperty(.position, as: "currentPos") - .setVariable("offset", to: simd_float3(x: 0, y: 0.1, z: 0)) - .addVec3("currentPos", "offset", as: "newPos") - .setProperty(.position, toVariable: "newPos") // Move entity up -``` - ---- - -## 7. Math Operations - -**Float Math:** -```swift -s.addFloat("a", "b", as: "sum") // sum = a + b (two variables) -s.addFloat("a", literal: 5.0, as: "sum") // sum = a + 5 (variable + literal) -s.mulFloat("a", "b", as: "product") // product = a * b (two variables) -s.mulFloat("a", literal: 2.0, as: "product") // product = a * 2 (variable * literal) -``` - -**Vector Math:** -```swift -s.addVec3("v1", "v2", as: "sum") // sum = v1 + v2 -s.scaleVec3("dir", literal: 2.0, as: "scaled") // scaled = dir * 2.0 -s.scaleVec3("dir", by: "scale", as: "scaled") // scaled = dir * scale -s.lengthVec3("vec", as: "length") // length = magnitude of vec -s.normalizeVec3("vec", as: "unitVec") // normalized vec (zero-safe) -s.dotVec3("a", "b", as: "dot") // dot product -> float -s.crossVec3("a", "b", as: "cross") // cross product -> vec3 -s.lerpVec3(from: "a", to: "b", t: "t", as: "lerped") // linear interpolation -s.lerpFloat(from: "a", to: "b", t: "t", as: "out") // scalar lerp -s.reflectVec3("v", normal: "n", as: "reflected") // reflect v about normal -s.projectVec3("v", onto: "axis", as: "proj") // project v onto axis -s.angleBetweenVec3("a", "b", as: "angleDeg") // angle in degrees -s.clampFloat("speed", min: "minSpeed", max: "maxSpeed", as: "clampedSpeed") // bounds via vars -s.clampVec3("velocity", min: "minVel", max: "maxVel", as: "clampedVel") // component-wise -``` - -**Example - Calculate velocity:** -```swift -s.onUpdate() - .setVariable("direction", to: simd_float3(x: 1, y: 0, z: 0)) - .setVariable("speed", to: 5.0) - .scaleVec3("direction", by: "speed", as: "velocity") - .setProperty(.velocity, toVariable: "velocity") -``` - ---- - -## 8. Built-in Behaviors (Steering, Camera, Physics) - -All behaviors are instruction helpers—no `callAction` or `ScriptArgKey`. - -**Steering** -```swift -// Seek toward a target and store the steering force -s.seek(targetPosition: .vec3(x: 10, y: 0, z: 0), - maxSpeed: .float(5.0), - result: "seekForce") - -s.steerSeek(targetPosition: .variableRef("targetPos"), - maxSpeed: .variableRef("maxSpeed"), - deltaTime: .variableRef("dt"), - turnSpeed: .variableRef("turnSpeed")) - -// Arrive with slowing radius -s.steerArrive(targetPosition: .variableRef("targetPos"), - maxSpeed: .variableRef("maxSpeed"), - slowingRadius: .variableRef("slowingRadius"), - deltaTime: .variableRef("dt"), - turnSpeed: .variableRef("turnSpeed")) - -// Evade a threat: compute force into result or apply directly -s.steerEvade(threatEntity: .string("Enemy"), - maxSpeed: .float(6.0), - result: "evadeForce") // omit result to apply immediately - -// Pursuit -s.steerPursuit(targetEntity: .variableRef("targetName"), - maxSpeed: .variableRef("maxSpeed"), - deltaTime: .variableRef("dt"), - turnSpeed: .variableRef("turnSpeed")) - -// Align orientation to current velocity (smooth) -s.alignOrientation(deltaTime: .float(0.016), - turnSpeed: .float(1.0)) -``` - -**Camera** -```swift -// Snap camera to a position -s.cameraMoveTo(.vec3(x: 0, y: 3, z: -10)) - -// Look at a target -s.cameraLookAt(eye: .vec3(x: 0, y: 3, z: -8), - target: .variableRef("lookTarget"), - up: .vec3(x: 0, y: 1, z: 0)) - -// Follow a target with smoothing -s.cameraFollow(target: .string("Player"), - offset: .vec3(x: 0, y: 3, z: -6), - smoothFactor: .float(5.0), - deltaTime: .float(0.016)) - -// WASDQE fly camera -s.cameraMoveWithInput(speedVar: "moveSpeed", - deltaTimeVar: "dt", - wVar: "wPressed", - aVar: "aPressed", - sVar: "sPressed", - dVar: "dPressed", - qVar: "qPressed", - eVar: "ePressed") - -// Orbit a target entity (auto look-at) -s.cameraOrbitTarget(target: .string("Boss"), - radius: .float(12.0), - speed: .float(1.5), - deltaTime: .float(0.016), - offsetY: .float(1.5)) -``` - -**Physics** -```swift -// Impulse -s.applyLinearImpulse(direction: .vec3(x: 1, y: 0, z: 0), - magnitude: .float(5.0)) - -// Continuous world force -s.applyWorldForce(direction: .vec3(x: 0, y: 1, z: 0), - magnitude: .float(3.0)) - -// Velocity control -s.setLinearVelocity(.vec3(x: 0, y: 0, z: 5)) -s.addLinearVelocity(.variableRef("deltaVel")) -s.clampLinearSpeed(min: .float(2.0), max: .float(8.0)) - -// Angular control -s.applyAngularImpulse(axis: .vec3(x: 0, y: 1, z: 0), magnitude: .float(2.0)) -s.clampAngularSpeed(max: .float(5.0)) -s.applyAngularDamping(damping: .float(0.6), deltaTime: .float(0.016)) -``` - ---- - -## 9. Transform & Physics Helpers - -**Transform:** -```swift -s.translateTo(x: 1, y: 2, z: 3) // Set absolute position -s.translateTo(simd_float3(x: 1, y: 2, z: 3)) // Alternative syntax -s.translateBy(x: 0.1, y: 0, z: 0) // Move relative -s.translateBy(simd_float3(x: 0.1, y: 0, z: 0)) // Alternative syntax -s.rotateTo(degrees: 45, axis: simd_float3(x: 0, y: 1, z: 0)) // Set absolute rotation -s.rotateBy(degrees: 45, axis: simd_float3(x: 0, y: 1, z: 0)) // Rotate relative -s.lookAt("targetEntityName") // Face another entity -``` - -**Physics - Force & Torque:** -```swift -s.applyForce(force: simd_float3(x: 0, y: 10, z: 0)) // Apply linear force -s.applyMoment(force: simd_float3(x: 5, y: 0, z: 0), at: simd_float3(x: 1, y: 0, z: 0)) // Apply torque at point -``` - -**Physics - Velocity Control:** -```swift -s.clearVelocity() // Stop linear movement instantly -s.clearAngularVelocity() // Stop rotation instantly -s.clearForces() // Clear accumulated forces -``` - -**Physics - Gravity & Pause:** -```swift -s.setGravityScale(0.5) // Half gravity (0 = no gravity, 1 = normal, 2 = double) -s.pausePhysicsComponent(isPaused: true) // Pause/unpause physics simulation -``` - -**Example - Jump mechanic:** -```swift -s.onEvent("Jump") - .getProperty(.velocity, as: "currentVel") - .setVariable("jumpForce", to: simd_float3(x: 0, y: 15, z: 0)) - .addVec3("currentVel", "jumpForce", as: "newVel") - .setProperty(.velocity, toVariable: "newVel") -``` - -**Example - Reset physics:** -```swift -s.onEvent("Respawn") - .clearVelocity() // Stop all movement - .clearAngularVelocity() // Stop all rotation - .clearForces() // Clear force accumulation - .translateTo(simd_float3(x: 0, y: 5, z: 0)) // Move to spawn point -``` - -**Example - Apply torque to spin:** -```swift -s.onUpdate() - .ifKeyPressed("R") { n in - // Apply torque at the right edge to spin left - n.applyMoment(force: simd_float3(x: 0, y: 10, z: 0), at: simd_float3(x: 1, y: 0, z: 0)) - } -``` - -**Animation:** -```swift -s.playAnimation("Walk", loop: true) // Play looping animation -s.playAnimation("Jump", loop: false) // Play once -s.stopAnimation() // Stop current animation -``` - ---- - -## 10. Input Conditions - -**Keyboard Input:** -```swift -s.ifKeyPressed("W") { nested in - nested.log("Forward") - nested.applyForce(force: simd_float3(x: 0, y: 0, z: -1)) -} - -s.ifKeyPressed("Space") { nested in - nested.log("Jump!") - nested.applyForce(force: simd_float3(x: 0, y: 10, z: 0)) -} -``` - -**Example - WASD movement:** -```swift -s.onUpdate() - .setVariable("moveSpeed", to: 5.0) - .ifKeyPressed("W") { n in - n.applyForce(force: simd_float3(x: 0, y: 0, z: -5)) - } - .ifKeyPressed("S") { n in - n.applyForce(force: simd_float3(x: 0, y: 0, z: 5)) - } - .ifKeyPressed("A") { n in - n.applyForce(force: simd_float3(x: -5, y: 0, z: 0)) - } - .ifKeyPressed("D") { n in - n.applyForce(force: simd_float3(x: 5, y: 0, z: 0)) - } -``` - ---- - -## 11. Logging & Debugging - -**Log Messages:** -```swift -s.log("Debug message") // Simple message -s.log("Player health: 100") // Can include values -s.logValue("velocity", value: .variableRef("vel")) // Log a labeled variable -s.logValue("spawnPoint", value: .vec3(x: 0, y: 1, z: 2)) // Log a literal with a label -``` - -**Debug Variables:** -```swift -s.onUpdate() - .getProperty(.position, as: "pos") - .log("Position updated") // Track when events occur -``` - ---- - -## 12. Best Practices - -### Use enums instead of raw strings -✅ **Good:** -```swift -s.getProperty(.position, as: "pos") -s.setProperty(.velocity, toVariable: "vel") -``` - -❌ **Avoid:** -```swift -s.getProperty("position", as: "pos") // String-based, no autocomplete -``` - -### Variable Naming -- Use descriptive names: `"playerHealth"` not `"h"` -- Consistent naming: `"currentPos"`, `"targetPos"`, `"newPos"` -- Avoid conflicts with property names - -### Performance -- Use `.perFrame` for continuous behaviors (movement, AI) -- Use `.event` for one-time triggers (collision, pickups) -- Minimize operations in `onUpdate()` when possible - -### Script Organization -```swift -let script = buildScript(name: "Enemy") { s in - // Initialization - s.onStart() - .setVariable("health", to: 100.0) - .setVariable("speed", to: 3.0) - - // Main loop - s.onUpdate() - .setVariable("maxSpeed", to: 5.0) - .seek(targetPosition: .string("Player"), - maxSpeed: .variableRef("maxSpeed"), - result: "steer") - .applyForce(force: .variableRef("steer")) - - // Event handlers (collision system coming soon) - s.onCollision(tag: "Bullet") - .subtractFloat("health", 10.0, as: "health") -} - -let outputPath = dir.appendingPathComponent("Enemy.uscript") -try? saveUSCScript(script, to: outputPath) -``` - -### Debugging Tips -- Add `.log()` statements to trace execution -- Use meaningful variable names for debugging -- Test scripts incrementally -- Check console output in Play mode diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/00_HelloWorld.md b/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/00_HelloWorld.md deleted file mode 100644 index d2300510a..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/00_HelloWorld.md +++ /dev/null @@ -1,228 +0,0 @@ -# "Hello World" - Your First Script - -**What you'll learn:** -- Creating a script in Untold Engine Studio -- Using `onStart()` and `onUpdate()` lifecycle events -- Logging messages for debugging -- Attaching scripts to entities - -**Time:** ~5 minutes - ---- - -## What We're Building - -A simple script that: -1. Logs "Hello, Untold Engine!" when the entity starts -2. Logs "Script running..." every frame during Play mode - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `HelloWorld` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateHelloWorld(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateHelloWorld(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateHelloWorld) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateHelloWorld(to dir: URL) { - let script = buildScript(name: "HelloWorld") { s in - // Runs once when entity starts - s.onStart() - .log("Hello, Untold Engine!") - .log("Script initialized successfully") - - // Runs every frame - s.onUpdate() - .log("Script running...") - } - - let outputPath = dir.appendingPathComponent("HelloWorld.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ HelloWorld.uscript") - } -} -``` - -### Understanding the Code - -**`buildScript(name:)`** - Creates a new script -- The name identifies the script in the editor - -**`onStart()`** - Lifecycle event that runs once -- Perfect for initialization -- Logs appear in the console when Play mode starts - -**`onUpdate()`** - Lifecycle event that runs every frame -- Use for continuous behaviors -- Be mindful of performance (runs 60+ times per second!) - -**`.log()`** - Outputs debug messages -- Messages appear in the editor's Console view -- Great for debugging and tracking execution - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ HelloWorld.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Select any entity in your scene (create a cube if needed) -3. In the Inspector panel, click **Add Component** → **Script Component** -4. In the Asset Browser, find `HelloWorld.uscript` under Scripts/Generated -5. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 6: Test It! - -1. Click **Play** in the toolbar -2. Open the **Console** view (bottom panel) -3. You should see: - ``` - Hello, Untold Engine! - Script initialized successfully - Script running... - Script running... - Script running... - ... - ``` - -4. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- **"Hello, Untold Engine!"** appears once (from `onStart()`) -- **"Script running..."** appears continuously (from `onUpdate()`) - -⚠️ **Performance Note:** `onUpdate()` runs every frame! In a real game, avoid heavy logging in `onUpdate()`. This example is just for demonstration. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Change the Messages -```swift -s.onStart() - .log("Game initialized") - .log("Player ready!") -``` - -### Remove the Update Log -```swift -s.onUpdate() - // Remove .log() to avoid console spam -``` - -### Add Initialization Variables -```swift -s.onStart() - .setVariable("playerName", to: "Hero") - .setVariable("health", to: 100.0) - .log("Player initialized with 100 health") -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ How to create a script in Untold Engine Studio -✅ Using `onStart()` for initialization -✅ Using `onUpdate()` for per-frame logic -✅ Logging debug messages -✅ Building and attaching scripts to entities -✅ Testing scripts in Play mode - ---- diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/02_Input/01_KeyboardMovement.md b/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/02_Input/01_KeyboardMovement.md deleted file mode 100644 index 20a9f5800..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/02_Input/01_KeyboardMovement.md +++ /dev/null @@ -1,268 +0,0 @@ -# Keyboard Movement - WASD Motion - -**What you'll learn:** -- Reading keyboard input with `getKeyState()` -- Building movement vectors from multiple keys -- Combining input with `setProperty(.position)` -- Iterating quickly with Xcode builds - -**Time:** ~10 minutes - -**Prerequisites:** -- Untold Engine Studio open with an entity you want to move -- Access to Xcode via **Scripts: Open in Xcode** (toolbar button) - ---- - -## What We're Building - -A simple controller that: -1. Moves with **WASD** -2. Supports diagonal movement by combining keys -3. Uses a configurable speed variable - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `KeyboardMovement` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateKeyboardMovement(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateKeyboardMovement(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateKeyboardMovement) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateKeyboardMovement(to dir: URL) { - let script = buildScript(name: "KeyboardMovement") { s in - // Runs once when the entity starts - s.onStart() - .setVariable("moveSpeed", to: 0.2) // units per frame - .setVariable("forward", to: simd_float3(x: 0, y: 0, z: 1)) - .setVariable("right", to: simd_float3(x: 1, y: 0, z: 0)) - .log("KeyboardMovement ready") - - // Runs every frame - s.onUpdate() - .getProperty(.position, as: "currentPos") - .setVariable("offset", to: simd_float3(x: 0, y: 0, z: 0)) - - // Read keys - .getKeyState("w", as: "wPressed") - .getKeyState("s", as: "sPressed") - .getKeyState("a", as: "aPressed") - .getKeyState("d", as: "dPressed") - - // Forward (W) - .ifEqual("wPressed", to: true) { n in - n.scaleVec3("forward", by: "moveSpeed", as: "step") - n.addVec3("offset", "step", as: "offset") - } - // Backward (S) - .ifEqual("sPressed", to: true) { n in - n.scaleVec3("forward", by: "moveSpeed", as: "stepForward") - n.scaleVec3("stepForward", literal: -1.0, as: "stepBack") - n.addVec3("offset", "stepBack", as: "offset") - } - // Left (A) - .ifEqual("aPressed", to: true) { n in - n.scaleVec3("right", by: "moveSpeed", as: "stepRight") - n.scaleVec3("stepRight", literal: -1.0, as: "stepLeft") - n.addVec3("offset", "stepLeft", as: "offset") - } - // Right (D) - .ifEqual("dPressed", to: true) { n in - n.scaleVec3("right", by: "moveSpeed", as: "stepRight") - n.addVec3("offset", "stepRight", as: "offset") - } - - // Apply movement - .addVec3("currentPos", "offset", as: "nextPos") - .setProperty(.position, toVariable: "nextPos") - } - - let outputPath = dir.appendingPathComponent("KeyboardMovement.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ KeyboardMovement.uscript") - } -} -``` - -### Understanding the Code - -**`getKeyState()`** - Reads whether a key is pressed this frame -- Lowercase strings like `"w"` match keyboard keys - -**Offset pattern** - Build movement from multiple keys -- Start with `offset = (0,0,0)` -- Add forward/back and left/right contributions -- Apply once per frame - -**`moveSpeed` variable** - Single place to tune speed -- Scale any direction by this value (or its negative) - -**Position-based control** - Directly updates `.position` -- No physics required for this example -- Works consistently every frame - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ KeyboardMovement.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Select the entity you want to move -3. In the Inspector panel, click **Add Component** → **Script Component** -4. In the Asset Browser, find `KeyboardMovement.uscript` under Scripts/Generated -5. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 5: Test It! - -1. Click **Play** in the toolbar -2. Press `W`, `A`, `S`, `D` to move the entity -3. Hold multiple keys (e.g., `W` + `D`) to move diagonally -4. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- Movement updates every frame based on current key states -- Diagonal movement combines offset vectors naturally -- Changing `moveSpeed` updates all directions at once - -⚠️ **Scene Scale Note:** If motion is too fast for your scene scale, drop `moveSpeed` to `0.05` and retest. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Adjustable Sprint -```swift -.getKeyState("lshift", as: "shiftPressed") -.setVariable("currentSpeed", to: 0.15) -.ifEqual("shiftPressed", to: true) { n in - n.setVariable("currentSpeed", to: 0.4) -} -// Use currentSpeed instead of moveSpeed when scaling direction -``` - -### Vertical Movement -```swift -.getKeyState("space", as: "upPressed") -.getKeyState("c", as: "downPressed") -.ifEqual("upPressed", to: true) { n in - n.addVec3("offset", simd_float3(x: 0, y: 0.2, z: 0), as: "offset") -} -.ifEqual("downPressed", to: true) { n in - n.addVec3("offset", simd_float3(x: 0, y: -0.2, z: 0), as: "offset") -} -``` - -### Normalize Diagonals -```swift -.normalizeVec3("offset", as: "offset") -.scaleVec3("offset", by: "moveSpeed", as: "offset") -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ Reading keyboard input with `getKeyState()` -✅ Building movement vectors from multiple keys -✅ Updating position every frame with `onUpdate()` -✅ Building and attaching scripts to entities -✅ Testing interactive input in Play mode - ---- - diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/03_Animation/02_AnimationStateSwitch.md b/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/03_Animation/02_AnimationStateSwitch.md deleted file mode 100644 index a43e42f11..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/03_Animation/02_AnimationStateSwitch.md +++ /dev/null @@ -1,265 +0,0 @@ -# Animation State Switch - Idle to Walk - -**What you'll learn:** -- Switching animations based on input -- Tracking current state to avoid restart flicker -- Using `playAnimation()` for looped clips -- Building and testing animations from Xcode - -**Time:** ~10 minutes - -**Prerequisites:** -- Untold Engine Studio open with an entity that has an **Animation Component** and two clips loaded (e.g., `idle`, `walk`) -- Access to Xcode via **Scripts: Open in Xcode** (toolbar button) - ---- - -## What We're Building - -An animation controller that: -1. Plays `idle` when no movement keys are pressed -2. Switches to `walk` when any WASD key is pressed -3. Only changes clips when the state actually changes - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `AnimationStateSwitch` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateAnimationStateSwitch(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateAnimationStateSwitch(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateAnimationStateSwitch) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateAnimationStateSwitch(to dir: URL) { - let script = buildScript(name: "AnimationStateSwitch") { s in - // Runs once when the entity starts - s.onStart() - .setVariable("currentAnim", to: "idle") - .playAnimation("idle", loop: true) - .log("AnimationStateSwitch ready - idle playing") - - // Runs every frame - s.onUpdate() - // Read movement keys - .getKeyState("w", as: "wPressed") - .getKeyState("a", as: "aPressed") - .getKeyState("s", as: "sPressed") - .getKeyState("d", as: "dPressed") - - // Determine if moving - .setVariable("isMoving", to: false) - .ifEqual("wPressed", to: true) { n in n.setVariable("isMoving", to: true) } - .ifEqual("aPressed", to: true) { n in n.setVariable("isMoving", to: true) } - .ifEqual("sPressed", to: true) { n in n.setVariable("isMoving", to: true) } - .ifEqual("dPressed", to: true) { n in n.setVariable("isMoving", to: true) } - - // Switch to walk when moving - .ifEqual("isMoving", to: true) { n in - n.ifCondition(lhs: .variableRef("currentAnim"), .notEqual, rhs: .string("walk")) { change in - change.playAnimation("walk", loop: true) - change.setVariable("currentAnim", to: "walk") - change.log("Switched to walk") - } - } - - // Switch back to idle when not moving - .ifEqual("isMoving", to: false) { n in - n.ifCondition(lhs: .variableRef("currentAnim"), .notEqual, rhs: .string("idle")) { change in - change.playAnimation("idle", loop: true) - change.setVariable("currentAnim", to: "idle") - change.log("Switched to idle") - } - } - } - - let outputPath = dir.appendingPathComponent("AnimationStateSwitch.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ AnimationStateSwitch.uscript") - } -} -``` - -### Understanding the Code - -**State tracking** - `currentAnim` stores what is playing -- Prevents restarting the same clip every frame -- Eliminates flicker when staying in the same state - -**Input gating** - `isMoving` becomes true if any WASD key is pressed -- One boolean drives all animation switching - -**`playAnimation()` only on change** - Uses a condition to switch -- Keeps transitions smooth -- Avoids repeating the same clip call - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ AnimationStateSwitch.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Add an entity to the scene. -3. In the Asset Library, double click on the animations you want to add to the entity, such as `idle` and `walk` -3. In the Inspector panel, click **Add Component** → **Script Component** -4. In the Asset Browser, find `AnimationStateSwitch.uscript` under Scripts/Generated -5. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 5: Test It! - -1. Click **Play** in the toolbar -2. With no input, the entity plays `idle` -3. Press any of `W`, `A`, `S`, `D` to switch to `walk` -4. Release the keys to return to `idle` -5. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- Idle is the default and fallback state -- Walk plays only while movement keys are down -- No flicker because the script checks before switching - -⚠️ **Name Matching:** Clip names are case-sensitive. If your clips are named differently (e.g., `Idle`, `Walking`), update the script strings. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Add a Run State with Shift -```swift -.getKeyState("lshift", as: "shiftPressed") -.ifEqual("shiftPressed", to: true) { n in - n.ifEqual("isMoving", to: true) { run in - run.ifCondition(lhs: .variableRef("currentAnim"), .notEqual, rhs: .string("run")) { change in - change.playAnimation("run", loop: true) - change.setVariable("currentAnim", to: "run") - } - } -} -``` - -### Add a Jump One-Shot -```swift -.getKeyState("space", as: "jumpPressed") -.ifEqual("jumpPressed", to: true) { n in - n.playAnimation("jump", loop: false) - n.setVariable("currentAnim", to: "jump") -} -``` - -### Reset to Idle After a Delay -```swift -.setVariable("idleTimer", to: 30.0) // half-second at ~60 FPS -.ifEqual("isMoving", to: false) { n in - n.addFloat("idleTimer", literal: -1.0, as: "idleTimer") - n.ifEqual("idleTimer", to: 0.0) { back in - back.playAnimation("idle", loop: true) - back.setVariable("currentAnim", to: "idle") - } -} -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ Switching animations based on player input -✅ Tracking the current clip to avoid restart flicker -✅ Using looped clips for idle/walk states -✅ Building, attaching, and testing animation controllers - ---- - diff --git a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/04_Physics/01_ApplyForce.md b/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/04_Physics/01_ApplyForce.md deleted file mode 100644 index ca5ba9cc7..000000000 --- a/website/versioned_docs/version-0.10.7/03-Game Development/04-Scripting-Experimental/03-Tutorials/04_Physics/01_ApplyForce.md +++ /dev/null @@ -1,232 +0,0 @@ -# Apply Force - Physics Push - -**What you'll learn:** -- Applying world-space forces with `applyWorldForce()` -- Controlling force direction and magnitude with variables -- Testing physics-driven motion from Xcode builds -- Building scripts in Xcode before playtesting - -**Time:** ~8 minutes - -**Prerequisites:** -- Untold Engine Studio open with an entity that has a Physics/Kinetic component -- Access to Xcode via **Scripts: Open in Xcode** (toolbar button) - ---- - -## What We're Building - -A physics push that: -1. Applies a continuous upward force -2. Uses variables for direction and magnitude -3. Shows how to tweak force strength between builds - ---- - -## Step 1: Create the Script - -1. Open **Untold Engine Studio** -2. In the toolbar, click **Scripts: Open in Xcode** (blue button) -3. Click the `+` button and enter the script name: `ApplyForce` -4. Click OK, then Xcode opens the Scripts package - -When the script is created: -- The source file is added to your project -- You edit the script in Xcode - -You'll see a template like this: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateApplyForce(to dir: URL) { - // Write your script here - } -} -``` -> This script is added to GenerateScripts, which is the entry point the engine uses to discover and generate USC scripts. - ---- - - -## Step 2: Wire Up the Script - -⚠️ IMPORTANT MANUAL STEP - -The editor created your script file, but you need to tell the build system to generate it. - -1. Click Scripts: Open in Xcode (blue button in toolbar) -2. In Xcode, open GenerateScripts.swift -3. Add your script to the main() function: - -```swift -@main -struct GenerateScripts { - static func main() { - print("🔨 Generating USC scripts...") - - let outputDir = URL(fileURLWithPath: "Generated/") - try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - - // Add this line: - generateApplyForce(to: outputDir) - - print("✅ All scripts generated in Generated/") - } -} -``` - -Now run the GenerateScripts target (`Cmd+R`) to generate the `.uscript` files. - - -MPORTANT NOTES: -The function name (e.g. generateApplyForce) MUST match the tutorial’s script -Only adjust that single line per tutorial -The structure, wording, and warning must remain consistent across all documents - ---- - - -## Step 3: Write the Script - -Replace the function with this complete script: - -```swift -import Foundation -import UntoldEngine -import simd - -extension GenerateScripts { - static func generateApplyForce(to dir: URL) { - let script = buildScript(name: "ApplyForce") { s in - // Runs once when the entity starts - s.onStart() - .setVariable("forceDir", to: simd_float3(x: 0, y: 1, z: 0)) - .setVariable("forceMagnitude", to: 0.2) - .log("ApplyForce ready") - - // Runs every frame - s.onUpdate() - .applyWorldForce( - direction: .variableRef("forceDir"), - magnitude: .variableRef("forceMagnitude") - ) - } - - let outputPath = dir.appendingPathComponent("ApplyForce.uscript") - try? saveUSCScript(script, to: outputPath) - print(" ✅ ApplyForce.uscript") - } -} -``` - -### Understanding the Code - -**`forceDir` + `forceMagnitude`** - Set the push direction and strength -- `(0, 1, 0)` pushes upward -- Reduce magnitude for gentle movement; increase for stronger lifts - -**`applyWorldForce()`** - Applies a force in world space -- Requires a Physics/Kinetic component on the entity -- Accumulates over time, so the object accelerates while the force runs - -**`onUpdate()`** - Applies force every frame -- Stop the push by setting magnitude to 0 or disabling the Script Component - ---- - -## Step 4: Build the Script - -1. In Xcode, press `Cmd+R` to run the GenerateScripts target and generate the `.uscript` (optional: `Cmd+B` first to verify the build). -2. Watch for the Xcode run output: - -``` -🔨 Generating USC scripts... - ✅ ApplyForce.uscript -✅ All scripts generated in Generated/ -``` - -**First build?** May take 30-60 seconds to download engine dependencies. Subsequent builds are much faster. - ---- - -## Step 5: Attach to an Entity - -1. Return to **Untold Engine Studio** -2. Add an entity to the scene. -3. In the Inspector panel, click **Add Component** → **Kinetic Component** -4. In the Inspector panel, click **Add Component** → **Script Component** -5. In the Asset Browser, find `ApplyForce.uscript` under Scripts/Generated -6. Double click on the `.uscript`. The script will be linked to the entity ---- - -## Step 6: Test It! - -1. Click **Play** in the toolbar -2. Watch the entity accelerate upward as the force is applied each frame -3. Open the **Console** view to confirm: - ``` - ApplyForce ready - ``` -4. Click **Stop** to exit Play mode - ---- - -## Understanding the Output - -- The entity accelerates while the force is active -- Direction is world-based; change `forceDir` to push sideways -- Lower gravity or heavier masses will change how fast it moves - -⚠️ **Physics Note:** Forces accumulate; if the object moves too fast, lower `forceMagnitude` or apply the force only on certain frames. - ---- - -## Modify and Experiment - -Try these changes to learn more: - -### Short Burst Instead of Continuous Force -```swift -s.onStart() - .setVariable("framesLeft", to: 60) // apply for 1 second at ~60 FPS - -s.onUpdate() - .ifGreater("framesLeft", than: 0) { n in - n.applyWorldForce(direction: .variableRef("forceDir"), - magnitude: .variableRef("forceMagnitude")) - n.addFloat("framesLeft", literal: -1, as: "framesLeft") - } -``` - -### Sideways Push -```swift -.setVariable("forceDir", to: simd_float3(x: 1, y: 0, z: 0)) // push along +X -``` - -### Switch to Impulse -```swift -s.onUpdate() - .applyLinearImpulse(direction: .variableRef("forceDir"), - magnitude: .variableRef("forceMagnitude")) -``` - -After making changes: -1. In Xcode, press `Cmd+R` to rerun the GenerateScripts target and regenerate the scripts (`Cmd+B` optional first). -2. Click **Reload** in the Script Component Inspector -3. Test in Play mode - ---- - -## What You Learned - -✅ Applying forces to physics-enabled entities -✅ Controlling direction and magnitude with variables -✅ Building and attaching physics scripts in the editor -✅ Testing physics responses in Play mode - ---- - diff --git a/website/versioned_docs/version-0.10.7/04-Engine Development/01-Overview.md b/website/versioned_docs/version-0.10.7/04-Engine Development/01-Overview.md deleted file mode 100644 index 670971d70..000000000 --- a/website/versioned_docs/version-0.10.7/04-Engine Development/01-Overview.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -id: engine-overview -title: Overview -sidebar_position: 1 ---- - -# Overview - -This section is for **engine developers and contributors** who want to work on the core of Untold Engine. - -It assumes familiarity with engine concepts and systems programming. - ---- - -## What You’ll Work On - -In this section, you’ll learn about: -- The engine architecture -- The ECS design -- Rendering systems -- Simulation and update flow -- Platform-specific layers - -This is where the **core behavior** of Untold Engine lives. - ---- - -## Installing the Engine for Development - -Engine development requires installing Untold Engine via the command line and working from source. - -Detailed installation steps are provided here: - - -1. Clone the Repository - -```bash -git clone https://github.com/untoldengine/UntoldEngine -cd UntoldEngine -open Package.swift -``` - -**How to Run** -1. Select the **DemoGame** scheme. -2. Download the [Demo Game Assets v1.0](https://github.com/untoldengine/UntoldEngine-Assets/releases/tag/v1) and place them in your Desktop folder. -3. Set the Scheme to **Demo Game**. Then set **My Mac** as the target device and hit **Run**. -4. Use **WASD** keys to move the player around. - - -![DemoGame](../images/demogame-noeditor.png) - - -You do not need Untold Engine Studio to work on the engine itself. - ---- - -## Engine Architecture - -Untold Engine is built around: -- A clear ECS model -- Explicit system update order -- Minimal hidden state -- Platform-specific rendering backends - -The architecture favors **clarity over abstraction**. - -A high-level breakdown is provided in: - -> **Engine Development → Architecture** - ---- - -## Rendering and Systems - -Rendering, physics, and other systems are implemented as: -- Independent modules -- Explicit update stages -- Predictable data flow - -Rendering is designed to expose low-level control while remaining approachable. - ---- - -## Who This Section Is Not For - -This section is **not** intended for: -- Game developers writing gameplay scripts -- Users who only want to use the editor -- Beginners to engine development - -If you want to make a game, see: - -> **Game Development → Overview** - diff --git a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingAsyncLoading.md b/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingAsyncLoading.md deleted file mode 100644 index c7312f5fb..000000000 --- a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingAsyncLoading.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -id: asyncloadingsystem -title: Async Loading System -sidebar_position: 9 ---- - -# Async USDZ Loading System - Usage Guide - -## Overview - -The async loading system allows you to load USDZ files with many models without blocking the main thread. The engine remains responsive during loading, and you can track progress for UI feedback. - -## Features - -✅ **Non-blocking loading** - Engine continues running while assets load -✅ **Progress tracking** - Monitor loading progress per entity or globally -✅ **Fallback meshes** - Automatically loads a cube if loading fails -✅ **Backward compatible** - Old synchronous APIs still work - ---- - -## Basic Usage - -### Load Entity Mesh Asynchronously - -```swift -// Create entity -let entityId = createEntity() - -// Load mesh asynchronously (fire and forget) -setEntityMeshAsync( - entityId: entityId, - filename: "large_model", - withExtension: "usdz" -) - -// Engine continues running - mesh appears when loaded -``` - -### With Completion Callback - -```swift -let entityId = createEntity() - -setEntityMeshAsync( - entityId: entityId, - filename: "large_model", - withExtension: "usdz" -) { success in - if success { - print("✅ Model loaded successfully!") - } else { - print("❌ Failed to load - fallback cube used") - } -} -``` - -### Load Entire Scene Asynchronously - -```swift -loadSceneAsync( - filename: "complex_scene", - withExtension: "usdz" -) { success in - if success { - print("Scene loaded with all entities") - } -} -``` - ---- - -## Progress Tracking - -### Check if Assets are Loading - -```swift -Task { - let isLoading = await AssetLoadingState.shared.isLoadingAny() - if isLoading { - print("Assets are currently loading...") - } -} -``` - -### Get Loading Count - -```swift -Task { - let count = await AssetLoadingState.shared.loadingCount() - print("Loading \(count) entities") -} -``` - -### Get Detailed Progress - -```swift -Task { - let (current, total) = await AssetLoadingState.shared.totalProgress() - let percentage = Float(current) / Float(total) * 100 - print("Progress: \(percentage)% (\(current)/\(total) meshes)") -} -``` - -### Get Progress Summary String - -```swift -Task { - let summary = await AssetLoadingState.shared.loadingSummary() - print(summary) // "Loading large_model.usdz: 5/20 meshes" -} -``` - -### Track Specific Entity - -```swift -Task { - if let progress = await AssetLoadingState.shared.getProgress(for: entityId) { - print("\(progress.filename): \(progress.currentMesh)/\(progress.totalMeshes)") - print("Percentage: \(progress.percentage * 100)%") - } -} -``` - ---- - -## Untold Editor Integration - -### Display Loading UI - -```swift -// In your editor's update loop or UI -Task { - if await AssetLoadingState.shared.isLoadingAny() { - let summary = await AssetLoadingState.shared.loadingSummary() - // Show loading indicator with summary text - showLoadingIndicator(text: summary) - } else { - // Hide loading indicator - hideLoadingIndicator() - } -} -``` - -### Progress Bar Example - -```swift -Task { - let allProgress = await AssetLoadingState.shared.getAllProgress() - - for progress in allProgress { - let percentage = progress.percentage * 100 - print("📦 \(progress.filename): \(Int(percentage))%") - - // Update UI progress bar - updateProgressBar( - id: progress.entityId, - filename: progress.filename, - percentage: percentage - ) - } -} -``` - ---- - -## Error Handling - -### Automatic Fallback - -When async loading fails, a fallback cube mesh is automatically loaded: - -```swift -setEntityMeshAsync( - entityId: entityId, - filename: "missing_file", - withExtension: "usdz" -) { success in - if !success { - // Entity now has a cube mesh as fallback - print("Loaded fallback cube") - } -} -``` - -### Manual Error Handling - -```swift -setEntityMeshAsync( - entityId: entityId, - filename: "model", - withExtension: "usdz" -) { success in - if !success { - // Handle error - entity has fallback cube - showErrorDialog("Failed to load model.usdz") - - // Optionally destroy entity or retry - // destroyEntity(entityId: entityId) - } -} -``` - ---- - -## Backward Compatibility - -The old synchronous APIs still work unchanged: - -```swift -// Old way (blocks main thread) -setEntityMesh( - entityId: entityId, - filename: "model", - withExtension: "usdz" -) - -// New way (non-blocking) -setEntityMeshAsync( - entityId: entityId, - filename: "model", - withExtension: "usdz" -) -``` - ---- - -## Advanced Usage - -### Loading Multiple Models - -```swift -let entity1 = createEntity() -let entity2 = createEntity() -let entity3 = createEntity() - -// All three load in parallel without blocking -setEntityMeshAsync(entityId: entity1, filename: "model1", withExtension: "usdz") -setEntityMeshAsync(entityId: entity2, filename: "model2", withExtension: "usdz") -setEntityMeshAsync(entityId: entity3, filename: "model3", withExtension: "usdz") - -// Track total progress -Task { - while await AssetLoadingState.shared.isLoadingAny() { - let summary = await AssetLoadingState.shared.loadingSummary() - print(summary) - try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds - } - print("All models loaded!") -} -``` - -### Coordinate System Conversion - -```swift -setEntityMeshAsync( - entityId: entityId, - filename: "blender_model", - withExtension: "usdz", - coordinateConversion: .forceZUpToYUp -) -``` - ---- - -## Implementation Details - -### What Runs on Background Thread - -- Reading USDZ file from disk -- Parsing MDL data structures -- Loading texture data -- Applying coordinate transformations - -### What Runs on Main Thread - -- Creating Metal resources (MTKMesh, MTLTexture) -- Registering ECS components -- Updating entity transforms -- Creating fallback meshes on error - -### Thread Safety - -All `AssetLoadingState` operations are thread-safe via Swift's actor model. You can safely query loading state from any thread. - ---- - -## Performance Tips - -1. **Prefer async for large files** (>20 models or >50MB) -2. **Use sync for small files** (1-5 models, <10MB) - less overhead -3. **Batch loads together** - loading multiple files in parallel is efficient -4. **Monitor progress** - use for UI feedback during long loads - ---- - -## Migration Guide - -### Before (Blocking) - -```swift -func loadGameAssets() { - let entity = createEntity() - setEntityMesh(entityId: entity, filename: "huge_scene", withExtension: "usdz") - // Engine was frozen here ❌ -} -``` - -### After (Non-blocking) - -```swift -func loadGameAssets() { - let entity = createEntity() - setEntityMeshAsync(entityId: entity, filename: "huge_scene", withExtension: "usdz") { - success in - if success { - print("Scene ready!") - } - } - // Engine continues running ✅ -} -``` - ---- - diff --git a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingGaussianSystem.md b/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingGaussianSystem.md deleted file mode 100644 index 63d87e5a8..000000000 --- a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingGaussianSystem.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: gaussiansystem -title: Gaussian System -sidebar_position: 3 ---- - -# Enabling Gaussian System in Untold Engine - -The Gaussian System in the Untold Engine is responsible for rendering Gaussian Splatting models. It enables you to visualize high-quality 3D reconstructions created from photogrammetry or neural rendering techniques, providing a modern approach to displaying complex 3D scenes. - -## How to Enable the Gaussian System - -### Step 1: Create an Entity - -Start by creating an entity that represents your Gaussian Splat object. - -```swift -let myEntity = createEntity() -``` ---- - -### Step 2: Link a Gaussian Splat to the Entity - -To display a Gaussian Splat model, load its .ply file and link it to the entity using setEntityGaussian. - -```swift -setEntityGaussian(entityId: myEntity, filename: "splat", withExtension: "ply") -``` - -Parameters: - -- entityId: The ID of the entity created earlier. -- filename: The name of the .ply file (without the extension). -- withExtension: The file extension, typically "ply". - -> Note: The Gaussian System renders point cloud data stored in the .ply format. Ensure your Gaussian Splat file is properly formatted and contains the necessary attributes (position, color, opacity, scale, rotation). - ---- - -### Running the Gaussian System - -Once everything is set up: - -1. Run the project. -2. Your Gaussian Splat model will appear in the game window. -3. If the model is not visible or appears incorrect, revisit the file path and format to ensure everything is loaded correctly. - diff --git a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingGeometryStreamingSystem.md b/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingGeometryStreamingSystem.md deleted file mode 100644 index a3304a48e..000000000 --- a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingGeometryStreamingSystem.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -id: geometrystreaminggsystem -title: Geometry Streaming System -sidebar_position: 13 ---- - -# Geometry Streaming System - -The Geometry Streaming System dynamically loads and unloads geometry based on the camera's proximity to objects. This system is essential for large-scale scenes where loading all geometry at once would exceed available memory or cause performance issues. - -## How It Works - -The streaming system monitors the distance between the camera and entities that have streaming enabled. Based on configurable radius values, the system automatically: - -1. **Loads geometry** when the camera moves within the streaming radius -2. **Keeps geometry loaded** while the camera remains between the streaming and unload radii -3. **Unloads geometry** when the camera moves beyond the unload radius - -This creates a "bubble" of loaded geometry around the camera that moves with it through the scene. - -## When to Use Geometry Streaming - -**Ideal for:** -- Large open-world environments with distant objects -- Scenes where not all objects are visible simultaneously -- Memory-constrained scenarios -- Games with large view distances (forests, cities, landscapes) - -**Not recommended for:** -- Small scenes where all objects fit comfortably in memory -- Objects that are always visible -- Dynamic objects that move frequently -- Critical gameplay objects that must always be loaded - -## Basic Usage - -Here's a simple example of enabling streaming for a single entity: - -```swift -private func setupStreaming(){ - let stadium = createEntity() - setEntityMeshAsync(entityId: stadium, filename: "stadium", withExtension: "usdz") { success in - if success { - - print("Scene loaded successfully") - - enableStreaming( - entityId: stadium, - streamingRadius: 250.0, // Load when within 250 units - unloadRadius: 350.0, // Unload when beyond 350 units - priority: 10 - ) - } - } -} -``` - -### Important Notes - -1. **Load mesh first**: Always call `setEntityMeshAsync()` before enabling streaming -2. **Use completion callback**: Enable streaming inside the completion callback to ensure the mesh is loaded -3. **Async loading**: The `setEntityMeshAsync()` function loads the mesh asynchronously, preventing frame drops - -## Parameters Explained - -### `streamingRadius` -The distance from the camera at which geometry will be loaded. -- Objects closer than this distance will have their geometry loaded -- Should be set based on your camera's view distance and scene requirements -- **Typical values**: 100-500 units depending on object size and importance - -### `unloadRadius` -The distance from the camera at which geometry will be unloaded. -- Must be **larger** than `streamingRadius` to create a buffer zone -- Prevents "thrashing" (rapid loading/unloading as camera moves near the boundary) -- **Recommended**: At least 50-100 units larger than `streamingRadius` - -### `priority` -Determines the loading order when multiple objects need to be streamed. -- Higher values = loaded first -- Lower values = loaded last -- **Range**: Typically 1-10, but can be any positive integer -- **Usage**: - - High priority (8-10): Important landmarks, gameplay-critical objects - - Medium priority (4-7): Standard environment objects - - Low priority (1-3): Background details, distant decorations - -## Radius Configuration Guidelines - -Choosing the right radius values is crucial for optimal performance: - -``` -Camera Position - | - |<-- streamingRadius (250) -->|<-- buffer zone -->|<-- unloadRadius (350) -->| - | - | Geometry LOADS here | Stays loaded | Geometry UNLOADS here -``` - -### Example Configurations - -**Small objects (trees, props):** -- `streamingRadius`: 150-250 units -- `unloadRadius`: 250-350 units -- Buffer: 100 units - -**Medium objects (buildings, vehicles):** -- `streamingRadius`: 250-400 units -- `unloadRadius`: 400-550 units -- Buffer: 150 units - -**Large objects (stadiums, mountains):** -- `streamingRadius`: 500-1000 units -- `unloadRadius`: 700-1300 units -- Buffer: 200-300 units - -## Combining with Other Systems - -Geometry streaming works seamlessly with LOD and Batching systems: - -- **LOD + Streaming**: Use LOD for quality management and streaming for memory management -- **Batching + Streaming**: Batches are automatically updated as geometry loads/unloads -- **All three together**: Optimal for large open-world scenes - -See the [Combining LOD, Batching, and Streaming](./UsingLOD-Batching-Streaming.md) guide for detailed examples. - -## Best Practices - -1. **Test radius values**: Start conservative and adjust based on performance metrics -2. **Monitor memory**: Use profiling tools to ensure streaming is reducing memory usage -3. **Priority assignment**: Reserve high priorities for gameplay-critical objects -4. **Buffer zones**: Always maintain adequate buffer between streaming and unload radii -5. **Camera speed**: Faster-moving cameras may need larger streaming radii to prevent pop-in -6. **Position before streaming**: Set entity transforms before enabling streaming - -## Common Issues - -### Objects Not Loading -- Ensure `streamingRadius` is large enough for your camera's viewing distance -- Check that the mesh was loaded successfully in the completion callback -- Verify the entity has been positioned in the scene - -### Geometry "Popping" In and Out -- Increase the buffer between `streamingRadius` and `unloadRadius` -- Consider using LOD to smooth transitions -- Adjust camera movement speed or increase radii - -### Performance Issues -- Too many objects loading simultaneously: Adjust priorities to stagger loading -- Streaming radius too large: Reduce radius to load fewer objects -- Use LOD to reduce complexity of loaded geometry diff --git a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingLOD-Batching-Streaming.md b/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingLOD-Batching-Streaming.md deleted file mode 100644 index 44cbcd17e..000000000 --- a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingLOD-Batching-Streaming.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -id: lodstaticbatchinstreaminggsystem -title: Lod + Static Batching + Streaming System -sidebar_position: 12 ---- - -# Combining LOD, Batching, and Geometry Streaming - -The Untold Engine provides three complementary systems for optimizing rendering performance: - -- **LOD (Level of Detail)**: Automatically switches between different mesh resolutions based on distance from the camera. Closer objects use high-detail meshes, while distant objects use simplified versions. -- **Static Batching**: Combines multiple static meshes with the same material into a single draw call, reducing CPU overhead. -- **Geometry Streaming**: Dynamically loads and unloads geometry based on proximity to the camera, keeping only nearby objects in memory. - -When used together, these systems provide powerful performance optimization: -- LOD reduces GPU load by rendering appropriate detail levels -- Batching reduces CPU overhead by minimizing draw calls -- Streaming reduces memory usage by only keeping nearby geometry loaded - -Before using the examples below, assume your scene has: -- A valid active camera (used for LOD switching and streaming distance checks) -- Entity transforms in world space before runtime optimization kicks in -- Mesh/material naming consistency across LOD levels (to avoid mismatched assets) -- A clear decision on whether setup happens procedurally at runtime or from a serialized scene file - -In practice, there are two common workflows: -- **Procedural setup**: You create entities and configure LOD/batching/streaming in code during scene initialization. -- **Scene deserialization**: You load a saved scene where components were already authored, then finalize runtime systems in completion callbacks. - -## LOD + Batching - -This combination is ideal for scenes with many static objects that remain loaded throughout the scene lifetime. The LOD system manages visual quality based on distance, while batching reduces draw calls for objects using the same material. - -**Key Points:** -- All entities must have their meshes loaded before calling `generateBatches()` -- Use the completion callback from `addLODLevels()` to ensure meshes are ready -- Enable batching and generate batches only after all entities are configured - -Why this order matters: batching relies on mesh/material data that is only guaranteed after async LOD loading completes. If `generateBatches()` runs too early, some entities may be missing from batches, causing inconsistent draw-call reduction. - -```swift -private func setupLODWithBatching() { - var loadedCount = 0 - let totalTrees = 20 - - for i in 0 ..< totalTrees { - let tree = createEntity() - setEntityName(entityId: tree, name: "Tree_\(i)") - - // Capture position values for the closure - let x = Float(i % 5) * 10.0 - let z = Float(i / 5) * 10.0 - - setEntityLodComponent(entityId: tree) - - addLODLevels(entityId: tree, levels: [ - (0, "tree_LOD0", "usdz", 50.0, 0.0), - (1, "tree_LOD1", "usdz", 100.0, 0.0), - (2, "tree_LOD2", "usdz", 200.0, 0.0), - ]) { success in - if success { - // Apply transform AFTER mesh is loaded - translateTo(entityId: tree, position: simd_float3(x, 0, z)) - setEntityStaticBatchComponent(entityId: tree) - } - - // Track completion - loadedCount += 1 - if loadedCount == totalTrees { - enableBatching(true) - generateBatches() - print("\(totalTrees) trees configured with LOD + Batching") - } - } - } - -} -``` - -For LOD + batching loaded from a scene file, the key is to defer batch generation until playSceneAt reports completion (after async mesh loading finishes): - -```swift -playSceneAt(url:url){ - enableBatching(true) - generateBatches() -} -``` - -## LOD + Batching + Streaming - -This combination is ideal for large open-world scenes where you have many objects spread across a large area. Streaming ensures only nearby geometry is loaded into memory, LOD manages visual quality, and batching reduces draw calls for loaded objects. - -**Key Points:** -- Enable streaming **after** the mesh is loaded and positioned -- Set `streamingRadius` larger than your farthest LOD distance to ensure smooth transitions -- Set `unloadRadius` larger than `streamingRadius` to provide a buffer zone -- Batching works with streamed geometry - batches are automatically updated as objects load/unload -- The order matters: LOD component → Load meshes → Transform → Enable streaming → Mark for batching - -This setup is best treated as a lifecycle: -1. Author LOD levels and streaming radii based on gameplay visibility needs. -2. Wait for asset load completion before enabling dependent systems. -3. Activate batching once entities are fully initialized, then let streaming maintain runtime memory pressure. - -**Radius Guidelines:** -- `streamingRadius`: Should be greater than your farthest LOD distance plus a buffer (e.g., if farthest LOD is 200, use 250) -- `unloadRadius`: Should be significantly larger than `streamingRadius` to avoid thrashing (e.g., 350 when streaming radius is 250) - -```swift -private func setupLODBatchingStreaming() { - var loadedCount = 0 - let totalTrees = 20 - - for i in 0 ..< totalTrees { - let tree = createEntity() - setEntityName(entityId: tree, name: "Tree_\(i)") - - // Capture position values for the closure - let x = Float(i % 5) * 10.0 - let z = Float(i / 5) * 10.0 - - // 1. Set LOD component FIRST - setEntityLodComponent(entityId: tree) - - // 2. Load LOD levels - addLODLevels(entityId: tree, levels: [ - (0, "tree_LOD0", "usdz", 50.0, 0.0), - (1, "tree_LOD1", "usdz", 100.0, 0.0), - (2, "tree_LOD2", "usdz", 200.0, 0.0), - (3, "tree_LOD3", "usdz", 300.0, 0.0), - ]) { success in - if success { - // 3. Apply transform AFTER mesh is loaded - translateTo(entityId: tree, position: simd_float3(x, 0, z)) - - // 4. Enable streaming AFTER mesh exists - // streamingRadius > farthest LOD distance (200) with buffer - // unloadRadius > streamingRadius with buffer - enableStreaming( - entityId: tree, - streamingRadius: 250.0, // Load when within 250 units - unloadRadius: 350.0, // Unload when beyond 350 units - priority: 10 - ) - - // 5. Mark for batching - setEntityStaticBatchComponent(entityId: tree) - } - - // Track completion - loadedCount += 1 - if loadedCount == totalTrees { - enableBatching(true) - generateBatches() - print("\(totalTrees) trees configured with LOD + Batching + Streaming") - } - } - } -} -``` - -When loading LOD + batching + streaming from a scene file, deserialization restores component state first, then your completion handler enables systems that depend on fully loaded geometry: - -```swift -playSceneAt(url: sceneURL) { - // Called after deserializeScene completion (async LOD/mesh loads done) - - enableBatching(true) - generateBatches() - - GeometryStreamingSystem.shared.enabled = true - print("Scene loaded with LOD + Batching + Streaming enabled") -} -``` - -## Best Practices - -### When to Use Each Combination - -**LOD Only:** -- Small scenes with few objects -- When objects are always visible and memory isn't a concern -- Dynamic objects that move frequently - -**LOD + Batching:** -- Medium-sized scenes with many static objects -- Objects share materials and remain loaded -- Memory usage is acceptable -- Example: Interior spaces, small outdoor areas - -**LOD + Batching + Streaming:** -- Large open-world scenes -- Many objects spread across large distances -- Memory optimization is critical -- Example: Forests, cities, large outdoor environments - -### Performance Tips - -1. **LOD Distances**: Set LOD transition distances based on your object size and visual importance. Smaller objects can transition earlier. - -2. **Batching Materials**: Entities must share the same material to be batched together. Group objects by material when possible. - -3. **Streaming Priorities**: Use higher priority values (e.g., 10) for important objects like landmarks, lower values (e.g., 1) for background details. - -4. **Testing**: Monitor frame rate and memory usage to fine-tune your radius values and LOD distances for your specific scene. - -5. **Completion Callbacks**: Always use the completion callback from `addLODLevels()` to ensure meshes are fully loaded before enabling other systems. diff --git a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingLODSystem.md b/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingLODSystem.md deleted file mode 100644 index 7af29fed7..000000000 --- a/website/versioned_docs/version-0.10.7/04-Engine Development/03-Engine Systems/UsingLODSystem.md +++ /dev/null @@ -1,466 +0,0 @@ ---- -id: lodsystem -title: LOD System -sidebar_position: 10 ---- - -# LOD (Level of Detail) System - Usage Guide - -The Untold Engine provides a flexible LOD system for optimizing rendering performance by displaying different mesh details based on camera distance. - -## Overview - -The LOD system allows you to: -- Add multiple levels of detail to any entity -- Automatically switch between LOD levels based on distance -- Customize distance thresholds for each LOD level -- Configure LOD behavior (bias, hysteresis, fade transitions) - -> **Choose Your Path:** You can set up LOD via the **Editor** (no code required) or **programmatically** in Swift. - ---- - -## Using the Editor - -### Adding LOD Support to an Entity - -1. **Select your entity** in the Scene Hierarchy -2. **Open the Inspector** and click **"Add Components"** -3. **Select "LOD Component"** from the list -4. An LOD Levels panel will appear in the Inspector - -### Adding LOD Levels - -1. **Select a model** from the Asset Browser (Models folder) -2. In the LOD Levels panel, click **"Add LOD Level"** -3. The selected model will be added as the next LOD level with a default distance: - - LOD0: 50 units - - LOD1: 100 units - - LOD2: 150 units, etc. - -### Adjusting LOD Distances - -1. Click the **distance value** next to any LOD level -2. Enter a new distance and press **Enter** -3. Objects will switch to this LOD when the camera is within this distance - -### Removing LOD Levels - -Click the **trash icon** next to any LOD level to remove it. - ---- - -## Using Code - -### Quick Start - -### Basic LOD Setup - -```swift -// Create entity -let tree = createEntity() - -// Add LOD component -setEntityLodComponent(entityId: tree) - -// Add LOD levels (from highest to lowest detail) -addLODLevel(entityId: tree, lodIndex: 0, fileName: "tree_LOD0", withExtension: "usdz", maxDistance: 50.0) -addLODLevel(entityId: tree, lodIndex: 1, fileName: "tree_LOD1", withExtension: "usdz", maxDistance: 100.0) -addLODLevel(entityId: tree, lodIndex: 2, fileName: "tree_LOD2", withExtension: "usdz", maxDistance: 200.0) -addLODLevel(entityId: tree, lodIndex: 3, fileName: "tree_LOD3", withExtension: "usdz", maxDistance: 400.0) -``` - -**How it works:** -- LOD0 (highest detail) renders when camera is < 50 units away -- LOD1 renders between 50-100 units -- LOD2 renders between 100-200 units -- LOD3 (lowest detail) renders beyond 200 units - -### Loading Multiple LOD Levels (Recommended) - -Use `addLODLevels` to load all LOD levels with a single completion handler. This is especially important when combining LOD with static batching: - -```swift -let tree = createEntity() -setEntityLodComponent(entityId: tree) - -// Load all LOD levels and wait for completion -addLODLevels(entityId: tree, levels: [ - (0, "tree_LOD0", "usdz", 50.0, 0.0), - (1, "tree_LOD1", "usdz", 100.0, 0.0), - (2, "tree_LOD2", "usdz", 200.0, 0.0), - (3, "tree_LOD3", "usdz", 400.0, 0.0) -]) { success in - if success { - print("All LOD levels loaded") - - // Apply transforms AFTER mesh is loaded - translateTo(entityId: tree, position: simd_float3(10, 0, 5)) - - // Apply static batching if necessary - } -} -``` - -> **Important:** When using LOD with async loading, apply transforms (`translateTo`, `rotateTo`, `scaleTo`) inside the completion handler. Transforms applied before the mesh loads may not take effect. - -### With Initial Mesh Loading - -You can also load an initial mesh synchronously before adding LOD levels: - -```swift -let tree = createEntity() - -// Load initial mesh synchronously (shows immediately) -setEntityMesh(entityId: tree, filename: "tree_LOD0", withExtension: "usdz") - -// Add LOD component -setEntityLodComponent(entityId: tree) - -// Add LOD levels (will replace initial mesh when ready) -addLODLevel(entityId: tree, lodIndex: 0, fileName: "tree_LOD0", withExtension: "usdz", maxDistance: 50.0) -addLODLevel(entityId: tree, lodIndex: 1, fileName: "tree_LOD1", withExtension: "usdz", maxDistance: 100.0) -addLODLevel(entityId: tree, lodIndex: 2, fileName: "tree_LOD2", withExtension: "usdz", maxDistance: 200.0) -addLODLevel(entityId: tree, lodIndex: 3, fileName: "tree_LOD3", withExtension: "usdz", maxDistance: 400.0) -``` - -### With Completion Handler - -Since `addLODLevel` loads meshes asynchronously, use the completion handler when you need to perform actions after loading completes: - -```swift -let tree = createEntity() -setEntityLodComponent(entityId: tree) - -// Chain completion handlers for sequential loading -addLODLevel(entityId: tree, lodIndex: 0, fileName: "tree_LOD0", withExtension: "usdz", maxDistance: 50.0) { success in - if success { - print("LOD0 loaded") - // Now it's safe to use the mesh data - } -} -``` - -## File Organization - -LOD files should be organized in subdirectories: - -``` -GameData/ -└── Models/ - ├── tree_LOD0/ - │ └── tree_LOD0.usdz - ├── tree_LOD1/ - │ └── tree_LOD1.usdz - ├── tree_LOD2/ - │ └── tree_LOD2.usdz - └── tree_LOD3/ - └── tree_LOD3.usdz -``` - -**Note:** Each LOD file should be in its own folder with the same name as the file (without extension). - -## API Reference - -### Core Functions - -#### `setEntityLodComponent(entityId:)` -Registers an LOD component on an entity. Call this before adding LOD levels. - -```swift -setEntityLodComponent(entityId: tree) -``` - -#### `addLODLevel(entityId:lodIndex:fileName:withExtension:maxDistance:completion:)` -Adds a single LOD level to an entity. - -**Parameters:** -- `entityId`: The entity to add LOD to -- `lodIndex`: LOD level index (0 = highest detail) -- `fileName`: Name of the mesh file (without extension) -- `withExtension`: File extension (e.g., "usdz") -- `maxDistance`: Maximum camera distance for this LOD -- `completion`: Optional callback when loading completes - -```swift -addLODLevel( - entityId: tree, - lodIndex: 0, - fileName: "tree_LOD0", - withExtension: "usdz", - maxDistance: 50.0 -) { success in - if success { - print("LOD0 loaded successfully") - } -} -``` - -#### `addLODLevels(entityId:levels:completion:)` -Adds multiple LOD levels with a single completion handler. Useful when you need to wait for all LOD levels to load. - -**Parameters:** -- `entityId`: The entity to add LOD levels to -- `levels`: Array of tuples: `(lodIndex, fileName, withExtension, maxDistance, screenPercentage)` -- `completion`: Called when ALL levels finish loading (true only if all succeeded) - -```swift -addLODLevels(entityId: tree, levels: [ - (0, "tree_LOD0", "usdz", 50.0, 0.0), - (1, "tree_LOD1", "usdz", 100.0, 0.0), - (2, "tree_LOD2", "usdz", 200.0, 0.0) -]) { success in - if success { - print("All LODs loaded") - } -} -``` - -#### `removeLODLevel(entityId:lodIndex:)` -Removes a specific LOD level from an entity. - -```swift -removeLODLevel(entityId: tree, lodIndex: 2) -``` - -#### `replaceLODLevel(entityId:lodIndex:fileName:withExtension:maxDistance:completion:)` -Replaces an existing LOD level with a new mesh. - -```swift -replaceLODLevel( - entityId: tree, - lodIndex: 1, - fileName: "tree_LOD1_new", - withExtension: "usdz", - maxDistance: 100.0 -) -``` - -#### `getLODLevelCount(entityId:) -> Int` -Returns the number of LOD levels for an entity. - -```swift -let count = getLODLevelCount(entityId: tree) -print("Entity has \(count) LOD levels") -``` - ---- - -## Advanced Usage - -### Custom Distance Thresholds - -Adjust distances based on your scene scale: - -```swift -// Small scene (indoor environment) -addLODLevel(entityId: prop, lodIndex: 0, fileName: "prop_LOD0", withExtension: "usdz", maxDistance: 10.0) -addLODLevel(entityId: prop, lodIndex: 1, fileName: "prop_LOD1", withExtension: "usdz", maxDistance: 20.0) - -// Large scene (outdoor landscape) -addLODLevel(entityId: mountain, lodIndex: 0, fileName: "mountain_LOD0", withExtension: "usdz", maxDistance: 500.0) -addLODLevel(entityId: mountain, lodIndex: 1, fileName: "mountain_LOD1", withExtension: "usdz", maxDistance: 1000.0) -addLODLevel(entityId: mountain, lodIndex: 2, fileName: "mountain_LOD2", withExtension: "usdz", maxDistance: 2000.0) -``` - -### LOD Configuration - -Configure global LOD behavior: - -```swift -// Adjust LOD bias (higher = switch to lower detail sooner) -LODConfig.shared.lodBias = 1.5 // Performance mode -LODConfig.shared.lodBias = 0.75 // Quality mode - -// Adjust hysteresis to prevent flickering -LODConfig.shared.hysteresis = 10.0 - -// Enable fade transitions between LODs - Not yet implemented -LODConfig.shared.enableFadeTransitions = true -LODConfig.shared.fadeTransitionTime = 0.5 // seconds -``` - -### Forced LOD Override - -Force a specific LOD level (useful for debugging): - -```swift -if let lodComponent = scene.get(component: LODComponent.self, for: tree) { - lodComponent.forcedLOD = 2 // Always show LOD2 - // lodComponent.forcedLOD = nil // Resume automatic LOD selection -} -``` - -### Programmatic LOD Management - -```swift -// Create entity with LOD component -let rock = createEntity() -setEntityLodComponent(entityId: rock) - -// Add LODs dynamically based on performance -let lodFiles = ["rock_LOD0", "rock_LOD1", "rock_LOD2"] -let distances: [Float] = [30.0, 60.0, 120.0] - -for (index, fileName) in lodFiles.enumerated() { - addLODLevel( - entityId: rock, - lodIndex: index, - fileName: fileName, - withExtension: "usdz", - maxDistance: distances[index] - ) -} - -// Check LOD count -let lodCount = getLODLevelCount(entityId: rock) -print("Rock has \(lodCount) LOD levels") - -// Remove highest detail LOD on low-end hardware -if isLowEndDevice { - removeLODLevel(entityId: rock, lodIndex: 0) -} -``` - ---- - -## Best Practices - -### Recommended LOD Counts -- **Small props**: 2-3 LODs -- **Characters**: 3-4 LODs -- **Vehicles**: 3-4 LODs -- **Buildings**: 4-5 LODs -- **Terrain**: 5-8 LODs - -### Polygon Reduction Guidelines -- **LOD0** (full detail): 100% polygons -- **LOD1**: ~50% polygon reduction -- **LOD2**: ~75% polygon reduction -- **LOD3**: ~90% polygon reduction or billboard - -### Distance Thresholds -Base distances on object importance and size: -- **Hero objects**: Longer high-detail distance -- **Background objects**: Shorter high-detail distance -- **Large objects**: Visible from farther away, need more LODs - -### Performance Tips -1. Always use async loading (`setEntityMeshAsync`) for better performance -2. Keep LOD0 for objects within 50 units of camera -3. Use billboards or impostors for very distant objects (LOD3+) -4. Test LOD transitions in-game to ensure smooth visual quality -5. Use `forcedLOD` during development to preview each LOD level - -## Troubleshooting - -### LODs Not Switching -- Verify LOD component is registered: `hasComponent(entityId: tree, componentType: LODComponent.self)` -- Check distance thresholds are set correctly -- Ensure camera has `CameraComponent` and is active - -### Visual Popping Between LODs -- Increase `LODConfig.shared.hysteresis` value -- Enable fade transitions: `LODConfig.shared.enableFadeTransitions = true` - not yet implemented -- Adjust LOD bias for smoother transitions - -### File Not Found Errors -- Verify file organization follows the subdirectory structure -- Check file names match exactly (case-sensitive) -- Ensure files are in the correct `GameData/Models/` path - -### Performance Issues -- Reduce number of LOD levels for less important objects -- Increase distance thresholds to switch LODs sooner -- Use LOD bias > 1.0 for performance mode - -## Example: Complete LOD Setup - -```swift -import UntoldEngine - -// Create multiple trees with LODs -var trees: [EntityID] = [] - -for i in 0..<10 { - let tree = createEntity() - setEntityName(entityId: tree, name: "Tree_\(i)") - - // Position trees - translateTo(entityId: tree, position: simd_float3(Float(i * 10), 0, 0)) - - // Add LOD component - setEntityLodComponent(entityId: tree) - - // Add 4 LOD levels - addLODLevel(entityId: tree, lodIndex: 0, fileName: "tree_LOD0", withExtension: "usdz", maxDistance: 50.0) - addLODLevel(entityId: tree, lodIndex: 1, fileName: "tree_LOD1", withExtension: "usdz", maxDistance: 100.0) - addLODLevel(entityId: tree, lodIndex: 2, fileName: "tree_LOD2", withExtension: "usdz", maxDistance: 200.0) - addLODLevel(entityId: tree, lodIndex: 3, fileName: "tree_LOD3", withExtension: "usdz", maxDistance: 400.0) - - trees.append(tree) -} - -// Configure LOD system for this scene -LODConfig.shared.lodBias = 1.2 // Slightly favor performance -LODConfig.shared.hysteresis = 8.0 // Prevent flickering -LODConfig.shared.enableFadeTransitions = true - -print("Created \(trees.count) trees with LOD support") -``` - -## Example: LOD with Static Batching - -When combining LOD with static batching, ensure transforms and batching setup happen **after** meshes are loaded: - -```swift -import UntoldEngine - -private func setupLODWithBatching() { - var loadedCount = 0 - let totalTrees = 20 - - for i in 0.. Note: If PBR textures (e.g., albedo, normal, roughness, metallic maps) are included, the rendering system will automatically use the appropriate PBR shader to render the model with realistic lighting and material properties. - -### Link a Gaussian Splat to the Entity - -To display a Gaussian Splat model, load its .ply file and link it to the entity using setEntityGaussian. - -```swift -setEntityGaussian(entityId: entity, filename: "splat", withExtension: "ply") -``` - -Parameters: - -- entityId: The ID of the entity created earlier. -- filename: The name of the .ply file (without the extension). -- withExtension: The file extension, typically "ply". - -> Note: The Gaussian System renders point cloud data stored in the .ply format. Ensure your Gaussian Splat file is properly formatted and contains the necessary attributes (position, color, opacity, scale, rotation). - -### Directional Light - -Use for sunlight or distant key lights. Orientation (rotation) defines its direction. - -```swift -let sun = createEntity() -createDirLight(entityId: sun) -``` -### Point Light - -Omni light that radiates equally in all directions from a position. - -```swift -let bulb = createEntity() -createPointLight(entityId: bulb) -``` - -### Spot Light - -Cone-shaped light with a position and direction. - -```swift -let spot = createEntity() -createSpotLight(entityId: spot) -``` - -### Area Light - -Rect/area emitter used to mimic panels/windows; position and orientation matter. - -```swift -let panel = createEntity() -createAreaLight(entityId: panel) -``` - -### Enable or Disable PostFX - -Toggle post-processing effects globally through the PostFX facade: - -```swift -PostFX.setEnabled(.colorGrading, false) - -PostFX.enableVignette(false) -``` - -### SSAO Controls - -SSAO is managed through a dedicated API because it affects render quality and pipelines: - -```swift -SSAO.setEnabled(true) -SSAO.setQuality(.high) -SSAO.setRadius(0.8) -SSAO.setBias(0.02) -SSAO.setIntensity(1.2) -``` - ---- - -# Animation - -### Load an Animation -Load the animation data for your model by providing the animation .usdc file and a name to reference the animation later. - -```swift -setEntityAnimations(entityId: redPlayer, filename: "running", withExtension: "usdc", name: "running") -``` - -### Set the Animation to play - -Trigger the animation by referencing its name. This will set the animation to play on the entity. - -```swift -changeAnimation(entityId: redPlayer, name: "running") -``` - -### Pause the animation (Optional) - -To pause the current animation, simply call the following function. The animation component will be paused for the current entity. - -```swift -pauseAnimationComponent(entityId: redPlayer, isPaused: true) -``` - ---- - -# Physics - -### Enable Physics on the Entity - -Activate the physics simulation for your entity using the setEntityKinetics function. This function prepares the entity for movement and dynamic interaction. - -```swift -setEntityKinetics(entityId: redPlayer) -``` ---- - -### Configure Physics Properties -You can customize the entity’s physics behavior by defining its mass and gravity scale: - -- Mass: Determines the force needed to move the object. Heavier objects require more force. -- Gravity Scale: Controls how strongly gravity affects the entity (default is 0.0). - -```swift -setMass(entityId: redPlayer, mass: 0.5) -setGravityScale(entityId: redPlayer, gravityScale: 1.0) -``` - -### Apply Forces (Optional) -You can apply a custom force to the entity for dynamic movement. This is useful for simulating actions like jumps or pushes. - -```swift -applyForce(entityId: redPlayer, force: simd_float3(0.0, 0.0, 5.0)) -``` - -> Note: Forces are applied per frame. To avoid unintended behavior, only apply forces when necessary. - -### Use the Steering System -For advanced movement behaviors, leverage the Steering System to steer entities toward or away from targets. This system automatically calculates the required forces. - -Example: Steering Toward a Position - -```swift -steerTo(entityId: redPlayer, targetPosition: simd_float3(0.0, 0.0, 5.0), maxSpeed: 2.0, deltaTime: deltaTime) -``` - -### Additional Steering Functions - -The Steering System includes other useful behaviors, such as: - -- steerAway() -- steerPursuit() -- followPath() - -These functions simplify complex movement patterns, making them easy to implement. - ---- - -# Components - -### Register Components - -Components define the behavior or attributes of an entity. Use registerComponent to add a component to an entity. - -```swift -let entity = createEntity() - -registerComponent(entityId: entity, componentType: RenderComponent.self) -``` - -### Create Custom Component - -Here’s an example of a simple custom component for a soccer player’s dribbling behavior: - -```swift -public class DribblinComponent: Component { - public required init() {} - var maxSpeed: Float = 5.0 - var kickSpeed: Float = 15.0 - var direction: simd_float3 = .zero -} -``` - -> ⚠️ Note: Components should not include functions or game logic. Keep them as pure data containers. - -### Attaching a Component to an Entity - -Once you’ve defined a component, you attach it to an entity in your scene: - -```swift -let player = createEntity(name: "player") - -// Attach DribblinComponent to the entity -registerComponent(entityId: player, componentType: DribblinComponent.self) -``` - -# Systems - -### Create Custom System - -If you’ve created a **custom component**, you’ll usually also want to create a **custom system** to make it do something. -Components store the data, but systems are where the behavior lives. - -The engine automatically calls systems every frame. - -Here’s a simple system that works with the `DribblinComponent` we defined earlier: - -```swift -public func dribblingSystemUpdate(deltaTime: Float) { - // 1. Get the ID of the DribblinComponent - let customId = getComponentId(for: DribblinComponent.self) - - // 2. Query all entities that have this component - let entities = queryEntitiesWithComponentIds([customId], in: scene) - - // 3. Loop through each entity and update its data - for entity in entities { - guard let dribblingComponent = scene.get(component: DribblinComponent.self, for: entity) else { - continue - } - - // Example logic: move player in the dribbling direction - dribblingComponent.direction = simd_normalize(dribblingComponent.direction) - let displacement = dribblingComponent.direction * dribblingComponent.maxSpeed * deltaTime - - if let transform = scene.get(component: LocalTransformComponent.self, for: entity) { - transform.position += displacement - } - } -} -``` - -### Registering the Custom System -All custom systems must be registered during initialization so the engine knows to run them every frame: - -```swift -registerCustomSystem(dribblingSystemUpdate) -``` diff --git a/website/versioned_docs/version-0.10.7/07-Contributor/ContributionGuidelines.md b/website/versioned_docs/version-0.10.7/07-Contributor/ContributionGuidelines.md deleted file mode 100644 index 5f52ebeb9..000000000 --- a/website/versioned_docs/version-0.10.7/07-Contributor/ContributionGuidelines.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -id: contributorguidelines -title: Contributor Guidelines -sidebar_position: 1 ---- - -# Contributing Guidelines - -Thank you for your interest in contributing! - -The vision for Untold Engine is to continually shape a 3D engine that is **stable, performant and developer-friendly**. -As maintainer, my focus is on **performance, testing, quality control, and API design**. -Contributors are encouraged to expand features, fix bugs, improve documentation, and enhance usability — always keeping the vision in mind. - ---- - -## Maintainer Responsibilities - -The Untold Engine is guided by a clear vision: To be a stable, performant, and developer-friendly 3D engine that empowers creativity, removes friction, and makes game development feel effortless. - -## Guiding Principles - -To achieve this vision, we follow these principles: - -- The engine strives to remain stable and crash-free. -- The codebase is backed by unit tests. -- We profile continuously to prevent regressions (visual and performance). -- The API must remain clear and user-friendly. -- We always think about the developer first—removing friction so they can focus on their games. - -As the maintainer, my primary focus is to ensure the project stays true to this vision. - -### What I Focus On -- **Performance** → keeping the renderer and systems lean, efficient, and optimized for Apple hardware. -- **Testing & Stability** → maintaining a reliable codebase with proper testing practices. -- **Quality Control** → reviewing PRs for clarity, readability, and adherence to coding standards. -- **API Design** → ensuring that the engine’s API remains logical, intuitive, and consistent. - -### What Contributors Are Encouraged to Focus On -- **Features** → adding or improving systems, tools, and workflows. -- **Bug Fixes** → addressing open issues and fixing edge cases. -- **Documentation** → clarifying how things work and providing examples. -- **Editor & Usability** → enhancing the UI, workflows, and overall developer experience. - -### Decision Making -All contributions are welcome, but acceptance will be guided by the project’s vision and the priorities above. -PRs that align with clarity, performance, or creativity — while keeping the engine stable and simple — are more likely to be accepted. - -These guidelines aren’t here to block you, but to make sure every contribution keeps the engine stable, clear, and useful for everyone. - -## Pull Request Guidelines - -- **One feature or bug fix per PR** - Each Pull Request should focus on a single feature, bug fix, or documentation improvement. - This keeps the history clean and makes it easier to track down issues later. - -- **Commit hygiene** - - Keep commits meaningful (avoid "misc changes" or "fix stuff"). - - Squash commits if needed, but do not mix unrelated features in the same commit. - - If your PR touches multiple files, make sure they all relate to the same feature or fix. - -✅ Example: -- Good: *“Add PhysicsSystem with gravity integration”* -- Bad: *“Added PhysicsSystem + fixed rendering bug + updated docs”* - ---- - -## Required Contributions for New System Support - -For **new systems or major features**, your PR must include: - -- **Unit Tests** → Validate functionality and cover edge cases. -- **How-To Guide** → A step-by-step markdown guide explaining how to use the system. - -This ensures new features are stable, documented, and accessible to all users. - -👉 Note: For small fixes or incremental features, a How-To is not required. - ---- - -### How-To Guide Format - -Your guide must follow this structure: - -1. Introduction - -- Briefly explain the feature and its purpose. -- Describe what problem it solves or what value it adds. - -2. Why Use It - -- Provide real-world examples or scenarios where the feature is useful. -- Explain the benefits of using the feature in these contexts. - -3. Step-by-Step Implementation - -- Break down the setup process into clear, actionable steps. -- Include well-commented code snippets for each step. - -4. What Happens Behind the Scenes - -- Provide technical insights into how the system works internally (if relevant). -- Explain any significant impacts on performance or functionality. - -5. Tips and Best Practices - -- Share advice for effective usage. -- Highlight common pitfalls and how to avoid them. - -6. Running the Feature - -- Explain how to test or interact with the feature after setup. - ---- - -### Additional Notes - ---- - -## Questions & Discussions - -To keep communication clear and accessible for everyone: - -- 💡 Use **[GitHub Discussions](https://github.com/untoldengine/UntoldEngine/discussions)** for feature proposals, ideas, or general questions. -- 🐞 Use **[GitHub Issues](https://github.com/untoldengine/UntoldEngine/issues)** for bugs or concrete tasks that need tracking. - -This way, conversations stay organized, visible to the community, and future contributors can benefit from past discussions. - ---- - -Thank you for contributing to the Untold Engine! Following these guidelines will ensure that your work aligns with the project's goals and provides value to users. diff --git a/website/versioned_docs/version-0.10.7/07-Contributor/Formatting.md b/website/versioned_docs/version-0.10.7/07-Contributor/Formatting.md deleted file mode 100644 index 6d45d6214..000000000 --- a/website/versioned_docs/version-0.10.7/07-Contributor/Formatting.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -id: formatting -title: Formatting -sidebar_position: 2 ---- - -# Formatting and Linting - -To maintain a consistent code style across the Untold Engine repo, we use [SwiftFormat](https://github.com/nicklockwood/SwiftFormat). SwiftFormat is a code formatter for Swift that helps enforce Swift style conventions and keep the codebase clean. If you don't have SwiftFormat installed, see the **Installing SwiftFormat** section below. - -## Quick Formatting & Linting - -Navigate to the root directory of Untold Engine and then run the commands below: - -### 🔍 Lint files - -To lint (check) all Swift files without making changes: - -```bash -swiftformat --lint . --swiftversion 5.8 --reporter github-actions-log -``` - -Or, using the Makefile: - -```bash -make lint -``` - -This command runs the same lint configuration as our GitHub Actions workflow and pre-commit hook, ensuring consistent results locally and in CI. - -### ✅ Formatting Files - -To format files: - -```bash -swiftformat --swiftversion 5.8 . -``` - -Alternatively, you can use the Makefile shortcut: - -```bash -make format -``` - -💡 Tip -If the pre-commit hook blocks your commit due to formatting issues, simply run: - -```bash -make format -``` - -then re-stage your changes and try committing again. - -You can bypass the hook temporarily (not recommended) with: - -```bash -git commit --no-verify -``` - -## Installing SwiftFormat - -The simplest way to install SwiftFormat is through the command line. - -1. Install SwiftFormat Using Homebrew: Open the terminal and run the following command: - -```bash -brew install swiftformat -``` -2. Verify Installation: After installation, verify that SwiftFormat is installed correctly by running: - -```bash -swiftformat --version -``` -This should print the installed version of SwiftFormat. - -### Using SwiftFormat - -Format a Single File - -To format a specific Swift file: - -1. Open the terminal and navigate to your project directory. - -2. Run the following command: - -```bash -swiftformat path/to/YourFile.swift -``` -This will format YourFile.swift according to the default rules. - -### Format Multiple Files - -To format all Swift files in your project: - -1. Navigate to your project directory in the terminal. - -2. Run the following command: - -```bash -swiftformat . -``` - -This will recursively format all Swift files in the current directory and its subdirectories. - - diff --git a/website/versioned_docs/version-0.10.7/07-Contributor/versioning.md b/website/versioned_docs/version-0.10.7/07-Contributor/versioning.md deleted file mode 100644 index ddb6c7a9d..000000000 --- a/website/versioned_docs/version-0.10.7/07-Contributor/versioning.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -id: versioning -title: Versioning -sidebar_position: 3 ---- - -# Versioning - -To help us identity the purpose of your commits, make sure to use the following tags in your commit messages. The tags will also automatically increment the the current version when pushed to github. - -- [Patch] - Bug fixes (eg. v1.0.0 -> v1.0.1) -- [Feature] - For new feature that don't break compatibility (eg. v1.0.0 -> v1.1.0) -- [API Change] - For major changes that are not API backward compatible (eg. v1.0.0 -> v2.0.0) - - - diff --git a/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetBrowserScripts.png b/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetBrowserScripts.png deleted file mode 100644 index 0b523ad75..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetBrowserScripts.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetBrowserView.png b/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetBrowserView.png deleted file mode 100644 index c5cbe12fe..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetBrowserView.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetLibraryLoupe.png b/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetLibraryLoupe.png deleted file mode 100644 index 9a4a0c8e3..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/EditorAssetLibraryLoupe.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/EditorMainShot-alt.png b/website/versioned_docs/version-0.10.7/images/Editor/EditorMainShot-alt.png deleted file mode 100644 index 2ba7138d1..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/EditorMainShot-alt.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_empty.png b/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_empty.png deleted file mode 100644 index 8dbc28800..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_empty.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_model_import.png b/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_model_import.png deleted file mode 100644 index 310df2be5..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_model_import.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_model_viewport.png b/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_model_viewport.png deleted file mode 100644 index 321d25529..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_model_viewport.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_name.png b/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_name.png deleted file mode 100644 index 09e711f6b..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/Editor/Editor_scene_name.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/UntoldEngineGrid.png b/website/versioned_docs/version-0.10.7/images/UntoldEngineGrid.png deleted file mode 100644 index d6790ef10..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/UntoldEngineGrid.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/addcomponent.png b/website/versioned_docs/version-0.10.7/images/addcomponent.png deleted file mode 100644 index cc2995fe7..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/addcomponent.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/animationexportblenderStandalone.png b/website/versioned_docs/version-0.10.7/images/animationexportblenderStandalone.png deleted file mode 100644 index 77666c05e..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/animationexportblenderStandalone.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/asset-browser-folder.png b/website/versioned_docs/version-0.10.7/images/asset-browser-folder.png deleted file mode 100644 index 6f0406ced..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/asset-browser-folder.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/engine-consolelog.png b/website/versioned_docs/version-0.10.7/images/engine-consolelog.png deleted file mode 100644 index 767e792c4..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/engine-consolelog.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/engine-editor-startup.png b/website/versioned_docs/version-0.10.7/images/engine-editor-startup.png deleted file mode 100644 index 52519a1ed..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/engine-editor-startup.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/engine-gizmo.png b/website/versioned_docs/version-0.10.7/images/engine-gizmo.png deleted file mode 100644 index 0faa36698..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/engine-gizmo.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/engine-inspector.png b/website/versioned_docs/version-0.10.7/images/engine-inspector.png deleted file mode 100644 index bd778f251..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/engine-inspector.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/engine-scenegraph.png b/website/versioned_docs/version-0.10.7/images/engine-scenegraph.png deleted file mode 100644 index 51e6e51a0..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/engine-scenegraph.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/howtoexport.png b/website/versioned_docs/version-0.10.7/images/howtoexport.png deleted file mode 100644 index ccd4079c6..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/howtoexport.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/howtoexportStandalone.png b/website/versioned_docs/version-0.10.7/images/howtoexportStandalone.png deleted file mode 100644 index 0858b82a5..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/howtoexportStandalone.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/inspector.png b/website/versioned_docs/version-0.10.7/images/inspector.png deleted file mode 100644 index 0504eb302..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/inspector.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/render-component.png b/website/versioned_docs/version-0.10.7/images/render-component.png deleted file mode 100644 index d15b36bf2..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/render-component.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/scripts_in_asset_browser.png b/website/versioned_docs/version-0.10.7/images/scripts_in_asset_browser.png deleted file mode 100644 index 002e8072b..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/scripts_in_asset_browser.png and /dev/null differ diff --git a/website/versioned_docs/version-0.10.7/images/untoldenginewhite.png b/website/versioned_docs/version-0.10.7/images/untoldenginewhite.png deleted file mode 100644 index b7440b8c1..000000000 Binary files a/website/versioned_docs/version-0.10.7/images/untoldenginewhite.png and /dev/null differ diff --git a/website/versions.json b/website/versions.json deleted file mode 100644 index 9d6be56fe..000000000 --- a/website/versions.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "0.10.10", - "0.10.7", - "0.10.6" -]