404 lines
13 KiB
Markdown
404 lines
13 KiB
Markdown
# 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.
|
|
|
|
[](https://www.rust-lang.org/)
|
|
[](LICENSE)
|
|
|
|
**⚠️ Work in Progress**: This library is under active development. APIs may change.
|
|
|
|
## Features
|
|
|
|
### Core Parsing
|
|
- **Multi-format support**: Parse `.unity` scenes, `.prefab` prefabs, and `.asset` files
|
|
- **ECS integration**: Automatically builds [Sparsey](https://github.com/LechintanTudor/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](https://github.com/dtolnay/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`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
unity_parser = { path = "unity-parser" }
|
|
sparsey = "0.13" # For ECS queries
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### Parse a Unity Scene
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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
|
|
|
|
1. **Scan**: Parser scans Unity project's `Assets/` for `*.cs.meta` files
|
|
2. **Build Map**: Extracts GUIDs from `.meta` files
|
|
3. **Extract Class**: Parses `.cs` files to get `class Name : MonoBehaviour`
|
|
4. **Resolve**: Maps GUID → Class Name → Registered Component
|
|
5. **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:
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
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 `MonoBehaviour` via manual `UnityComponent` implementation
|
|
- ⚠️ `#[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
|
|
1. **`unity-parser/src/project/mod.rs` is OUTDATED**
|
|
- Built for old architecture before `UnityFile` enum refactor
|
|
- References non-existent `UnityDocument` type (should be `RawDocument`)
|
|
- Module is disabled in lib.rs until refactored
|
|
|
|
2. **Derive macro namespace mismatch**
|
|
- `unity-parser-macros` uses `unity_parser` namespace
|
|
- Actual crate name is `unity_parser::` (underscore, not hyphen)
|
|
- Manual `UnityComponent` implementation recommended
|
|
|
|
3. **Placeholder values in Cargo.toml**
|
|
- Author and repository fields need updating
|
|
|
|
### Minor Issues
|
|
1. Disabled example/test files may reference outdated APIs
|
|
2. Some examples may have incorrect YAML access patterns
|
|
|
|
## Running Examples
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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 `project` module 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](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
- MIT license ([LICENSE-MIT](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
|