13 KiB
Cursebreaker Unity Parser
A high-performance Rust library for parsing Unity project files (.unity scenes, .prefab prefabs, and .asset files) with automatic MonoBehaviour component discovery and ECS integration.
⚠️ Work in Progress: This library is under active development. APIs may change.
Features
Core Parsing
- Multi-format support: Parse
.unityscenes,.prefabprefabs, and.assetfiles - ECS integration: Automatically builds Sparsey ECS worlds from scenes
- Type-safe: Strong typing for Unity primitives (Vector3, Quaternion, Color, etc.)
- Fast: Efficient YAML parsing with minimal allocations
Component System
- Derive macro:
#[derive(UnityComponent)]for automatic component parsing - Auto-registration: Components register themselves via inventory
- GUID resolution: Automatically resolves MonoBehaviour script GUIDs to class names
- Prefab resolution: Resolves prefab GUIDs for nested prefab references
- Type filtering: Selectively parse only the components you need
Advanced Features
- Prefab instantiation: Clone and modify prefab instances (basic support)
- Reference resolution: FileID → Entity mapping
- Regex filtering: Parse only files matching specific patterns
- Transform hierarchies: Parent-child relationships preserved
- Memory efficient: 128-bit GUIDs stored as u128 (16 bytes vs ~56 bytes for String)
Installation
Add to your Cargo.toml:
[dependencies]
unity_parser = { path = "unity-parser" }
sparsey = "0.13" # For ECS queries
Quick Start
Parse a Unity Scene
use unity_parser::UnityFile;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = UnityFile::from_path("Scene.unity")?;
match file {
UnityFile::Scene(scene) => {
println!("Scene with {} entities", scene.entity_map.len());
// Query transforms
let transforms = scene.world.borrow::<unity_parser::Transform>();
for (file_id, entity) in &scene.entity_map {
if let Some(transform) = transforms.get(*entity) {
println!("Entity {} at {:?}", file_id, transform.local_position());
}
}
}
UnityFile::Prefab(prefab) => {
println!("Prefab with {} documents", prefab.documents.len());
}
UnityFile::Asset(asset) => {
println!("Asset with {} documents", asset.documents.len());
}
}
Ok(())
}
Parse a Prefab
use unity_parser::UnityFile;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = UnityFile::from_path("Player.prefab")?;
if let UnityFile::Prefab(prefab) = file {
// Find all GameObjects
let game_objects = prefab.get_documents_by_class("GameObject");
for doc in game_objects {
if let Some(mapping) = doc.as_mapping() {
if let Some(name) = mapping.get("m_Name").and_then(|v| v.as_str()) {
println!("GameObject: {}", name);
}
}
}
// Find documents by type ID
let transforms = prefab.get_documents_by_type(4); // Transform = type 4
println!("Found {} Transforms", transforms.len());
}
Ok(())
}
Define Custom Components
⚠️ Note: The derive macro currently has namespace issues and may not compile. Manual implementation is recommended until fixed.
use unity_parser::{UnityComponent, ComponentContext};
use serde_yaml::Mapping;
#[derive(Debug, Clone)]
pub struct PlaySFX {
pub volume: f64,
pub start_time: f64,
pub end_time: f64,
pub is_loop: bool,
}
// Manual implementation (recommended until macro is fixed)
impl UnityComponent for PlaySFX {
fn parse(yaml: &Mapping, _ctx: &ComponentContext) -> Option<Self> {
Some(Self {
volume: unity_parser::yaml_helpers::get_f64(yaml, "volume").unwrap_or(1.0),
start_time: unity_parser::yaml_helpers::get_f64(yaml, "startTime").unwrap_or(0.0),
end_time: unity_parser::yaml_helpers::get_f64(yaml, "endTime").unwrap_or(0.0),
is_loop: unity_parser::yaml_helpers::get_bool(yaml, "isLoop").unwrap_or(false),
})
}
}
GUID Resolution
The parser automatically resolves Unity MonoBehaviour GUIDs to class names:
Unity Scene File Rust Code
───────────────── ─────────
MonoBehaviour: Custom component
m_Script: in ECS World
guid: 091c537... ──────────>
volume: 1.0
isLoop: 0
How It Works
- Scan: Parser scans Unity project's
Assets/for*.cs.metafiles - Build Map: Extracts GUIDs from
.metafiles - Extract Class: Parses
.csfiles to getclass Name : MonoBehaviour - Resolve: Maps GUID → Class Name → Registered Component
- Parse: Automatically parses MonoBehaviour YAML into components
The GUID resolver builds automatically when parsing scenes if a Unity project root is detected.
Type Filtering
Parse only specific Unity types and MonoBehaviours for better performance:
use unity_parser::{TypeFilter, parse_unity_file_filtered};
use std::collections::HashSet;
// Parse only Transforms and GameObjects
let mut types = HashSet::new();
types.insert("Transform".to_string());
types.insert("GameObject".to_string());
let filter = TypeFilter::with_unity_types(types);
let file = parse_unity_file_filtered(
Path::new("Scene.unity"),
None, // No regex filter
Some(&filter)
)?;
Regex Path Filtering
Parse only files matching specific patterns:
use regex::Regex;
use unity_parser::parse_unity_file_filtered;
// Only parse production scenes
let filter = Regex::new(r"Assets/Scenes/Production/")?;
let scene = parse_unity_file_filtered(
Path::new("Assets/Scenes/Production/Level1.unity"),
Some(&filter),
None
)?;
Supported Unity Types
Built-in Components
- ✅ GameObject
- ✅ Transform
- ✅ RectTransform
- ✅ PrefabInstance
Value Types
- ✅ Vector2, Vector3
- ✅ Quaternion
- ✅ Color
- ✅ FileID, GUID
- ✅ ExternalRef, FileRef
Custom Components
- ✅ Any
MonoBehaviourvia manualUnityComponentimplementation - ⚠️
#[derive(UnityComponent)]macro (has namespace bugs, not recommended)
Architecture
Component Flow
Unity Scene File
↓
Raw YAML Documents
↓
┌─────────────────────┐
│ GUID Resolution │ ← .meta files + .cs files
│ (MonoBehaviour) │ (Script GUID → Class Name)
└─────────────────────┘
↓
┌─────────────────────┐
│ Prefab GUID Res. │ ← .prefab.meta files
│ (Nested Prefabs) │ (Prefab GUID → Prefab Path)
└─────────────────────┘
↓
┌─────────────────────┐
│ Component Registry │ ← UnityComponent trait impls
│ (inventory) │
└─────────────────────┘
↓
┌─────────────────────┐
│ ECS World │ ← Sparsey entities & components
│ (Transform, etc) │
└─────────────────────┘
Memory Efficiency
GUID Storage:
- Old approach:
String(24 bytes stack + 32 bytes heap = 56 bytes) - Current:
u128(16 bytes on stack) - 3.5x memory reduction for GUID storage
GUID Comparison:
- Old: O(n) string comparison (32 characters)
- New: O(1) integer comparison
- Significant speedup for HashMap lookups
Project Structure
cursebreaker-parser-rust/
├── unity-parser/ # Main library crate
│ ├── src/
│ │ ├── ecs/ # ECS world building
│ │ │ └── builder.rs
│ │ ├── model/ # UnityFile, Scene, Prefab, Asset
│ │ │ └── mod.rs
│ │ ├── parser/ # YAML parsing & GUID resolution
│ │ │ ├── guid_resolver.rs # Script GUID → Class Name
│ │ │ ├── prefab_guid_resolver.rs # Prefab GUID → Path
│ │ │ ├── meta.rs # .meta file parsing
│ │ │ ├── yaml.rs # YAML document splitting
│ │ │ └── mod.rs
│ │ ├── project/ # ⚠️ OUTDATED - needs refactoring
│ │ │ └── mod.rs
│ │ ├── types/ # Unity types & components
│ │ │ ├── unity_types/
│ │ │ │ ├── game_object.rs
│ │ │ │ ├── transform.rs
│ │ │ │ ├── prefab_instance.rs
│ │ │ │ └── mod.rs
│ │ │ ├── component.rs # UnityComponent trait & helpers
│ │ │ ├── guid.rs # 128-bit GUID type
│ │ │ ├── ids.rs # FileID, LocalID
│ │ │ ├── reference.rs # UnityReference enum
│ │ │ ├── type_filter.rs # TypeFilter for selective parsing
│ │ │ ├── values.rs # Vector3, Quaternion, Color, etc.
│ │ │ └── mod.rs
│ │ ├── error.rs # Error types
│ │ ├── macros.rs
│ │ ├── property/
│ │ └── lib.rs
│ ├── examples/
│ │ ├── basic_parsing.rs
│ │ ├── custom_component.rs
│ │ ├── ecs_integration.rs
│ │ ├── find_playsfx.rs
│ │ ├── parse_resources.rs
│ │ └── parse_resource_prefabs.rs
│ ├── tests/
│ └── Cargo.toml
├── unity-parser-macros/ # Proc macro crate (⚠️ has bugs)
│ ├── src/
│ │ └── lib.rs
│ └── Cargo.toml
├── Cargo.toml # Workspace config
└── README.md
Known Issues
Critical Issues
-
unity-parser/src/project/mod.rsis OUTDATED- Built for old architecture before
UnityFileenum refactor - References non-existent
UnityDocumenttype (should beRawDocument) - Module is disabled in lib.rs until refactored
- Built for old architecture before
-
Derive macro namespace mismatch
unity-parser-macrosusesunity_parsernamespace- Actual crate name is
unity_parser::(underscore, not hyphen) - Manual
UnityComponentimplementation recommended
-
Placeholder values in Cargo.toml
- Author and repository fields need updating
Minor Issues
- Disabled example/test files may reference outdated APIs
- Some examples may have incorrect YAML access patterns
Running Examples
# Parse basic Unity file
cargo run --example basic_parsing
# Custom component parsing (requires Unity project)
cargo run --example custom_component
# ECS integration showcase
cargo run --example ecs_integration
# Find PlaySFX components (requires CBAssets project)
cargo run --example find_playsfx
Testing
# Unit tests
cargo test --lib
# Integration tests
cargo test --test integration_tests
# All tests
cargo test
Roadmap
Completed
- ✅ Phase 1: GUID Resolution (Script GUID → Class Name)
- ✅ Phase 2: MonoBehaviour Parser
- ✅ Phase 3: Prefab GUID Resolution
- ✅ Type filtering for selective parsing
- ✅ Regex path filtering
- ✅ Basic prefab instantiation
In Progress / Needs Work
- 🔧 Refactor
projectmodule for new architecture - 🔧 Update disabled example/test files
- 🔧 Fix example code YAML access patterns
Future Enhancements
- Full prefab modification system
- Persistent GUID cache for instant loading
- Watch mode for live Unity project monitoring
- More built-in Unity component types
- Better error messages with line numbers
- Parallel processing support
- Cross-platform path handling improvements
Contributing
Contributions welcome! Areas needing help:
- Documentation: API docs, more examples, tutorials
- Testing: Integration tests with real Unity projects
- Performance: Optimize YAML parsing, parallel processing
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Acknowledgments
- Unity Technologies: For the YAML-based file format
- Sparsey: ECS library for component storage
- serde_yaml: YAML parsing foundation
- inventory: Compile-time component registration