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
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
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