Merge 09654c7996 into f6656c1256
This commit is contained in:
commit
3644e980bc
|
|
@ -0,0 +1 @@
|
|||
raw
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Unity3D Enhanced Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
---
|
||||
name: unity3d-enhanced
|
||||
description: Advanced Unity3D development guidance covering common pitfalls, API migrations, and best practices for both 2D and 3D projects. Use when working with Unity projects, especially for debugging build errors, setting up project configurations, handling Unity API changes, or troubleshooting runtime exceptions.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Unity3D Enhanced Development Guide
|
||||
|
||||
This skill provides expert guidance for Unity3D development based on real-world lessons learned from both 2D and 3D Unity projects. It helps you avoid common pitfalls, debug complex errors, and follow best practices.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when working with:
|
||||
- Unity build errors or runtime exceptions
|
||||
- Project configuration (Input System, URP, Physics)
|
||||
- Asset references, GUIDs, and meta files
|
||||
- Unity API migrations between versions
|
||||
- Physics setup (Rigidbody, triggers, collisions)
|
||||
- Imported models and camera setups
|
||||
- Performance optimization
|
||||
- Version control with Unity projects
|
||||
|
||||
## How to Use This Skill
|
||||
|
||||
To solve Unity development issues:
|
||||
|
||||
1. **Identify the issue category** from your error or task
|
||||
2. **Run diagnostic scripts** from the `scripts/` directory (optional but recommended):
|
||||
- `scripts/guid_validator.py` - Validate all GUIDs, find duplicates and invalid formats
|
||||
- `scripts/meta_file_checker.py` - Check meta file synchronization with git
|
||||
- `scripts/project_health_check.py` - Comprehensive project configuration check
|
||||
3. **Load the appropriate reference file** from the `reference/` directory:
|
||||
- `reference/guid-system.md` - GUID errors, meta files, asset references, prefabs
|
||||
- `reference/input-systems.md` - Input System setup, API migration, common errors
|
||||
- `reference/physics-system.md` - Rigidbody, triggers vs collisions, fast-moving objects
|
||||
- `reference/urp-setup.md` - Universal Render Pipeline, camera configuration
|
||||
- `reference/common-errors.md` - Catalog of common Unity errors and solutions
|
||||
- `reference/project-setup.md` - Project setup checklist, workflows, best practices
|
||||
4. **Follow the specific instructions** in that file to diagnose and fix the issue
|
||||
5. **Check examples** in the `examples/` directory for quick reference:
|
||||
- `examples/quick-fixes.md` - Common issues with one-line fixes
|
||||
|
||||
If the issue doesn't match any category, search the reference files for keywords from your error message, or ask for more context.
|
||||
|
||||
## Workflow
|
||||
|
||||
### For Build/Runtime Errors
|
||||
|
||||
1. **Identify error message** from Unity Console or build output
|
||||
2. **Run diagnostic script** (optional): `python scripts/guid_validator.py <project_path>`
|
||||
3. **Search keywords** in `reference/common-errors.md`
|
||||
4. **Apply solution** from the matching error pattern
|
||||
5. **Verify fix** by rebuilding or running the project
|
||||
|
||||
### For Project Setup
|
||||
|
||||
1. **Run health check** (optional): `python scripts/project_health_check.py <project_path>`
|
||||
2. **Review checklist** in `reference/project-setup.md`
|
||||
3. **Configure settings** before writing code
|
||||
4. **Verify configuration** matches your requirements
|
||||
5. **Test in Play mode** to ensure settings work
|
||||
|
||||
### For API Migrations
|
||||
|
||||
1. **Check Unity version** you're migrating to
|
||||
2. **Review relevant API changes** in reference files
|
||||
3. **Update code** following the migration examples
|
||||
4. **Test thoroughly** after each change
|
||||
|
||||
### For Performance Issues
|
||||
|
||||
1. **Identify bottleneck** (Physics? Rendering? Input?)
|
||||
2. **Load relevant reference** (e.g., `physics-system.md` for physics)
|
||||
3. **Apply optimization strategies** from best practices section
|
||||
4. **Profile with Unity Profiler** to verify improvements
|
||||
|
||||
## Diagnostic Scripts
|
||||
|
||||
The skill includes Python scripts for automated project validation:
|
||||
|
||||
### `scripts/guid_validator.py`
|
||||
Validates GUIDs in Unity projects:
|
||||
- Checks for invalid GUID formats (non-hexadecimal characters)
|
||||
- Detects duplicate GUIDs across meta files
|
||||
- Finds orphaned meta files without corresponding assets
|
||||
- Identifies missing meta files
|
||||
|
||||
**Usage**: `python scripts/guid_validator.py <project_path> [--fix-orphaned]`
|
||||
|
||||
### `scripts/meta_file_checker.py`
|
||||
Ensures meta files are synchronized with version control:
|
||||
- Detects staged assets without meta files
|
||||
- Finds orphaned meta files in git
|
||||
- Validates git workflow for Unity assets
|
||||
|
||||
**Usage**: `python scripts/meta_file_checker.py <project_path> --check-git`
|
||||
|
||||
### `scripts/project_health_check.py`
|
||||
Comprehensive project configuration check:
|
||||
- Validates Input System configuration
|
||||
- Checks URP setup
|
||||
- Verifies version control settings
|
||||
- Validates .gitignore configuration
|
||||
- Checks physics settings
|
||||
|
||||
**Usage**: `python scripts/project_health_check.py <project_path>`
|
||||
|
||||
## Key Unity Concepts
|
||||
|
||||
### GUID System
|
||||
Unity uses 32-character hexadecimal GUIDs to track assets. Never manually create GUIDs—always copy from `.meta` files. Keep meta files in version control.
|
||||
→ See `reference/guid-system.md` for details
|
||||
→ Use `scripts/guid_validator.py` to validate GUIDs
|
||||
|
||||
### Input Systems
|
||||
Unity has legacy and new Input Systems. Mixing them causes errors. Configure `activeInputHandler` correctly before writing input code.
|
||||
→ See `reference/input-systems.md` for details
|
||||
→ Use `scripts/project_health_check.py` to verify configuration
|
||||
|
||||
### Physics
|
||||
Understand when to use triggers vs collisions. Use triggers for fast-moving projectiles to avoid tunneling.
|
||||
→ See `reference/physics-system.md` for details
|
||||
|
||||
### URP
|
||||
Universal Render Pipeline requires additional camera setup. Always create cameras via Unity Editor when using URP.
|
||||
→ See `reference/urp-setup.md` for details
|
||||
→ Use `scripts/project_health_check.py` to verify URP setup
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Most Common Errors
|
||||
|
||||
| Error Pattern | Quick Fix | Details |
|
||||
|--------------|-----------|---------|
|
||||
| GUID extraction failed | Copy correct GUID from `.meta` file | `guid-system.md` |
|
||||
| Input System exception | Set `activeInputHandler: 2` | `input-systems.md` |
|
||||
| Fast bullets miss targets | Use triggers instead of collisions | `physics-system.md` |
|
||||
| Camera data is null (URP) | Set `m_RendererIndex: 0` | `urp-setup.md` |
|
||||
| NullReferenceException | Assign SerializeField in Inspector | `common-errors.md` |
|
||||
|
||||
### Unity API Changes by Version
|
||||
|
||||
**Unity 2023.1+**
|
||||
- `Rigidbody.velocity` → `Rigidbody.linearVelocity`
|
||||
|
||||
**Unity 2019.1+**
|
||||
- New Input System package available
|
||||
- Can use legacy, new, or both systems
|
||||
|
||||
→ See reference files for complete migration guides
|
||||
|
||||
## Keywords
|
||||
|
||||
Unity, Unity3D, GUID, meta files, Input System, Rigidbody, physics, triggers, collisions, URP, Universal Render Pipeline, prefabs, scenes, API migration, version control, build errors, runtime errors, NullReferenceException, InvalidOperationException
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Unity Documentation
|
||||
- [Input System Manual](https://docs.unity3d.com/Packages/com.unity.inputsystem@latest)
|
||||
- [URP Documentation](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest)
|
||||
- [Physics Best Practices](https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity6.html)
|
||||
|
||||
### Lessons Learned From
|
||||
|
||||
This skill is based on real-world lessons from:
|
||||
- **2D Unity Projects** (Unity 2022.3+ with URP)
|
||||
- Scene bootstrapping and prefab setup
|
||||
- Input system compatibility
|
||||
- Resources folder patterns
|
||||
- Component references
|
||||
|
||||
- **3D Unity Projects** (Unity 2023.1+)
|
||||
- VR development
|
||||
- Physics-based gameplay
|
||||
- Model imports and orientation
|
||||
- Fast-moving projectile detection
|
||||
|
||||
---
|
||||
|
||||
*Based on Unity 2022.3+ (URP) and Unity 2023.1+ projects.*
|
||||
*Always verify API compatibility with your Unity version.*
|
||||
|
|
@ -0,0 +1,500 @@
|
|||
# Unity Quick Fixes Reference
|
||||
|
||||
This guide provides quick, copy-paste solutions for common Unity problems. For detailed explanations, see the reference files.
|
||||
|
||||
---
|
||||
|
||||
## Input System Issues
|
||||
|
||||
### Fix: Input System Exception
|
||||
|
||||
**Error**: `InvalidOperationException: You are trying to read Input using the UnityEngine.Input class...`
|
||||
|
||||
**One-line fix**: Change in `ProjectSettings/ProjectSettings.asset`:
|
||||
```yaml
|
||||
activeInputHandler: 2 # Enable both input systems
|
||||
```
|
||||
|
||||
**Via Editor**: Edit → Project Settings → Player → Active Input Handling → **Both** → Restart Unity
|
||||
|
||||
→ Details: `reference/input-systems.md`
|
||||
|
||||
---
|
||||
|
||||
## GUID Errors
|
||||
|
||||
### Fix: Invalid GUID in Prefab
|
||||
|
||||
**Error**: `Could not extract GUID in text file Assets/Prefabs/MyPrefab.prefab`
|
||||
|
||||
**Quick fix**:
|
||||
```bash
|
||||
# 1. Find correct GUID
|
||||
grep "^guid:" Assets/Scripts/MyScript.cs.meta
|
||||
|
||||
# 2. Replace in prefab file
|
||||
# Old: guid: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
# New: guid: 6004172616168c346951d6f4cddd83da (from step 1)
|
||||
```
|
||||
|
||||
→ Details: `reference/guid-system.md`
|
||||
|
||||
### Fix: Orphaned Meta Files
|
||||
|
||||
**Error**: `Broken PPtr [guid]...`
|
||||
|
||||
**Quick fix**:
|
||||
```bash
|
||||
# Find and remove orphaned .meta files
|
||||
for meta in Assets/**/*.meta; do
|
||||
[ -f "${meta%.meta}" ] || git rm "$meta"
|
||||
done
|
||||
```
|
||||
|
||||
→ Details: `reference/guid-system.md`
|
||||
|
||||
---
|
||||
|
||||
## URP Issues
|
||||
|
||||
### Fix: Camera Data is Null
|
||||
|
||||
**Error**: `Camera data is null. Please ensure the render pipeline is correctly initialized...`
|
||||
|
||||
**Quick fix**: Change in `.unity` scene file (search for camera):
|
||||
```yaml
|
||||
m_RendererIndex: 0 # Changed from -1
|
||||
```
|
||||
|
||||
**Or create new camera**: GameObject → Camera (Unity handles setup automatically)
|
||||
|
||||
→ Details: `reference/urp-setup.md`
|
||||
|
||||
### Fix: Pink Materials
|
||||
|
||||
**Symptom**: Materials appear bright pink/magenta
|
||||
|
||||
**Quick fix**: Edit → Render Pipeline → URP → **Upgrade Project Materials to URP Materials**
|
||||
|
||||
**Or manually**: Change shader from `Standard` to `Universal Render Pipeline/Lit`
|
||||
|
||||
→ Details: `reference/urp-setup.md`
|
||||
|
||||
---
|
||||
|
||||
## Physics Issues
|
||||
|
||||
### Fix: Bullets Missing Targets
|
||||
|
||||
**Problem**: Fast bullets pass through colliders
|
||||
|
||||
**Quick fix**: Use **triggers** on targets:
|
||||
```csharp
|
||||
// Target script
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.gameObject.layer == bulletLayer)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Setup**:
|
||||
- Target: `IsTrigger = true`, needs Rigidbody
|
||||
- Bullet: Solid collider, Rigidbody with velocity
|
||||
|
||||
→ Details: `reference/physics-system.md`
|
||||
|
||||
### Fix: Triggers Not Detecting
|
||||
|
||||
**Problem**: `OnTriggerEnter` never called
|
||||
|
||||
**Quick fix checklist**:
|
||||
- [ ] At least one object has Rigidbody
|
||||
- [ ] Collider has `IsTrigger = true`
|
||||
- [ ] Layer collision matrix enabled (Edit → Project Settings → Physics)
|
||||
- [ ] Method signature exact: `void OnTriggerEnter(Collider other)`
|
||||
|
||||
→ Details: `reference/physics-system.md`
|
||||
|
||||
### Fix: Rigidbody Velocity (Unity 2023.1+)
|
||||
|
||||
**Warning**: `'Rigidbody.velocity' is obsolete...`
|
||||
|
||||
**Quick fix**: Find & Replace
|
||||
```csharp
|
||||
// Find: rb.velocity
|
||||
// Replace: rb.linearVelocity
|
||||
```
|
||||
|
||||
→ Details: `reference/physics-system.md`
|
||||
|
||||
---
|
||||
|
||||
## NullReferenceException Fixes
|
||||
|
||||
### Fix: Unassigned SerializeField
|
||||
|
||||
**Error**: `NullReferenceException` at line using SerializeField
|
||||
|
||||
**Quick fix**: Add null check
|
||||
```csharp
|
||||
[SerializeField] private Transform target;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
Debug.LogError($"{name}: Target not assigned!");
|
||||
return;
|
||||
}
|
||||
// Use target safely
|
||||
}
|
||||
```
|
||||
|
||||
**Better**: Use `OnValidate` to catch at edit-time
|
||||
```csharp
|
||||
private void OnValidate()
|
||||
{
|
||||
if (target == null)
|
||||
Debug.LogWarning($"{name}: Target not assigned!");
|
||||
}
|
||||
```
|
||||
|
||||
→ Details: `reference/common-errors.md`
|
||||
|
||||
### Fix: GetComponent Returns Null
|
||||
|
||||
**Quick fix**: Use `TryGetComponent`
|
||||
```csharp
|
||||
// ❌ Unsafe
|
||||
Rigidbody rb = GetComponent<Rigidbody>();
|
||||
rb.linearVelocity = Vector3.zero; // Crash if no Rigidbody!
|
||||
|
||||
// ✓ Safe
|
||||
if (TryGetComponent<Rigidbody>(out var rb))
|
||||
{
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
}
|
||||
```
|
||||
|
||||
→ Details: `reference/common-errors.md`
|
||||
|
||||
---
|
||||
|
||||
## Resources Loading
|
||||
|
||||
### Fix: Resources.Load Returns Null
|
||||
|
||||
**Problem**: File exists but Resources.Load returns null
|
||||
|
||||
**Quick fix**: Move file to Resources folder
|
||||
```
|
||||
Assets/
|
||||
Resources/ ← Must be named exactly "Resources"
|
||||
Data/
|
||||
config.json ← Your file here
|
||||
```
|
||||
|
||||
**Load without extension**:
|
||||
```csharp
|
||||
// File at: Assets/Resources/Data/config.json
|
||||
TextAsset data = Resources.Load<TextAsset>("Data/config");
|
||||
```
|
||||
|
||||
→ Details: `reference/common-errors.md`
|
||||
|
||||
---
|
||||
|
||||
## Compiler Warnings
|
||||
|
||||
### Fix: CS0414 (Field Never Used)
|
||||
|
||||
**Warning**: `Field 'MyClass.field' is assigned but its value is never used`
|
||||
|
||||
**Quick fix**: Actually use the field
|
||||
```csharp
|
||||
[SerializeField] private float speed = 5f;
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Use it!
|
||||
transform.position += transform.forward * speed * Time.deltaTime;
|
||||
}
|
||||
```
|
||||
|
||||
→ Details: `reference/common-errors.md`
|
||||
|
||||
---
|
||||
|
||||
## Git and Meta Files
|
||||
|
||||
### Quick Commands
|
||||
|
||||
```bash
|
||||
# Check for orphaned .meta files
|
||||
for meta in Assets/**/*.meta; do
|
||||
[ -f "${meta%.meta}" ] || echo "Orphaned: $meta"
|
||||
done
|
||||
|
||||
# Find assets missing .meta files
|
||||
find Assets -type f ! -name "*.meta" ! -path "*/.*" | while read file; do
|
||||
[ -f "$file.meta" ] || echo "Missing .meta: $file"
|
||||
done
|
||||
|
||||
# Stage asset and meta together
|
||||
git add Assets/NewScript.cs Assets/NewScript.cs.meta
|
||||
|
||||
# Remove deleted asset's meta
|
||||
git rm Assets/DeletedScript.cs.meta
|
||||
```
|
||||
|
||||
→ Details: `reference/guid-system.md`
|
||||
|
||||
---
|
||||
|
||||
## Code Snippets
|
||||
|
||||
### Null-Safe GetComponent
|
||||
|
||||
```csharp
|
||||
// Safe component access pattern
|
||||
if (TryGetComponent<T>(out var component))
|
||||
{
|
||||
// Use component
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"{name} missing {typeof(T)} component!");
|
||||
}
|
||||
```
|
||||
|
||||
### Input System Device Check
|
||||
|
||||
```csharp
|
||||
// New Input System - always null-check devices
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
private Keyboard keyboard;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (keyboard == null)
|
||||
{
|
||||
keyboard = Keyboard.current;
|
||||
if (keyboard == null) return;
|
||||
}
|
||||
|
||||
if (keyboard.spaceKey.wasPressedThisFrame)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Object Pooling Pattern
|
||||
|
||||
```csharp
|
||||
public class ObjectPool : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject prefab;
|
||||
private Queue<GameObject> pool = new Queue<GameObject>();
|
||||
|
||||
public GameObject Get()
|
||||
{
|
||||
if (pool.Count > 0)
|
||||
{
|
||||
GameObject obj = pool.Dequeue();
|
||||
obj.SetActive(true);
|
||||
return obj;
|
||||
}
|
||||
return Instantiate(prefab);
|
||||
}
|
||||
|
||||
public void Return(GameObject obj)
|
||||
{
|
||||
obj.SetActive(false);
|
||||
pool.Enqueue(obj);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Trigger Detection with Layers
|
||||
|
||||
```csharp
|
||||
[SerializeField] private LayerMask targetLayers;
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Check if other object is in target layers
|
||||
if (((1 << other.gameObject.layer) & targetLayers) != 0)
|
||||
{
|
||||
// Handle trigger
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### URP Camera Creation
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
void CreateCamera()
|
||||
{
|
||||
GameObject camObj = new GameObject("Main Camera");
|
||||
Camera cam = camObj.AddComponent<Camera>();
|
||||
|
||||
// Required for URP
|
||||
UniversalAdditionalCameraData cameraData =
|
||||
camObj.AddComponent<UniversalAdditionalCameraData>();
|
||||
|
||||
camObj.tag = "MainCamera";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Settings Quick Checks
|
||||
|
||||
### Input System Configuration
|
||||
|
||||
```bash
|
||||
# Check current setting
|
||||
grep "activeInputHandler:" ProjectSettings/ProjectSettings.asset
|
||||
|
||||
# Should be:
|
||||
# activeInputHandler: 0 (Legacy only)
|
||||
# activeInputHandler: 1 (New only)
|
||||
# activeInputHandler: 2 (Both - recommended)
|
||||
```
|
||||
|
||||
### URP Verification
|
||||
|
||||
```bash
|
||||
# Check if URP asset assigned
|
||||
grep "m_CustomRenderPipeline:" ProjectSettings/GraphicsSettings.asset
|
||||
|
||||
# Should reference URP asset, not "m_CustomRenderPipeline: {fileID: 0}"
|
||||
```
|
||||
|
||||
### Meta Files Validation
|
||||
|
||||
```bash
|
||||
# Check project configured for text serialization
|
||||
grep "m_ExternalVersionControlSupport:" ProjectSettings/EditorSettings.asset
|
||||
|
||||
# Should be: m_ExternalVersionControlSupport: Visible Meta Files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Emergency Troubleshooting
|
||||
|
||||
### Nuclear Options (Use with Caution!)
|
||||
|
||||
**Clear Library** (forces reimport of all assets):
|
||||
```bash
|
||||
# Close Unity first!
|
||||
rm -rf Library/
|
||||
# Reopen Unity - will regenerate Library/
|
||||
```
|
||||
|
||||
**Reimport All Assets**:
|
||||
- Assets → Reimport All
|
||||
- Use when: Meta files corrupted, import settings broken
|
||||
|
||||
**Reset Project Settings**:
|
||||
- Manually delete `ProjectSettings/` (backup first!)
|
||||
- Unity regenerates default settings
|
||||
|
||||
**Note**: Always backup before using these!
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Quick Fixes
|
||||
|
||||
### Android
|
||||
|
||||
**Fix: Missing SDK/NDK**
|
||||
- Edit → Preferences → External Tools → Browse to Android SDK/NDK paths
|
||||
|
||||
**Fix: API Level Too Low**
|
||||
- Player Settings → Android → Minimum API Level → 24 (Android 7.0)
|
||||
|
||||
### iOS
|
||||
|
||||
**Fix: Bundle Identifier Invalid**
|
||||
- Player Settings → iOS → Bundle Identifier → `com.company.appname`
|
||||
|
||||
**Fix: Missing Camera Permission**
|
||||
- Player Settings → iOS → Camera Usage Description → "Required for AR features"
|
||||
|
||||
---
|
||||
|
||||
## Performance Quick Wins
|
||||
|
||||
```csharp
|
||||
// Cache transforms
|
||||
private Transform myTransform;
|
||||
void Awake() { myTransform = transform; }
|
||||
|
||||
// Use CompareTag instead of ==
|
||||
if (other.CompareTag("Player")) // Faster
|
||||
|
||||
// Disable cameras you're not using
|
||||
camera.enabled = false;
|
||||
|
||||
// Use object pooling for frequently spawned objects
|
||||
// (see Object Pooling Pattern above)
|
||||
|
||||
// Set Rigidbodies to sleep when idle
|
||||
rb.Sleep();
|
||||
|
||||
// Use layer collision matrix
|
||||
// Edit → Project Settings → Physics → Layer Collision Matrix
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Diagnostic Commands
|
||||
|
||||
```bash
|
||||
# Find all invalid GUIDs
|
||||
grep -Ern "guid: [^0-9a-f]" Assets/
|
||||
|
||||
# List all cameras in scenes
|
||||
grep -n "m_Name: Main Camera" Assets/Scenes/*.unity
|
||||
|
||||
# Find all Resources folders
|
||||
find Assets -type d -name "Resources"
|
||||
|
||||
# Check Unity version
|
||||
grep "m_EditorVersion:" ProjectSettings/ProjectVersion.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When All Else Fails
|
||||
|
||||
1. **Read the error carefully** - the answer is often in the message
|
||||
2. **Check the line number** - exact location of the problem
|
||||
3. **Google the exact error** - others have likely solved it
|
||||
4. **Unity Forums / Stack Overflow** - active communities
|
||||
5. **Check Unity documentation** - official API reference
|
||||
6. **Review recent changes** - git diff to see what changed
|
||||
7. **Start fresh** - create minimal reproduction case
|
||||
|
||||
---
|
||||
|
||||
## Reference File Index
|
||||
|
||||
| Issue Category | Reference File |
|
||||
|---------------|---------------|
|
||||
| GUIDs, meta files, asset references | `reference/guid-system.md` |
|
||||
| Input System, API migration | `reference/input-systems.md` |
|
||||
| Rigidbody, triggers, collisions | `reference/physics-system.md` |
|
||||
| URP, cameras, rendering | `reference/urp-setup.md` |
|
||||
| Common error catalog | `reference/common-errors.md` |
|
||||
| Project setup, workflows | `reference/project-setup.md` |
|
||||
|
|
@ -0,0 +1,487 @@
|
|||
# Common Unity Errors Catalog
|
||||
|
||||
## Overview
|
||||
|
||||
This document catalogs common Unity errors with their causes, solutions, and prevention strategies. Search by error message or keyword to quickly find solutions.
|
||||
|
||||
---
|
||||
|
||||
## GUID and Meta File Errors
|
||||
|
||||
### Could not extract GUID
|
||||
|
||||
**Error**:
|
||||
```
|
||||
Could not extract GUID in text file Assets/Prefabs/MyPrefab.prefab at line 97.
|
||||
```
|
||||
|
||||
**Cause**: Invalid GUID format (non-hexadecimal characters)
|
||||
|
||||
**Solution**:
|
||||
1. Find correct GUID: `grep "^guid:" Assets/Scripts/MyScript.cs.meta`
|
||||
2. Replace invalid GUID in prefab file
|
||||
3. Ensure GUID is 32 hexadecimal characters (0-9, a-f)
|
||||
|
||||
**Prevention**: Never manually create GUIDs; always copy from `.meta` files
|
||||
|
||||
→ See `reference/guid-system.md` for details
|
||||
|
||||
### Broken PPtr
|
||||
|
||||
**Error**:
|
||||
```
|
||||
Broken PPtr [guid 1234...] in file Assets/Scenes/MyScene.unity
|
||||
```
|
||||
|
||||
**Cause**: Reference to deleted asset (orphaned `.meta` file)
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Find and remove orphaned meta files
|
||||
for meta in Assets/**/*.meta; do
|
||||
[ -f "${meta%.meta}" ] || git rm "$meta"
|
||||
done
|
||||
```
|
||||
|
||||
**Prevention**: Delete files via Unity Editor or delete both `.cs` and `.meta` together
|
||||
|
||||
→ See `reference/guid-system.md` for details
|
||||
|
||||
---
|
||||
|
||||
## Input System Errors
|
||||
|
||||
### InvalidOperationException: Input System
|
||||
|
||||
**Error**:
|
||||
```
|
||||
InvalidOperationException: You are trying to read Input using the UnityEngine.Input class,
|
||||
but you have switched active Input handling to Input System package in Player Settings.
|
||||
```
|
||||
|
||||
**Cause**: Using legacy Input API when project configured for new Input System only
|
||||
|
||||
**Solution**:
|
||||
Change `activeInputHandler` to `2` (Both) in `ProjectSettings/ProjectSettings.asset`:
|
||||
```yaml
|
||||
activeInputHandler: 2
|
||||
```
|
||||
|
||||
Or via Editor: **Edit → Project Settings → Player → Active Input Handling → Both**
|
||||
|
||||
**Prevention**: Check Input System configuration before writing input code
|
||||
|
||||
→ See `reference/input-systems.md` for details
|
||||
|
||||
### Keyboard.current is null
|
||||
|
||||
**Error**: NullReferenceException when accessing `Keyboard.current`
|
||||
|
||||
**Cause**: Keyboard device not connected or not initialized
|
||||
|
||||
**Solution**:
|
||||
```csharp
|
||||
// Always null-check devices
|
||||
if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Always null-check device references in new Input System
|
||||
|
||||
→ See `reference/input-systems.md` for details
|
||||
|
||||
---
|
||||
|
||||
## URP Errors
|
||||
|
||||
### Camera data is null
|
||||
|
||||
**Error**:
|
||||
```
|
||||
Camera data is null. Please ensure the render pipeline is correctly initialized
|
||||
and the camera has a UniversalAdditionalCameraData component.
|
||||
```
|
||||
|
||||
**Cause**: Camera's `m_RendererIndex` set to `-1` (invalid) in URP scene
|
||||
|
||||
**Solution**:
|
||||
Change in `.unity` scene file:
|
||||
```yaml
|
||||
m_RendererIndex: 0 # Changed from -1
|
||||
```
|
||||
|
||||
**Prevention**: Always create cameras via Unity Editor when using URP
|
||||
|
||||
→ See `reference/urp-setup.md` for details
|
||||
|
||||
### Pink Materials (Shader Error)
|
||||
|
||||
**Symptom**: Materials appear bright pink/magenta
|
||||
|
||||
**Cause**: Shader not compatible with active render pipeline
|
||||
|
||||
**Solution**:
|
||||
1. **Automatic**: Edit → Render Pipeline → URP → Upgrade Project Materials
|
||||
2. **Manual**: Change shader to URP equivalent
|
||||
- `Standard` → `Universal Render Pipeline/Lit`
|
||||
- `Unlit` → `Universal Render Pipeline/Unlit`
|
||||
|
||||
**Prevention**: Use URP-compatible shaders from the start
|
||||
|
||||
→ See `reference/urp-setup.md` for details
|
||||
|
||||
---
|
||||
|
||||
## Physics Errors
|
||||
|
||||
### Objects fall through floor
|
||||
|
||||
**Symptom**: Rigidbody objects pass through colliders
|
||||
|
||||
**Causes**:
|
||||
- Fast-moving objects (tunneling)
|
||||
- Collider too thin
|
||||
- Layer collision matrix disabled
|
||||
- Missing Rigidbody
|
||||
|
||||
**Solutions**:
|
||||
1. Use Continuous Collision Detection:
|
||||
```csharp
|
||||
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||
```
|
||||
2. Increase collider thickness
|
||||
3. Check layer collision matrix (**Edit → Project Settings → Physics**)
|
||||
4. Use triggers for fast objects
|
||||
|
||||
**Prevention**: Use appropriate collision detection mode for object speed
|
||||
|
||||
→ See `reference/physics-system.md` for details
|
||||
|
||||
### Triggers not detecting
|
||||
|
||||
**Symptom**: `OnTriggerEnter` not called
|
||||
|
||||
**Causes**:
|
||||
- No Rigidbody on either object
|
||||
- Layer collision matrix disabled
|
||||
- `IsTrigger` not set to true
|
||||
- Callback method misspelled
|
||||
|
||||
**Solutions**:
|
||||
1. Add Rigidbody to at least one object
|
||||
2. Verify layer collision matrix enabled
|
||||
3. Check collider has `IsTrigger = true`
|
||||
4. Verify method signature exact: `void OnTriggerEnter(Collider other)`
|
||||
|
||||
**Prevention**: Ensure at least one Rigidbody present for trigger detection
|
||||
|
||||
→ See `reference/physics-system.md` for details
|
||||
|
||||
---
|
||||
|
||||
## NullReferenceException Errors
|
||||
|
||||
### Unassigned SerializeField
|
||||
|
||||
**Error**:
|
||||
```
|
||||
NullReferenceException: Object reference not set to an instance of an object
|
||||
ComponentName.MethodName () (at Assets/Scripts/ComponentName.cs:42)
|
||||
```
|
||||
|
||||
**Cause**: `[SerializeField]` field not assigned in Inspector
|
||||
|
||||
**Example**:
|
||||
```csharp
|
||||
[SerializeField] private Transform targetTransform; // Not assigned!
|
||||
|
||||
void Start()
|
||||
{
|
||||
Vector3 pos = targetTransform.position; // ← Throws here
|
||||
}
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
1. Assign field in Inspector
|
||||
2. Add null check:
|
||||
```csharp
|
||||
if (targetTransform == null)
|
||||
{
|
||||
Debug.LogError("Target not assigned!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
3. Use `OnValidate`:
|
||||
```csharp
|
||||
void OnValidate()
|
||||
{
|
||||
if (targetTransform == null)
|
||||
Debug.LogWarning($"{name}: Target not assigned!");
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Always assign SerializeField references or add null checks
|
||||
|
||||
### GetComponent returns null
|
||||
|
||||
**Error**: NullReferenceException after GetComponent
|
||||
|
||||
**Cause**: Component doesn't exist on GameObject
|
||||
|
||||
**Solution**:
|
||||
```csharp
|
||||
// ❌ Unsafe
|
||||
Rigidbody rb = GetComponent<Rigidbody>();
|
||||
rb.velocity = Vector3.zero; // Crash if no Rigidbody!
|
||||
|
||||
// ✓ Safe
|
||||
if (TryGetComponent<Rigidbody>(out var rb))
|
||||
{
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Rigidbody not found!");
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Use `TryGetComponent` or null-check `GetComponent` results
|
||||
|
||||
---
|
||||
|
||||
## Resources Loading Errors
|
||||
|
||||
### Resources.Load returns null
|
||||
|
||||
**Error**: Resources.Load returns null, file exists but not loading
|
||||
|
||||
**Cause**: File not in `Assets/Resources/` folder
|
||||
|
||||
**Solution**:
|
||||
1. Create `Assets/Resources/` folder structure
|
||||
2. Move files into Resources folder
|
||||
3. Load without extension or path prefix:
|
||||
```csharp
|
||||
// File at: Assets/Resources/Data/config.json
|
||||
TextAsset data = Resources.Load<TextAsset>("Data/config");
|
||||
```
|
||||
|
||||
**Prevention**: Understand `Resources.Load()` only works with Resources folders
|
||||
|
||||
---
|
||||
|
||||
## Compiler Warnings (Treat as Errors)
|
||||
|
||||
### CS0414: Field assigned but never used
|
||||
|
||||
**Warning**:
|
||||
```
|
||||
Assets/Scripts/MyScript.cs(19,25): warning CS0414:
|
||||
The field 'MyScript.myField' is assigned but its value is never used
|
||||
```
|
||||
|
||||
**Cause**: SerializeField declared but never accessed in code
|
||||
|
||||
**Solution**: Actually use the field in your logic:
|
||||
```csharp
|
||||
[SerializeField] private float speed = 5f;
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Use the field!
|
||||
transform.position += transform.forward * speed * Time.deltaTime;
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Use fields immediately after declaration; test Inspector changes
|
||||
|
||||
### CS0649: Field never assigned
|
||||
|
||||
**Warning**:
|
||||
```
|
||||
warning CS0649: Field 'MyClass.myField' is never assigned to,
|
||||
and will always have its default value null
|
||||
```
|
||||
|
||||
**Cause**: Private field without `[SerializeField]` that's never assigned in code
|
||||
|
||||
**Solution**:
|
||||
1. Add `[SerializeField]` if assigning in Inspector
|
||||
2. Or assign in code
|
||||
3. Or remove if unused
|
||||
|
||||
---
|
||||
|
||||
## Build Errors
|
||||
|
||||
### Scripts have compiler errors
|
||||
|
||||
**Error**: Cannot build due to compiler errors
|
||||
|
||||
**Cause**: Syntax errors, missing namespaces, or API mismatches
|
||||
|
||||
**Solution**:
|
||||
1. Open Console (**Window → General → Console**)
|
||||
2. Fix all red errors
|
||||
3. Clear all yellow warnings (treat them seriously!)
|
||||
4. Verify script compiles: **Assets → Reimport All**
|
||||
|
||||
### Missing assembly references
|
||||
|
||||
**Error**:
|
||||
```
|
||||
error CS0246: The type or namespace name 'InputSystem' could not be found
|
||||
```
|
||||
|
||||
**Cause**: Missing package or assembly reference
|
||||
|
||||
**Solution**:
|
||||
1. Install required package via Package Manager
|
||||
2. Add assembly reference in .asmdef (if using assembly definitions)
|
||||
3. Check `using` statements
|
||||
|
||||
---
|
||||
|
||||
## API Deprecation Warnings
|
||||
|
||||
### Rigidbody.velocity is obsolete
|
||||
|
||||
**Warning**:
|
||||
```
|
||||
'Rigidbody.velocity' is obsolete: 'Use linearVelocity instead.'
|
||||
```
|
||||
|
||||
**Cause**: Using deprecated API in Unity 2023.1+
|
||||
|
||||
**Solution**:
|
||||
```csharp
|
||||
// Old (pre-2023.1)
|
||||
rb.velocity = Vector3.forward;
|
||||
|
||||
// New (2023.1+)
|
||||
rb.linearVelocity = Vector3.forward;
|
||||
```
|
||||
|
||||
**Prevention**: Keep up with Unity API changes when upgrading versions
|
||||
|
||||
→ See `reference/physics-system.md` for details
|
||||
|
||||
---
|
||||
|
||||
## Performance Warnings
|
||||
|
||||
### Too many vertices in mesh
|
||||
|
||||
**Warning**: Mesh has excessive vertex count
|
||||
|
||||
**Solution**:
|
||||
1. Reduce polygon count in 3D software
|
||||
2. Use LOD (Level of Detail) system
|
||||
3. Enable mesh compression
|
||||
|
||||
### Too many draw calls
|
||||
|
||||
**Symptom**: Low framerate, many draw calls in Profiler
|
||||
|
||||
**Solution**:
|
||||
1. Enable **Static Batching** (mark objects static)
|
||||
2. Use **GPU Instancing** on materials
|
||||
3. Combine meshes
|
||||
4. Use texture atlases
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Errors
|
||||
|
||||
### Android Build Failed
|
||||
|
||||
**Common causes**:
|
||||
- Missing Android SDK/NDK
|
||||
- Incorrect API level
|
||||
- Keystore issues
|
||||
- Permissions not set
|
||||
|
||||
**Solution**:
|
||||
1. **Edit → Preferences → External Tools** → Set Android SDK/NDK
|
||||
2. **Player Settings → Android** → Set minimum API level
|
||||
3. Configure keystore
|
||||
4. Add required permissions in manifest
|
||||
|
||||
### iOS Build Failed
|
||||
|
||||
**Common causes**:
|
||||
- Xcode not installed
|
||||
- Invalid bundle identifier
|
||||
- Missing capabilities
|
||||
- Code signing issues
|
||||
|
||||
**Solution**:
|
||||
1. Install latest Xcode
|
||||
2. Set valid bundle identifier
|
||||
3. Configure capabilities in Xcode project
|
||||
4. Set up code signing
|
||||
|
||||
---
|
||||
|
||||
## Debugging Strategies
|
||||
|
||||
### General Approach
|
||||
|
||||
1. **Read the full error message** - often contains the solution
|
||||
2. **Note the line number** - error location is critical
|
||||
3. **Check recent changes** - what changed before the error?
|
||||
4. **Google the exact error** - likely someone else solved it
|
||||
5. **Check Unity Forums** - official community support
|
||||
6. **Profile if performance** - use Unity Profiler
|
||||
|
||||
### Console Tools
|
||||
|
||||
```csharp
|
||||
// Basic logging
|
||||
Debug.Log("Value: " + value);
|
||||
|
||||
// Warnings and errors
|
||||
Debug.LogWarning("This might be a problem");
|
||||
Debug.LogError("This is definitely wrong");
|
||||
|
||||
// Conditional logging
|
||||
Debug.Assert(value != null, "Value should not be null!");
|
||||
|
||||
// Context logging (clickable in Console)
|
||||
Debug.Log("Object info", gameObject);
|
||||
```
|
||||
|
||||
### Break on Error
|
||||
|
||||
**Edit → Preferences → General → Error Pause**
|
||||
- Enable to pause Editor when error occurs
|
||||
- Helpful for debugging runtime errors
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Error Keywords
|
||||
|
||||
| Error Keyword | Common Cause | Quick Fix |
|
||||
|--------------|--------------|-----------|
|
||||
| GUID | Invalid or missing GUID | Copy from `.meta` file |
|
||||
| Input System | API mismatch | Set `activeInputHandler: 2` |
|
||||
| Camera data is null | URP renderer index | Set `m_RendererIndex: 0` |
|
||||
| NullReferenceException | Unassigned field | Assign in Inspector or null-check |
|
||||
| Resources.Load | Wrong folder | Move to `Resources/` folder |
|
||||
| velocity obsolete | Old API (2023.1+) | Use `linearVelocity` |
|
||||
| CS0414 | Unused field | Actually use the field |
|
||||
| Pink material | Wrong shader | Use URP shaders |
|
||||
| Trigger not working | Missing Rigidbody | Add Rigidbody component |
|
||||
| Broken PPtr | Orphaned meta file | Remove orphaned `.meta` |
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Unity Documentation](https://docs.unity3d.com/)
|
||||
- [Unity Forums](https://forum.unity.com/)
|
||||
- [Unity Answers](https://answers.unity.com/)
|
||||
- [Stack Overflow - Unity Tag](https://stackoverflow.com/questions/tagged/unity3d)
|
||||
- [Unity Issue Tracker](https://issuetracker.unity3d.com/)
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
# Unity GUID System & Asset References
|
||||
|
||||
## Overview
|
||||
|
||||
Unity uses a GUID-based system to track asset references. Every asset has a `.meta` file containing a unique 32-character hexadecimal GUID, import settings, and asset configuration.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
1. **Never manually create GUIDs** - always copy from existing `.meta` files
|
||||
2. **Keep `.meta` files in version control** - they're essential for maintaining references
|
||||
3. **Delete files via Unity Editor** - or manually delete both `.cs` and `.meta` together
|
||||
4. **GUID format**: Exactly 32 hexadecimal characters (0-9, a-f only)
|
||||
|
||||
## Valid vs Invalid GUIDs
|
||||
|
||||
**Valid GUID**: `6004172616168c346951d6f4cddd83da` (32 hex characters)
|
||||
**Invalid GUID**: `a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6` (contains non-hex: g, h, i, j, k, l, m, n, o, p)
|
||||
|
||||
## Finding the Correct GUID
|
||||
|
||||
```bash
|
||||
# Find a script's GUID
|
||||
grep "^guid:" Assets/Scripts/YourScript.cs.meta
|
||||
|
||||
# Find all invalid GUIDs
|
||||
grep -Ern "guid: [^0-9a-f]" Assets/
|
||||
|
||||
# Find orphaned meta files
|
||||
for meta in Assets/**/*.meta; do
|
||||
[ -f "${meta%.meta}" ] || echo "Orphaned: $meta"
|
||||
done
|
||||
```
|
||||
|
||||
## Common GUID Errors
|
||||
|
||||
### Error: GUID Extraction Failed
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Could not extract GUID in text file Assets/Prefabs/MyPrefab.prefab at line 97.
|
||||
```
|
||||
|
||||
**Root Cause**: Invalid or malformed GUID in prefab's MonoBehaviour reference
|
||||
|
||||
**Location Example**:
|
||||
```yaml
|
||||
m_Script: {fileID: 11500000, guid: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6, type: 3}
|
||||
```
|
||||
|
||||
**Solution**:
|
||||
1. Find the script's `.meta` file
|
||||
```bash
|
||||
find Assets/Scripts -name "MyScript.cs"
|
||||
grep "^guid:" Assets/Scripts/MyScript.cs.meta
|
||||
```
|
||||
|
||||
2. Copy the correct GUID from the meta file
|
||||
|
||||
3. Replace in prefab:
|
||||
```yaml
|
||||
m_Script: {fileID: 11500000, guid: 6004172616168c346951d6f4cddd83da, type: 3}
|
||||
```
|
||||
|
||||
### Error: Broken PPtr
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Broken PPtr [guid] in file [path]
|
||||
```
|
||||
|
||||
**Root Cause**: Orphaned `.meta` files for deleted scripts
|
||||
|
||||
**How It Happens**:
|
||||
- Script deleted outside Unity Editor
|
||||
- `.meta` file still exists in git
|
||||
- Unity can't find the asset but meta file references it
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Find orphaned meta files
|
||||
git status | grep -E "D.*\.cs$" -A 1
|
||||
|
||||
# Remove orphaned meta files
|
||||
git rm path/to/deleted/file.cs.meta
|
||||
```
|
||||
|
||||
## Git Workflow with Meta Files
|
||||
|
||||
### File Patterns
|
||||
```
|
||||
Asset.ext # The actual asset
|
||||
Asset.ext.meta # Unity metadata (GUID, import settings)
|
||||
```
|
||||
|
||||
**Always keep them synchronized!**
|
||||
|
||||
### When Adding New Assets
|
||||
|
||||
```
|
||||
Create Asset → Via Unity Editor? → Unity handles .meta
|
||||
↓
|
||||
Verify in git status
|
||||
↓
|
||||
Commit both files together
|
||||
```
|
||||
|
||||
**Manual creation** (if not using Unity Editor):
|
||||
1. Create the asset file
|
||||
2. Let Unity generate `.meta` file
|
||||
3. Stage both files: `git add Asset.ext Asset.ext.meta`
|
||||
4. Commit together
|
||||
|
||||
### When Deleting Assets
|
||||
|
||||
```
|
||||
Delete Asset → Via Unity Editor? → Unity deletes both
|
||||
↓
|
||||
Check git status
|
||||
↓
|
||||
Commit deletions
|
||||
```
|
||||
|
||||
**Manual deletion**:
|
||||
```bash
|
||||
# Delete both files
|
||||
rm Assets/Scripts/MyScript.cs
|
||||
rm Assets/Scripts/MyScript.cs.meta
|
||||
|
||||
# Stage deletions
|
||||
git add -A
|
||||
|
||||
# Or explicitly
|
||||
git rm Assets/Scripts/MyScript.cs
|
||||
git rm Assets/Scripts/MyScript.cs.meta
|
||||
```
|
||||
|
||||
### Checking for Issues
|
||||
|
||||
```bash
|
||||
# Check for orphaned meta files before commit
|
||||
for meta in Assets/**/*.meta; do
|
||||
[ -f "${meta%.meta}" ] || echo "Orphaned: $meta"
|
||||
done
|
||||
|
||||
# Find untracked meta files
|
||||
git status | grep "\.meta$"
|
||||
|
||||
# Verify all .cs files have matching .meta files
|
||||
find Assets -name "*.cs" | while read file; do
|
||||
[ -f "$file.meta" ] || echo "Missing .meta: $file"
|
||||
done
|
||||
```
|
||||
|
||||
## Prefab References
|
||||
|
||||
### How Prefabs Reference Scripts
|
||||
|
||||
Prefabs store script references using GUIDs from `.meta` files:
|
||||
|
||||
```yaml
|
||||
MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 6004172616168c346951d6f4cddd83da, type: 3}
|
||||
```
|
||||
|
||||
Where:
|
||||
- `fileID: 11500000` = MonoScript asset type (always this value)
|
||||
- `guid: ...` = The script's GUID from its `.meta` file
|
||||
- `type: 3` = Asset type indicator
|
||||
|
||||
### When Creating Prefabs Manually
|
||||
|
||||
**Best Practice**: Always use Unity Editor to create prefabs
|
||||
|
||||
**If editing manually**:
|
||||
1. Find the script you want to reference
|
||||
2. Open its `.meta` file
|
||||
3. Copy the GUID
|
||||
4. Use in prefab with correct format
|
||||
|
||||
### Validating Prefab GUIDs
|
||||
|
||||
```bash
|
||||
# Extract all GUIDs from a prefab
|
||||
grep "guid:" Assets/Prefabs/MyPrefab.prefab
|
||||
|
||||
# Check if all GUIDs are valid hex
|
||||
grep "guid:" Assets/Prefabs/MyPrefab.prefab | grep -E "guid: [^0-9a-f]"
|
||||
|
||||
# Find the script file for a specific GUID
|
||||
guid="6004172616168c346951d6f4cddd83da"
|
||||
find Assets -name "*.meta" -exec grep -l "guid: $guid" {} \;
|
||||
```
|
||||
|
||||
## Scene References
|
||||
|
||||
Scenes also use GUIDs to reference assets. Same rules apply:
|
||||
|
||||
```yaml
|
||||
--- !u!1 &1234567890
|
||||
GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1234567891}
|
||||
- component: {fileID: 1234567892, guid: 6004172616168c346951d6f4cddd83da, type: 3}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
✅ Use Unity Editor for all asset operations
|
||||
✅ Keep `.meta` files in version control
|
||||
✅ Commit `.cs` and `.meta` files together
|
||||
✅ Delete through Unity Editor
|
||||
✅ Copy GUIDs from existing `.meta` files
|
||||
✅ Validate GUID format before using
|
||||
|
||||
### Don'ts
|
||||
❌ Manually create or edit GUIDs
|
||||
❌ Delete only `.cs` without `.meta`
|
||||
❌ Delete only `.meta` without `.cs`
|
||||
❌ Edit prefabs/scenes without Unity Editor
|
||||
❌ Ignore orphaned `.meta` files
|
||||
❌ Use non-hexadecimal characters in GUIDs
|
||||
|
||||
## Troubleshooting Commands
|
||||
|
||||
```bash
|
||||
# Find all assets missing meta files
|
||||
find Assets -type f ! -name "*.meta" ! -path "*/.*" | while read file; do
|
||||
[ -f "$file.meta" ] || echo "Missing .meta: $file"
|
||||
done
|
||||
|
||||
# Find all meta files without matching assets
|
||||
find Assets -name "*.meta" | while read meta; do
|
||||
asset="${meta%.meta}"
|
||||
[ -f "$asset" ] || echo "Orphaned: $meta"
|
||||
done
|
||||
|
||||
# Validate all GUIDs are proper hex
|
||||
find Assets -name "*.meta" -exec grep -H "guid:" {} \; | grep -v "guid: [0-9a-f]\{32\}"
|
||||
|
||||
# List all GUIDs in project
|
||||
find Assets -name "*.meta" -exec grep "guid:" {} \; | sort | uniq
|
||||
|
||||
# Find duplicate GUIDs (should never happen!)
|
||||
find Assets -name "*.meta" -exec grep "guid:" {} \; | sort | uniq -d
|
||||
```
|
||||
|
||||
## Recovery Strategies
|
||||
|
||||
### If GUID References Break
|
||||
|
||||
1. **Identify broken references**:
|
||||
- Unity Console shows "Missing Referenced Script"
|
||||
- Prefabs/scenes show "None (Script)" in Inspector
|
||||
|
||||
2. **Find the original script**:
|
||||
- Search project for script name
|
||||
- Check if script was renamed or moved
|
||||
|
||||
3. **Fix the reference**:
|
||||
- Reassign in Unity Inspector (automatic GUID fix)
|
||||
- Or manually copy correct GUID to prefab/scene file
|
||||
|
||||
### If Meta Files Are Lost
|
||||
|
||||
1. **Never delete `.meta` files from version control**
|
||||
2. If already deleted:
|
||||
- Unity generates new GUIDs (breaks all references!)
|
||||
- May need to restore from backup
|
||||
- Or manually reassign all references in Unity Editor
|
||||
|
||||
### Mass Reference Fixing
|
||||
|
||||
If many references break (e.g., after bad merge):
|
||||
1. Create a backup
|
||||
2. Use Unity's "Reimport All" (risky, regenerates meta files)
|
||||
3. Manually fix critical prefabs in Inspector
|
||||
4. Use git to restore known-good `.meta` files
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Unity Manual: [Meta File Format](https://docs.unity3d.com/Manual/AssetMetaFiles.html)
|
||||
- Unity Manual: [Asset Database](https://docs.unity3d.com/Manual/AssetDatabase.html)
|
||||
- Unity Forum: Search "GUID extraction error"
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
# Unity Input Systems
|
||||
|
||||
## Overview
|
||||
|
||||
Unity has two input systems that cannot be mixed without proper configuration. Understanding which system your project uses and how to configure it correctly is essential to avoid runtime errors.
|
||||
|
||||
## The Two Systems
|
||||
|
||||
### Legacy Input Manager (Old)
|
||||
|
||||
**Package**: Built into Unity core
|
||||
**Namespace**: `UnityEngine.Input`
|
||||
**Available**: All Unity versions
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
|
||||
// Simple, straightforward API
|
||||
Vector3 mousePos = Input.mousePosition;
|
||||
bool spacePressed = Input.GetKeyDown(KeyCode.Space);
|
||||
float horizontal = Input.GetAxis("Horizontal");
|
||||
bool leftClick = Input.GetMouseButtonDown(0);
|
||||
```
|
||||
|
||||
**Pros**:
|
||||
- Simple and straightforward
|
||||
- Built into Unity core
|
||||
- No additional setup required
|
||||
- Good for simple projects and prototypes
|
||||
|
||||
**Cons**:
|
||||
- Limited rebinding capabilities
|
||||
- Hard-coded input strings
|
||||
- No multi-device management
|
||||
- Deprecated (will be removed eventually)
|
||||
|
||||
### New Input System (2019.1+)
|
||||
|
||||
**Package**: `com.unity.inputsystem`
|
||||
**Namespace**: `UnityEngine.InputSystem`
|
||||
**Available**: Unity 2019.1+
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
private Keyboard keyboard;
|
||||
private Mouse mouse;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
keyboard = Keyboard.current;
|
||||
mouse = Mouse.current;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Always null-check devices
|
||||
if (keyboard == null)
|
||||
{
|
||||
keyboard = Keyboard.current;
|
||||
if (keyboard == null) return;
|
||||
}
|
||||
|
||||
if (keyboard.spaceKey.wasPressedThisFrame)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
|
||||
if (mouse != null && mouse.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
Shoot();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pros**:
|
||||
- Flexible and powerful
|
||||
- Full rebinding support
|
||||
- Asset-based input configuration
|
||||
- Better multi-device support (gamepad, touch, VR)
|
||||
- Action-based workflow
|
||||
- Event-driven architecture
|
||||
|
||||
**Cons**:
|
||||
- More complex setup
|
||||
- Requires package installation
|
||||
- Steeper learning curve
|
||||
- Null-checking required for devices
|
||||
|
||||
## Configuration
|
||||
|
||||
### Location
|
||||
`ProjectSettings/ProjectSettings.asset` (line ~681)
|
||||
|
||||
```yaml
|
||||
activeInputHandler: [value]
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Name | Legacy API | New API | Use Case |
|
||||
|-------|------|------------|---------|----------|
|
||||
| 0 | Input Manager (Old) | ✓ | ✗ | Legacy projects only |
|
||||
| 1 | Input System (New) | ✗ | ✓ | New projects with new system |
|
||||
| 2 | Both | ✓ | ✓ | **Migration, compatibility** |
|
||||
|
||||
### Changing in Unity Editor
|
||||
|
||||
1. **Edit → Project Settings → Player**
|
||||
2. Scroll to **"Active Input Handling"**
|
||||
3. Select one of:
|
||||
- Input Manager (Old) - Legacy only
|
||||
- Input System Package (New) - New only
|
||||
- **Both** - Legacy + New (Recommended during migration)
|
||||
4. **Restart Unity** (required for changes to take effect)
|
||||
|
||||
## Common Input System Error
|
||||
|
||||
### InvalidOperationException
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
InvalidOperationException: You are trying to read Input using the UnityEngine.Input class,
|
||||
but you have switched active Input handling to Input System package in Player Settings.
|
||||
```
|
||||
|
||||
**Root Cause**: Code uses `UnityEngine.Input` (legacy) but project configured for new Input System only (`activeInputHandler: 1`)
|
||||
|
||||
**Where It Happens**:
|
||||
```csharp
|
||||
// This code causes the error when activeInputHandler = 1
|
||||
Vector3 mousePos = Input.mousePosition; // Legacy API
|
||||
if (Input.GetMouseButtonDown(0)) { } // Legacy API
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
|
||||
#### Option 1: Enable Both Systems (Recommended)
|
||||
Change `activeInputHandler` to `2`:
|
||||
|
||||
```yaml
|
||||
# Before
|
||||
activeInputHandler: 1
|
||||
|
||||
# After
|
||||
activeInputHandler: 2 # Enables both systems
|
||||
```
|
||||
|
||||
**Why this is best**:
|
||||
- No code changes required
|
||||
- Backward compatible
|
||||
- Gradual migration possible
|
||||
- UI system (EventSystem) works with both
|
||||
|
||||
#### Option 2: Rewrite Code for New Input System
|
||||
Migrate to new API:
|
||||
|
||||
```csharp
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
private void HandleInput()
|
||||
{
|
||||
// New Input System API
|
||||
if (Keyboard.current == null) return;
|
||||
|
||||
if (Keyboard.current.wKey.isPressed)
|
||||
{
|
||||
MoveForward();
|
||||
}
|
||||
|
||||
if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
Shoot();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Modern, flexible system
|
||||
- Better for complex input
|
||||
- Future-proof
|
||||
|
||||
**Drawbacks**:
|
||||
- Requires code rewrite
|
||||
- More complex setup
|
||||
- Breaking change
|
||||
|
||||
#### Option 3: Revert to Legacy Only
|
||||
```yaml
|
||||
activeInputHandler: 0 # Legacy Input Manager only
|
||||
```
|
||||
|
||||
**Not recommended** - Legacy system is deprecated
|
||||
|
||||
## API Comparison
|
||||
|
||||
### Keyboard Input
|
||||
|
||||
| Action | Legacy Input | New Input System |
|
||||
|--------|--------------|------------------|
|
||||
| Check if key pressed | `Input.GetKey(KeyCode.W)` | `Keyboard.current.wKey.isPressed` |
|
||||
| Key pressed this frame | `Input.GetKeyDown(KeyCode.W)` | `Keyboard.current.wKey.wasPressedThisFrame` |
|
||||
| Key released this frame | `Input.GetKeyUp(KeyCode.W)` | `Keyboard.current.wKey.wasReleasedThisFrame` |
|
||||
| Any key pressed | `Input.anyKey` | `Keyboard.current.anyKey.isPressed` |
|
||||
|
||||
### Mouse Input
|
||||
|
||||
| Action | Legacy Input | New Input System |
|
||||
|--------|--------------|------------------|
|
||||
| Mouse position | `Input.mousePosition` | `Mouse.current.position.ReadValue()` |
|
||||
| Left button | `Input.GetMouseButton(0)` | `Mouse.current.leftButton.isPressed` |
|
||||
| Left button down | `Input.GetMouseButtonDown(0)` | `Mouse.current.leftButton.wasPressedThisFrame` |
|
||||
| Right button | `Input.GetMouseButton(1)` | `Mouse.current.rightButton.isPressed` |
|
||||
| Middle button | `Input.GetMouseButton(2)` | `Mouse.current.middleButton.isPressed` |
|
||||
| Scroll wheel | `Input.mouseScrollDelta` | `Mouse.current.scroll.ReadValue()` |
|
||||
|
||||
### Axis Input
|
||||
|
||||
| Action | Legacy Input | New Input System |
|
||||
|--------|--------------|------------------|
|
||||
| Horizontal axis | `Input.GetAxis("Horizontal")` | Use Input Actions or manual key checks |
|
||||
| Vertical axis | `Input.GetAxis("Vertical")` | Use Input Actions or manual key checks |
|
||||
|
||||
**Note**: New Input System requires Input Action Assets for axis input. More powerful but more setup.
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Phase 1: Support Both (Current State)
|
||||
```yaml
|
||||
activeInputHandler: 2
|
||||
```
|
||||
|
||||
**Actions**:
|
||||
- Keep existing legacy code working
|
||||
- Start writing new features with new Input System
|
||||
- Test both systems work together
|
||||
- No breaking changes
|
||||
|
||||
### Phase 2: Gradual Migration
|
||||
|
||||
**Strategy**:
|
||||
1. Create abstraction layer (optional but helpful)
|
||||
2. Migrate one script at a time
|
||||
3. Test thoroughly after each migration
|
||||
4. Keep `activeInputHandler: 2` during transition
|
||||
|
||||
**Example Abstraction**:
|
||||
```csharp
|
||||
public static class GameInput
|
||||
{
|
||||
public static Vector3 MousePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Mouse.current?.position.ReadValue() ?? Vector3.zero;
|
||||
#else
|
||||
return Input.mousePosition;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LeftMouseDown
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Mouse.current?.leftButton.wasPressedThisFrame ?? false;
|
||||
#else
|
||||
return Input.GetMouseButtonDown(0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Full Migration (Future)
|
||||
|
||||
```yaml
|
||||
activeInputHandler: 1 # New system only
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- All code uses new Input System
|
||||
- No legacy `Input` API calls
|
||||
- Input Actions configured
|
||||
- Thoroughly tested
|
||||
|
||||
## Device Null Checking
|
||||
|
||||
**Critical**: Always null-check device references in new Input System
|
||||
|
||||
```csharp
|
||||
// ❌ BAD - Will crash if device not present
|
||||
if (Keyboard.current.spaceKey.isPressed)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
|
||||
// ✓ GOOD - Safe null checking
|
||||
if (Keyboard.current != null && Keyboard.current.spaceKey.isPressed)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
|
||||
// ✓ BETTER - Cache and check
|
||||
private Keyboard keyboard;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (keyboard == null)
|
||||
{
|
||||
keyboard = Keyboard.current;
|
||||
if (keyboard == null) return;
|
||||
}
|
||||
|
||||
if (keyboard.spaceKey.isPressed)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Input Actions (Advanced)
|
||||
|
||||
For complex input scenarios, use Input Action Assets:
|
||||
|
||||
### Creating Input Actions
|
||||
|
||||
1. **Create Asset**: Right-click in Project → Create → Input Actions
|
||||
2. **Define Actions**: Add action maps, actions, and bindings
|
||||
3. **Generate C# Class**: Check "Generate C# Class" in Inspector
|
||||
4. **Use in Scripts**:
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public class PlayerController : MonoBehaviour
|
||||
{
|
||||
private PlayerInputActions inputActions;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
inputActions = new PlayerInputActions();
|
||||
inputActions.Player.Move.performed += OnMove;
|
||||
inputActions.Player.Jump.performed += OnJump;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
inputActions.Enable();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
inputActions.Disable();
|
||||
}
|
||||
|
||||
private void OnMove(InputAction.CallbackContext context)
|
||||
{
|
||||
Vector2 moveInput = context.ReadValue<Vector2>();
|
||||
// Handle movement
|
||||
}
|
||||
|
||||
private void OnJump(InputAction.CallbackContext context)
|
||||
{
|
||||
// Handle jump
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Clean, event-driven code
|
||||
- Easy rebinding
|
||||
- Cross-platform support
|
||||
- Separates input from logic
|
||||
|
||||
## UI and EventSystem
|
||||
|
||||
Unity's UI system (UGUI) works with both input systems:
|
||||
|
||||
### Legacy Input Manager
|
||||
```csharp
|
||||
// Uses StandaloneInputModule (automatically configured)
|
||||
UnityEngine.EventSystems.StandaloneInputModule
|
||||
```
|
||||
|
||||
### New Input System
|
||||
```csharp
|
||||
// Replace with InputSystemUIInputModule
|
||||
// Add to EventSystem GameObject
|
||||
using UnityEngine.InputSystem.UI;
|
||||
|
||||
// Unity automatically adds this when new Input System active
|
||||
```
|
||||
|
||||
**Both systems support**:
|
||||
- Button clicks
|
||||
- Scroll views
|
||||
- Sliders
|
||||
- Toggle interactions
|
||||
- Navigation
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
✅ Check `activeInputHandler` before writing input code
|
||||
✅ Null-check devices in new Input System
|
||||
✅ Use `activeInputHandler: 2` during migration
|
||||
✅ Create abstraction layer for easy switching
|
||||
✅ Test input on target platforms
|
||||
✅ Document which input system your project uses
|
||||
|
||||
### Don'ts
|
||||
❌ Mix APIs without setting `activeInputHandler: 2`
|
||||
❌ Forget to restart Unity after changing input settings
|
||||
❌ Use legacy Input System for new projects
|
||||
❌ Skip null checks for devices
|
||||
❌ Hard-code input keys (use Input Actions)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Input Not Working
|
||||
|
||||
**Check**:
|
||||
1. Is `activeInputHandler` configured correctly?
|
||||
2. Are you using the right API for your configuration?
|
||||
3. For new Input System: Are devices null-checked?
|
||||
4. For Input Actions: Are they enabled?
|
||||
5. For UI: Is EventSystem present and configured?
|
||||
|
||||
### Build Errors
|
||||
|
||||
**Error**: `The type or namespace name 'InputSystem' could not be found`
|
||||
|
||||
**Solution**: Install Input System package via Package Manager
|
||||
|
||||
```json
|
||||
// Packages/manifest.json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.inputsystem": "1.7.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
|
||||
**New Input System** can be more performant for:
|
||||
- Many input devices
|
||||
- Complex input scenarios
|
||||
- Event-driven architectures
|
||||
|
||||
**Legacy Input Manager** may be simpler for:
|
||||
- Prototypes
|
||||
- Simple games
|
||||
- Fewer input sources
|
||||
|
||||
## Package Management
|
||||
|
||||
### Installing New Input System
|
||||
|
||||
1. **Window → Package Manager**
|
||||
2. Search for "Input System"
|
||||
3. Click **Install**
|
||||
4. Unity prompts to enable new Input System → Select "Yes" → Restart
|
||||
|
||||
### Checking Package
|
||||
|
||||
```json
|
||||
// Packages/manifest.json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.inputsystem": "1.7.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Unity Manual - Input System](https://docs.unity3d.com/Packages/com.unity.inputsystem@latest)
|
||||
- [Migration Guide](https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/manual/Migration.html)
|
||||
- [Input Actions Documentation](https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/manual/Actions.html)
|
||||
- [Quick Start Guide](https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/manual/QuickStartGuide.html)
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
# Unity Physics System
|
||||
|
||||
## Overview
|
||||
|
||||
Understanding Unity's physics system is crucial for creating responsive gameplay. This guide covers Rigidbody API changes, the critical difference between triggers and collisions, and best practices for physics-based games.
|
||||
|
||||
## Rigidbody API Changes (Unity 2023.1+)
|
||||
|
||||
### The Change
|
||||
|
||||
Unity 2023.1 renamed `Rigidbody.velocity` to `Rigidbody.linearVelocity` for clarity and consistency.
|
||||
|
||||
### Old API (Pre-2023.1)
|
||||
|
||||
```csharp
|
||||
// Setting velocity
|
||||
rb.velocity = moveDirection;
|
||||
|
||||
// Getting velocity
|
||||
Vector3 currentVelocity = rb.velocity;
|
||||
|
||||
// Modifying velocity
|
||||
rb.velocity += Vector3.up * jumpForce;
|
||||
```
|
||||
|
||||
### New API (Unity 2023.1+)
|
||||
|
||||
```csharp
|
||||
// Setting velocity
|
||||
rb.linearVelocity = moveDirection;
|
||||
|
||||
// Getting velocity
|
||||
Vector3 currentVelocity = rb.linearVelocity;
|
||||
|
||||
// Modifying velocity
|
||||
rb.linearVelocity += Vector3.up * jumpForce;
|
||||
```
|
||||
|
||||
### Related Changes
|
||||
|
||||
| Old API | New API | Notes |
|
||||
|---------|---------|-------|
|
||||
| `Rigidbody.velocity` | `Rigidbody.linearVelocity` | Renamed for clarity |
|
||||
| `Rigidbody.angularVelocity` | `Rigidbody.angularVelocity` | Unchanged (already clear) |
|
||||
|
||||
### Migration
|
||||
|
||||
**Automatic**: Unity auto-upgrades scripts when opened in 2023.1+
|
||||
|
||||
**Manual**: Use Find & Replace
|
||||
```csharp
|
||||
// Find: rb.velocity
|
||||
// Replace: rb.linearVelocity
|
||||
```
|
||||
|
||||
**Best Practice**: Always use `linearVelocity` for Unity 2023.1+ projects
|
||||
|
||||
## Triggers vs Collisions
|
||||
|
||||
Understanding when to use triggers vs collisions is critical for game physics.
|
||||
|
||||
### Collision Detection (Solid Colliders)
|
||||
|
||||
**Setup**:
|
||||
- Collider: `IsTrigger = false`
|
||||
- Requires: At least one Rigidbody on interacting objects
|
||||
|
||||
**Callback**:
|
||||
```csharp
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
Debug.Log($"Collided with {collision.gameObject.name}");
|
||||
|
||||
// Access collision information
|
||||
ContactPoint contact = collision.contacts[0];
|
||||
Vector3 normal = contact.normal;
|
||||
float impulse = collision.impulse.magnitude;
|
||||
}
|
||||
```
|
||||
|
||||
**Physics Behavior**:
|
||||
- Objects physically interact
|
||||
- Bouncing, pushing, momentum transfer
|
||||
- Realistic physics simulation
|
||||
- More expensive computationally
|
||||
|
||||
**When to Use**:
|
||||
- Realistic physics interactions needed
|
||||
- Objects should bounce or push
|
||||
- Slower-moving objects
|
||||
- Vehicle physics
|
||||
- Ragdoll systems
|
||||
- Destructible objects that react physically
|
||||
|
||||
### Trigger Detection (Ghost Colliders)
|
||||
|
||||
**Setup**:
|
||||
- Collider: `IsTrigger = true`
|
||||
- Requires: At least one Rigidbody on interacting objects
|
||||
|
||||
**Callback**:
|
||||
```csharp
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
Debug.Log($"Triggered by {other.gameObject.name}");
|
||||
|
||||
// No collision information available
|
||||
// Just detection
|
||||
}
|
||||
```
|
||||
|
||||
**Physics Behavior**:
|
||||
- No physical interaction
|
||||
- Objects pass through each other
|
||||
- Detection-only
|
||||
- Cheaper computationally
|
||||
|
||||
**When to Use**:
|
||||
- Fast-moving projectiles (bullets, arrows)
|
||||
- Zone detection (powerups, checkpoints, sensors)
|
||||
- Detection-only systems
|
||||
- Performance-critical scenarios
|
||||
- Guaranteed detection without tunneling
|
||||
|
||||
## Comparison Table
|
||||
|
||||
| Aspect | Collision (Solid) | Trigger (Ghost) |
|
||||
|--------|------------------|-----------------|
|
||||
| **Detection reliability** | Can miss fast objects (tunneling) | Always detects overlaps |
|
||||
| **Physics response** | Objects bounce/react | No physical interaction |
|
||||
| **Performance** | More expensive | Cheaper |
|
||||
| **Information** | Contact points, impulse, normal | Basic overlap detection |
|
||||
| **Use case** | Physics reactions | Detection only |
|
||||
| **Rigidbody required** | Yes (at least one) | Yes (at least one) |
|
||||
| **Fast objects** | Risk of tunneling | Guaranteed detection |
|
||||
|
||||
## Fast-Moving Objects & Tunneling
|
||||
|
||||
### The Tunneling Problem
|
||||
|
||||
Fast objects can pass through colliders between physics frames.
|
||||
|
||||
**Example**:
|
||||
- Bullet speed: 50 units/sec
|
||||
- Physics update: 0.02s (50 Hz)
|
||||
- Bullet travels: 1 unit per frame
|
||||
- Thin wall: 0.5 units thick
|
||||
- Result: Bullet **tunnels through** wall
|
||||
|
||||
### Solutions
|
||||
|
||||
#### 1. Use Triggers (Recommended for Bullets)
|
||||
|
||||
```csharp
|
||||
// Barrel with trigger collider
|
||||
public class BarrelDestruction : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private LayerMask bulletLayer;
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.gameObject.layer == bulletLayer)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
}
|
||||
|
||||
private void Explode()
|
||||
{
|
||||
// Destruction logic
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Setup**:
|
||||
- Barrel: CapsuleCollider with `IsTrigger = true`, Rigidbody present
|
||||
- Bullet: SphereCollider (solid), Rigidbody with velocity
|
||||
|
||||
**Benefits**:
|
||||
- Guaranteed detection
|
||||
- No tunneling
|
||||
- Better performance
|
||||
- Simple implementation
|
||||
|
||||
#### 2. Continuous Collision Detection
|
||||
|
||||
```csharp
|
||||
Rigidbody rb = GetComponent<Rigidbody>();
|
||||
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||
// or
|
||||
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
||||
```
|
||||
|
||||
**Modes**:
|
||||
- **Discrete**: Default, cheapest, can tunnel
|
||||
- **Continuous**: Prevents tunneling against static colliders
|
||||
- **ContinuousDynamic**: Prevents tunneling against all colliders (most expensive)
|
||||
- **ContinuousSpeculative**: Good balance (Unity 2022+)
|
||||
|
||||
**Use for**: Important physics objects that must not tunnel
|
||||
|
||||
#### 3. Increase Fixed Timestep
|
||||
|
||||
```csharp
|
||||
// Edit → Project Settings → Time
|
||||
Time.fixedDeltaTime = 0.01f; // 100 Hz (default: 0.02 = 50 Hz)
|
||||
```
|
||||
|
||||
**Tradeoffs**:
|
||||
- More frequent physics updates
|
||||
- Better collision detection
|
||||
- Higher CPU usage
|
||||
- Affects entire project
|
||||
|
||||
#### 4. Use Raycasting
|
||||
|
||||
For instant-hit weapons:
|
||||
|
||||
```csharp
|
||||
public class RaycastGun : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float range = 100f;
|
||||
[SerializeField] private LayerMask hitLayers;
|
||||
|
||||
void Shoot()
|
||||
{
|
||||
if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, range, hitLayers))
|
||||
{
|
||||
// Instant hit, no tunneling possible
|
||||
if (hit.collider.TryGetComponent<IDamageable>(out var damageable))
|
||||
{
|
||||
damageable.TakeDamage(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- No tunneling ever
|
||||
- Instant hit
|
||||
- Very accurate
|
||||
- Good for hitscan weapons
|
||||
|
||||
**Drawbacks**:
|
||||
- No bullet travel time
|
||||
- Less visual feedback
|
||||
- Different gameplay feel
|
||||
|
||||
## Bullet Detection Best Practices
|
||||
|
||||
### Why Triggers for Bullets
|
||||
|
||||
**Bullets are**:
|
||||
- Fast (50+ units/sec)
|
||||
- Lightweight (mass 0.01)
|
||||
- Destroyed on impact
|
||||
- Don't need physics reactions
|
||||
|
||||
**Benefits of Triggers**:
|
||||
1. Guaranteed detection (no tunneling)
|
||||
2. Better performance (no physics calculation)
|
||||
3. Simple implementation
|
||||
4. Stationary targets don't move
|
||||
|
||||
### Example Setup
|
||||
|
||||
**Bullet**:
|
||||
```csharp
|
||||
public class Bullet : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float speed = 50f;
|
||||
[SerializeField] private float lifetime = 5f;
|
||||
[SerializeField] private int damage = 10;
|
||||
|
||||
private Rigidbody rb;
|
||||
|
||||
void Start()
|
||||
{
|
||||
rb = GetComponent<Rigidbody>();
|
||||
rb.linearVelocity = transform.forward * speed;
|
||||
Destroy(gameObject, lifetime);
|
||||
}
|
||||
|
||||
// Destroy on any trigger collision
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.TryGetComponent<IDamageable>(out var damageable))
|
||||
{
|
||||
damageable.TakeDamage(damage);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Component Setup**:
|
||||
- SphereCollider: `IsTrigger = false` (solid bullet)
|
||||
- Rigidbody: `Use Gravity = false`, mass = 0.01
|
||||
- Layer: "Bullet" (custom layer)
|
||||
|
||||
**Target (Barrel)**:
|
||||
```csharp
|
||||
public class Barrel : MonoBehaviour, IDamageable
|
||||
{
|
||||
[SerializeField] private int health = 1;
|
||||
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
health -= damage;
|
||||
if (health <= 0)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
}
|
||||
|
||||
private void Explode()
|
||||
{
|
||||
// Spawn explosion effect
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Component Setup**:
|
||||
- CapsuleCollider: `IsTrigger = true` (ghost collider for bullets)
|
||||
- Rigidbody: Required for trigger detection
|
||||
- Layer: "Damageable"
|
||||
|
||||
## Rigidbody Requirements
|
||||
|
||||
Both triggers and collisions require **at least one Rigidbody**.
|
||||
|
||||
### Valid Combinations
|
||||
|
||||
| Object A | Object B | Collision Detected? | Trigger Detected? |
|
||||
|----------|----------|---------------------|-------------------|
|
||||
| Rigidbody + Collider | Static Collider | ✅ Yes | ✅ Yes |
|
||||
| Rigidbody + Trigger | Static Collider | ❌ No | ✅ Yes |
|
||||
| Rigidbody + Collider | Rigidbody + Collider | ✅ Yes | ✅ Yes |
|
||||
| Static Collider | Static Collider | ❌ No | ❌ No |
|
||||
| Static Trigger | Static Collider | ❌ No | ❌ No |
|
||||
|
||||
**Rule**: At least one object must have a Rigidbody
|
||||
|
||||
### Kinematic Rigidbodies
|
||||
|
||||
```csharp
|
||||
rb.isKinematic = true;
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
- Not affected by physics forces
|
||||
- Can be moved via transform
|
||||
- Still participates in collisions/triggers
|
||||
- Good for moving platforms, elevators
|
||||
|
||||
**Use Cases**:
|
||||
- Character controllers (root motion)
|
||||
- Moving platforms
|
||||
- Doors and elevators
|
||||
- Objects that move via animation
|
||||
|
||||
## Multiple Colliders
|
||||
|
||||
You can have multiple colliders on one GameObject:
|
||||
|
||||
```csharp
|
||||
// Example: Enemy with both trigger and collision
|
||||
Enemy (Root)
|
||||
├── BoxCollider (IsTrigger = false) // Physics body
|
||||
└── SphereCollider (IsTrigger = true) // Detection zone
|
||||
|
||||
// Example: Barrel with layered detection
|
||||
Barrel (Root)
|
||||
├── CapsuleCollider (IsTrigger = true) // Bullet detection
|
||||
└── BoxCollider (IsTrigger = false) // Physics with environment
|
||||
```
|
||||
|
||||
**Use Cases**:
|
||||
- Detection zones around physical objects
|
||||
- Different collision layers
|
||||
- Separate bullet vs melee detection
|
||||
|
||||
## Layer-Based Collision
|
||||
|
||||
Configure which layers interact:
|
||||
|
||||
**Edit → Project Settings → Physics → Layer Collision Matrix**
|
||||
|
||||
```csharp
|
||||
// Example layer setup
|
||||
Layer 6: Bullet
|
||||
Layer 7: Enemy
|
||||
Layer 8: Environment
|
||||
|
||||
// Disable bullet-bullet collisions
|
||||
// Enable bullet-enemy collisions
|
||||
// Enable bullet-environment collisions
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Performance optimization
|
||||
- Prevents unwanted interactions
|
||||
- Clear collision logic
|
||||
|
||||
## Physics Materials
|
||||
|
||||
Control bounciness and friction:
|
||||
|
||||
```csharp
|
||||
// Create Physics Material
|
||||
// Right-click → Create → Physics Material
|
||||
|
||||
PhysicMaterial bouncyMaterial = new PhysicMaterial();
|
||||
bouncyMaterial.bounciness = 0.8f;
|
||||
bouncyMaterial.dynamicFriction = 0.3f;
|
||||
bouncyMaterial.staticFriction = 0.3f;
|
||||
bouncyMaterial.frictionCombine = PhysicMaterialCombine.Average;
|
||||
bouncyMaterial.bounceCombine = PhysicMaterialCombine.Maximum;
|
||||
|
||||
// Assign to collider
|
||||
GetComponent<Collider>().material = bouncyMaterial;
|
||||
```
|
||||
|
||||
**Properties**:
|
||||
- **Bounciness**: 0 (no bounce) to 1 (perfect bounce)
|
||||
- **Dynamic Friction**: Friction when moving
|
||||
- **Static Friction**: Friction when stationary
|
||||
- **Combine**: How materials combine
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Best Practices
|
||||
|
||||
**For Bullets**:
|
||||
✅ Use triggers (cheaper than collisions)
|
||||
✅ Use object pooling (avoid Instantiate/Destroy)
|
||||
✅ Set short lifetime (clean up automatically)
|
||||
✅ Layer-based collision matrix (avoid unnecessary checks)
|
||||
|
||||
**For Static Objects**:
|
||||
✅ No Rigidbody if never moving
|
||||
✅ Combine meshes when possible
|
||||
✅ Use simple collider shapes (box, sphere, capsule)
|
||||
|
||||
**For Physics Objects**:
|
||||
✅ Use appropriate collision detection mode
|
||||
✅ Disable gravity if not needed
|
||||
✅ Set objects to sleep when stationary
|
||||
✅ Use compound colliders sparingly
|
||||
|
||||
### Collision Detection Mode by Use Case
|
||||
|
||||
| Use Case | Detection Mode |
|
||||
|----------|---------------|
|
||||
| Slow-moving objects | Discrete |
|
||||
| Fast bullets | Continuous |
|
||||
| Player/Characters | ContinuousSpeculative |
|
||||
| Static objects | N/A (no Rigidbody) |
|
||||
| Kinematic controllers | Discrete |
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Projectile Pattern
|
||||
|
||||
```csharp
|
||||
// Fast bullet: Use trigger on target
|
||||
Bullet: Solid collider + Rigidbody
|
||||
Target: Trigger collider + Rigidbody
|
||||
|
||||
// Why: Guaranteed detection, good performance
|
||||
```
|
||||
|
||||
### Pickup Pattern
|
||||
|
||||
```csharp
|
||||
// Pickup item: Use trigger
|
||||
Pickup: Trigger collider + Rigidbody
|
||||
Player: Solid collider + Rigidbody
|
||||
|
||||
// Why: No physics interaction needed, detection only
|
||||
```
|
||||
|
||||
### Explosive Force Pattern
|
||||
|
||||
```csharp
|
||||
// Explosion affects nearby objects
|
||||
public void Explode()
|
||||
{
|
||||
Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
|
||||
foreach (Collider col in colliders)
|
||||
{
|
||||
if (col.TryGetComponent<Rigidbody>(out var rb))
|
||||
{
|
||||
rb.AddExplosionForce(force, transform.position, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Physics
|
||||
|
||||
### Visualize Colliders
|
||||
|
||||
```csharp
|
||||
// Gizmos in Scene view
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireSphere(transform.position, radius);
|
||||
}
|
||||
|
||||
// Debug rays
|
||||
Debug.DrawRay(origin, direction * distance, Color.red, duration);
|
||||
```
|
||||
|
||||
### Physics Debugger
|
||||
|
||||
**Window → Analysis → Physics Debugger**
|
||||
|
||||
Shows:
|
||||
- All Rigidbodies
|
||||
- Collision shapes
|
||||
- Contacts
|
||||
- Forces
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Objects fall through floor**:
|
||||
- Check collision layers
|
||||
- Verify Rigidbody settings
|
||||
- Check for kinematic flag
|
||||
- Ensure colliders are large enough
|
||||
|
||||
**Triggers not detecting**:
|
||||
- Verify at least one Rigidbody
|
||||
- Check layer collision matrix
|
||||
- Ensure `IsTrigger = true`
|
||||
- Check trigger callback methods exist
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Unity Manual - Physics](https://docs.unity3d.com/Manual/PhysicsSection.html)
|
||||
- [Unity Manual - Colliders](https://docs.unity3d.com/Manual/CollidersOverview.html)
|
||||
- [Unity Manual - Rigidbody](https://docs.unity3d.com/Manual/class-Rigidbody.html)
|
||||
- [Physics Best Practices](https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity6.html)
|
||||
|
|
@ -0,0 +1,610 @@
|
|||
# Unity Project Setup & Best Practices
|
||||
|
||||
## Overview
|
||||
|
||||
Proper project setup prevents most common Unity errors. This guide covers initial configuration, workflows, and best practices for Unity projects with version control.
|
||||
|
||||
## Project Setup Checklist
|
||||
|
||||
### Before Starting Development
|
||||
|
||||
#### 1. Choose Render Pipeline
|
||||
- [ ] **Built-in**: Legacy, simple projects
|
||||
- [ ] **URP**: Modern, cross-platform (recommended)
|
||||
- [ ] **HDRP**: High-end PC/console only
|
||||
|
||||
**If using URP**:
|
||||
- [ ] Install URP package
|
||||
- [ ] Create URP asset
|
||||
- [ ] Assign in Graphics settings
|
||||
- [ ] Verify renderer configured
|
||||
|
||||
→ See `reference/urp-setup.md` for details
|
||||
|
||||
#### 2. Configure Input System
|
||||
- [ ] **Legacy (0)**: Old projects only
|
||||
- [ ] **New (1)**: New projects with new system
|
||||
- [ ] **Both (2)**: Migration or compatibility (recommended)
|
||||
|
||||
**Location**: Edit → Project Settings → Player → Active Input Handling
|
||||
|
||||
**Restart Unity after changing!**
|
||||
|
||||
→ See `reference/input-systems.md` for details
|
||||
|
||||
#### 3. Set Up Version Control
|
||||
|
||||
**Git Configuration**:
|
||||
|
||||
Create `.gitignore`:
|
||||
```gitignore
|
||||
# Unity generated
|
||||
/[Ll]ibrary/
|
||||
/[Tt]emp/
|
||||
/[Oo]bj/
|
||||
/[Bb]uild/
|
||||
/[Bb]uilds/
|
||||
/[Ll]ogs/
|
||||
/[Uu]ser[Ss]ettings/
|
||||
|
||||
# Asset meta files MUST be tracked
|
||||
# (Do NOT ignore *.meta files!)
|
||||
|
||||
# Visual Studio / VSCode
|
||||
.vs/
|
||||
.vscode/
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
```
|
||||
|
||||
**Critical**: Keep `.meta` files in version control!
|
||||
|
||||
→ See `reference/guid-system.md` for details
|
||||
|
||||
#### 4. Configure Editor Settings
|
||||
|
||||
**Edit → Preferences**:
|
||||
- [ ] External Script Editor: Set to VSCode/Rider/Visual Studio
|
||||
- [ ] Error Pause: Enable for debugging
|
||||
- [ ] Auto Refresh: Enable
|
||||
|
||||
**Edit → Project Settings → Editor**:
|
||||
- [ ] Asset Serialization Mode: **Force Text** (better for git)
|
||||
- [ ] Version Control Mode: **Visible Meta Files**
|
||||
|
||||
#### 5. Set Up Quality Settings
|
||||
|
||||
**Edit → Project Settings → Quality**:
|
||||
- [ ] Configure quality levels for platforms
|
||||
- [ ] Set shadow settings
|
||||
- [ ] Configure anti-aliasing
|
||||
- [ ] Assign URP asset (if using URP)
|
||||
|
||||
#### 6. Configure Physics
|
||||
|
||||
**Edit → Project Settings → Physics**:
|
||||
- [ ] Set up Layer Collision Matrix
|
||||
- [ ] Configure default solver iterations
|
||||
- [ ] Set Fixed Timestep (default: 0.02 = 50Hz)
|
||||
|
||||
#### 7. Set Up Layers
|
||||
|
||||
**Edit → Project Settings → Tags and Layers**:
|
||||
|
||||
Common layer structure:
|
||||
```
|
||||
Layer 0: Default
|
||||
Layer 6: Player
|
||||
Layer 7: Enemy
|
||||
Layer 8: Bullet
|
||||
Layer 9: Environment
|
||||
Layer 10: UI
|
||||
Layer 11: Ignore Raycast
|
||||
```
|
||||
|
||||
**Configure collision matrix**: Physics → Layer Collision Matrix
|
||||
|
||||
---
|
||||
|
||||
## Folder Structure
|
||||
|
||||
### Recommended Organization
|
||||
|
||||
```
|
||||
Assets/
|
||||
├── Scenes/
|
||||
│ ├── MainMenu.unity
|
||||
│ └── Gameplay.unity
|
||||
├── Scripts/
|
||||
│ ├── Player/
|
||||
│ ├── Enemy/
|
||||
│ ├── UI/
|
||||
│ └── Utilities/
|
||||
├── Prefabs/
|
||||
│ ├── Characters/
|
||||
│ ├── Props/
|
||||
│ └── UI/
|
||||
├── Materials/
|
||||
├── Textures/
|
||||
├── Models/
|
||||
├── Audio/
|
||||
│ ├── Music/
|
||||
│ └── SFX/
|
||||
├── Animations/
|
||||
├── Resources/ # For Resources.Load()
|
||||
│ └── Data/
|
||||
└── Settings/
|
||||
└── URP/ (or HDRP/)
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
✅ **Do**:
|
||||
- Organize by feature or content type
|
||||
- Keep folder names clear and consistent
|
||||
- Use Resources folder sparingly
|
||||
- Separate art assets from code
|
||||
|
||||
❌ **Don't**:
|
||||
- Mix unrelated assets in one folder
|
||||
- Create deep nested hierarchies (>5 levels)
|
||||
- Put everything in root Assets folder
|
||||
|
||||
---
|
||||
|
||||
## Git Workflows
|
||||
|
||||
### Adding New Assets
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Create Asset in Unity] --> B[Unity generates .meta]
|
||||
B --> C[git status - verify both files]
|
||||
C --> D[git add Asset + Asset.meta]
|
||||
D --> E[git commit]
|
||||
```
|
||||
|
||||
**Commands**:
|
||||
```bash
|
||||
# Check what's new
|
||||
git status
|
||||
|
||||
# Stage both asset and meta
|
||||
git add Assets/NewScript.cs
|
||||
git add Assets/NewScript.cs.meta
|
||||
|
||||
# Or stage all new files
|
||||
git add Assets/
|
||||
|
||||
# Commit
|
||||
git commit -m "Add NewScript"
|
||||
```
|
||||
|
||||
### Deleting Assets
|
||||
|
||||
**Via Unity Editor** (Recommended):
|
||||
1. Delete in Project window
|
||||
2. Unity removes both files
|
||||
3. `git status` shows deletions
|
||||
4. `git add -A` to stage deletions
|
||||
5. Commit
|
||||
|
||||
**Manual deletion**:
|
||||
```bash
|
||||
# Delete both files
|
||||
rm Assets/Scripts/OldScript.cs
|
||||
rm Assets/Scripts/OldScript.cs.meta
|
||||
|
||||
# Stage deletions
|
||||
git add -A
|
||||
|
||||
# Or explicitly
|
||||
git rm Assets/Scripts/OldScript.cs
|
||||
git rm Assets/Scripts/OldScript.cs.meta
|
||||
|
||||
# Commit
|
||||
git commit -m "Remove OldScript"
|
||||
```
|
||||
|
||||
### Checking for Issues
|
||||
|
||||
```bash
|
||||
# Find orphaned .meta files
|
||||
for meta in Assets/**/*.meta; do
|
||||
[ -f "${meta%.meta}" ] || echo "Orphaned: $meta"
|
||||
done
|
||||
|
||||
# Find assets without .meta files
|
||||
find Assets -type f ! -name "*.meta" ! -path "*/.*" | while read file; do
|
||||
[ -f "$file.meta" ] || echo "Missing .meta: $file"
|
||||
done
|
||||
|
||||
# Check for untracked .meta files
|
||||
git status | grep "\.meta$"
|
||||
```
|
||||
|
||||
### Merge Conflicts
|
||||
|
||||
#### Scene/Prefab Conflicts
|
||||
|
||||
Unity scenes/prefabs are YAML but complex. Merge conflicts can break them.
|
||||
|
||||
**Strategy**:
|
||||
1. **Use Smart Merge** (Unity's tool)
|
||||
2. **Or choose one version** (theirs or yours)
|
||||
3. **Or recreate changes manually**
|
||||
|
||||
**Unity Smart Merge** (UnityYAMLMerge):
|
||||
```bash
|
||||
# Configure git to use Unity's merge tool
|
||||
# In .git/config or ~/.gitconfig:
|
||||
|
||||
[merge]
|
||||
tool = unityyamlmerge
|
||||
|
||||
[mergetool "unityyamlmerge"]
|
||||
trustExitCode = false
|
||||
cmd = 'C:\\Program Files\\Unity\\Editor\\Data\\Tools\\UnityYAMLMerge.exe' merge -p "$BASE" "$REMOTE" "$LOCAL" "$MERGED"
|
||||
```
|
||||
|
||||
**Alternative**: Use a staging/prefab variant workflow to avoid conflicts
|
||||
|
||||
#### Script Conflicts
|
||||
|
||||
Standard git merge works fine for `.cs` files. Resolve conflicts manually.
|
||||
|
||||
---
|
||||
|
||||
## Asset Naming Conventions
|
||||
|
||||
### Files and Assets
|
||||
|
||||
| Asset Type | Convention | Example |
|
||||
|------------|-----------|---------|
|
||||
| Scripts | PascalCase | `PlayerController.cs` |
|
||||
| Prefabs | PascalCase | `EnemyGoblin.prefab` |
|
||||
| Scenes | PascalCase | `MainMenu.unity` |
|
||||
| Materials | PascalCase_Mat | `Metal_Mat.mat` |
|
||||
| Textures | PascalCase_Tex | `Brick_Diffuse_Tex.png` |
|
||||
| Audio | PascalCase | `Explosion_01.wav` |
|
||||
| Animations | PascalCase_Anim | `Walk_Anim.anim` |
|
||||
|
||||
### GameObjects in Scene
|
||||
|
||||
- **Descriptive names**: `Player`, `MainCamera`, `EnemySpawner`
|
||||
- **Hierarchy markers**: Use empty GameObjects as folders
|
||||
- **Consistent naming**: Follow team conventions
|
||||
|
||||
---
|
||||
|
||||
## Code Quality Practices
|
||||
|
||||
### Script Organization
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem; // External
|
||||
using System.Collections.Generic; // System
|
||||
|
||||
namespace MyGame.Player // Namespace (optional but recommended)
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls player movement and input.
|
||||
/// </summary>
|
||||
public class PlayerController : MonoBehaviour
|
||||
{
|
||||
#region Serialized Fields
|
||||
[Header("Movement")]
|
||||
[SerializeField] private float speed = 5f;
|
||||
[SerializeField] private float jumpForce = 10f;
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private Rigidbody rb;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private bool isGrounded;
|
||||
private Vector3 moveDirection;
|
||||
#endregion
|
||||
|
||||
#region Unity Callbacks
|
||||
private void Awake()
|
||||
{
|
||||
// Component caching
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Initialization
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Input and per-frame logic
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
// Physics logic
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
// Public interface
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private void HandleMovement()
|
||||
{
|
||||
// Internal logic
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
✅ **Do**:
|
||||
- Use `[SerializeField]` instead of public fields
|
||||
- Add `[Header]` for organization in Inspector
|
||||
- Cache component references in Awake/Start
|
||||
- Use descriptive variable names
|
||||
- Add XML comments for public APIs
|
||||
- Use regions to organize code (optional)
|
||||
|
||||
❌ **Don't**:
|
||||
- Use `public` fields (use `[SerializeField] private` instead)
|
||||
- Call `GetComponent` in Update
|
||||
- Use `GameObject.Find` or `GameObject.FindWithTag` in Update
|
||||
- Leave unused variables (CS0414 warning)
|
||||
- Mix initialization code in Update
|
||||
|
||||
### Type Hints
|
||||
|
||||
Always use type hints:
|
||||
```csharp
|
||||
// ✓ Good
|
||||
private Rigidbody rb;
|
||||
private PlayerController playerController;
|
||||
private List<Enemy> enemies;
|
||||
|
||||
// ❌ Bad (no type hints)
|
||||
private var rb; // Not valid C#, but illustrates point
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Best Practices
|
||||
|
||||
### General Optimization
|
||||
|
||||
**Object Pooling**:
|
||||
```csharp
|
||||
// Instead of Instantiate/Destroy in loops
|
||||
public class BulletPool : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject bulletPrefab;
|
||||
private Queue<GameObject> pool = new Queue<GameObject>();
|
||||
|
||||
public GameObject GetBullet()
|
||||
{
|
||||
if (pool.Count > 0)
|
||||
{
|
||||
GameObject bullet = pool.Dequeue();
|
||||
bullet.SetActive(true);
|
||||
return bullet;
|
||||
}
|
||||
return Instantiate(bulletPrefab);
|
||||
}
|
||||
|
||||
public void ReturnBullet(GameObject bullet)
|
||||
{
|
||||
bullet.SetActive(false);
|
||||
pool.Enqueue(bullet);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Cache References**:
|
||||
```csharp
|
||||
// ❌ Bad - GetComponent every frame
|
||||
void Update()
|
||||
{
|
||||
GetComponent<Rigidbody>().linearVelocity = Vector3.forward;
|
||||
}
|
||||
|
||||
// ✓ Good - Cache in Awake
|
||||
private Rigidbody rb;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
rb = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
rb.linearVelocity = Vector3.forward;
|
||||
}
|
||||
```
|
||||
|
||||
**Avoid Find in Update**:
|
||||
```csharp
|
||||
// ❌ Bad - Find every frame
|
||||
void Update()
|
||||
{
|
||||
GameObject player = GameObject.FindWithTag("Player");
|
||||
}
|
||||
|
||||
// ✓ Good - Find once
|
||||
private GameObject player;
|
||||
|
||||
void Start()
|
||||
{
|
||||
player = GameObject.FindWithTag("Player");
|
||||
}
|
||||
```
|
||||
|
||||
### Physics Optimization
|
||||
|
||||
- Use simple colliders (box, sphere, capsule)
|
||||
- Use triggers instead of collisions when possible
|
||||
- Configure layer collision matrix
|
||||
- Set objects to sleep when stationary
|
||||
- Use continuous collision detection only when needed
|
||||
|
||||
→ See `reference/physics-system.md` for details
|
||||
|
||||
### Rendering Optimization
|
||||
|
||||
- Enable Static Batching for static objects
|
||||
- Use GPU Instancing on materials
|
||||
- Use LOD (Level of Detail) for complex models
|
||||
- Limit real-time lights
|
||||
- Use texture compression
|
||||
- Optimize shadow settings
|
||||
|
||||
→ See `reference/urp-setup.md` for details
|
||||
|
||||
---
|
||||
|
||||
## Testing and Debugging
|
||||
|
||||
### Unity Test Framework
|
||||
|
||||
**Install Test Framework**:
|
||||
- Window → Package Manager → Test Framework → Install
|
||||
|
||||
**Create Tests**:
|
||||
```csharp
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerTests
|
||||
{
|
||||
[Test]
|
||||
public void PlayerTakesDamage()
|
||||
{
|
||||
var player = new GameObject().AddComponent<Player>();
|
||||
player.TakeDamage(10);
|
||||
Assert.AreEqual(90, player.Health);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Profiling
|
||||
|
||||
**Window → Analysis → Profiler**
|
||||
|
||||
Monitor:
|
||||
- CPU usage
|
||||
- Rendering time
|
||||
- Physics time
|
||||
- Memory allocations
|
||||
- GC (Garbage Collection)
|
||||
|
||||
---
|
||||
|
||||
## Build Settings
|
||||
|
||||
### Platform-Specific Settings
|
||||
|
||||
**Android**:
|
||||
- API Level: 24+ (Android 7.0+)
|
||||
- Scripting Backend: IL2CPP (better performance)
|
||||
- Target Architectures: ARMv7 + ARM64
|
||||
|
||||
**iOS**:
|
||||
- Target SDK: Latest
|
||||
- Architecture: ARM64
|
||||
- Camera Usage Description: Required for camera access
|
||||
|
||||
**PC (Windows/Mac/Linux)**:
|
||||
- API Compatibility: .NET Standard 2.1
|
||||
- Scripting Backend: Mono (faster builds) or IL2CPP (better runtime)
|
||||
|
||||
### Development vs Release
|
||||
|
||||
**Development Build**:
|
||||
- Enable for testing
|
||||
- Shows profiler stats
|
||||
- Allows debugger attachment
|
||||
|
||||
**Release Build**:
|
||||
- Disable development build
|
||||
- Enable optimization
|
||||
- Strip debug symbols
|
||||
|
||||
---
|
||||
|
||||
## Project Maintenance
|
||||
|
||||
### Regular Tasks
|
||||
|
||||
**Weekly**:
|
||||
- [ ] Run tests
|
||||
- [ ] Check for Unity updates
|
||||
- [ ] Review Console warnings
|
||||
- [ ] Update packages (carefully!)
|
||||
|
||||
**Monthly**:
|
||||
- [ ] Clean unused assets
|
||||
- [ ] Optimize texture sizes
|
||||
- [ ] Review performance in Profiler
|
||||
- [ ] Backup project
|
||||
|
||||
**Before Release**:
|
||||
- [ ] Fix ALL warnings
|
||||
- [ ] Remove debug code
|
||||
- [ ] Test on target devices
|
||||
- [ ] Verify build size
|
||||
- [ ] Test loading times
|
||||
- [ ] Profile performance
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### When Upgrading Unity Version
|
||||
|
||||
1. **Backup project** (entire folder + git commit)
|
||||
2. **Read release notes** (API changes, breaking changes)
|
||||
3. **Test in new version** (create copy first!)
|
||||
4. **Fix deprecation warnings**
|
||||
5. **Test thoroughly** (all features, all platforms)
|
||||
6. **Update packages** (check compatibility)
|
||||
7. **Commit changes** (separate from feature work)
|
||||
|
||||
### When Switching Render Pipeline
|
||||
|
||||
1. **Backup project**
|
||||
2. **Install new pipeline package**
|
||||
3. **Create pipeline asset**
|
||||
4. **Upgrade materials** (automatic tool)
|
||||
5. **Fix pink materials** (manual shader changes)
|
||||
6. **Reconfigure lighting**
|
||||
7. **Test performance**
|
||||
8. **Fix post-processing** (different systems)
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Unity Manual - Project Setup](https://docs.unity3d.com/Manual/CreatingProjects.html)
|
||||
- [Unity Manual - Version Control](https://docs.unity3d.com/Manual/Versioncontrol.html)
|
||||
- [Git for Unity](https://thoughtbot.com/blog/how-to-git-with-unity)
|
||||
- [Unity Best Practices](https://unity.com/how-to/programming-unity)
|
||||
- [Performance Optimization](https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity6.html)
|
||||
|
|
@ -0,0 +1,481 @@
|
|||
# Universal Render Pipeline (URP) Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Universal Render Pipeline (URP) is Unity's modern, performant render pipeline. It requires additional configuration compared to the built-in render pipeline, especially for cameras and lighting.
|
||||
|
||||
## Render Pipeline Comparison
|
||||
|
||||
| Feature | Built-in RP | URP | HDRP |
|
||||
|---------|------------|-----|------|
|
||||
| **Performance** | Moderate | High | Moderate (high-end) |
|
||||
| **Platforms** | All | All | High-end only |
|
||||
| **Camera setup** | Simple | Requires URP data | Requires HD data |
|
||||
| **Lighting** | Traditional | Scriptable | Advanced |
|
||||
| **Target** | Legacy | Mobile/Cross-platform | High-end PC/Console |
|
||||
|
||||
## Common URP Camera Error
|
||||
|
||||
### Camera Data is Null
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Camera data is null. Please ensure the render pipeline is correctly initialized
|
||||
and the camera has a UniversalAdditionalCameraData component.
|
||||
```
|
||||
|
||||
**Root Cause**: Camera's renderer index set to `-1` (invalid) in URP scene
|
||||
|
||||
**Location**: `.unity` scene file
|
||||
|
||||
**Problematic Configuration**:
|
||||
```yaml
|
||||
--- !u!20 &1234567890
|
||||
Camera:
|
||||
m_GameObject: {fileID: 1234567889}
|
||||
m_Enabled: 1
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_RendererIndex: -1 # ❌ INVALID - causes error
|
||||
```
|
||||
|
||||
**Solution**: Change `m_RendererIndex` from `-1` to `0`
|
||||
|
||||
```yaml
|
||||
m_RendererIndex: 0 # ✅ Valid - uses first renderer in URP asset
|
||||
```
|
||||
|
||||
### Why This Happens
|
||||
|
||||
When creating cameras manually (outside Unity Editor):
|
||||
- Default renderer index may be `-1`
|
||||
- URP requires valid renderer index (usually `0`)
|
||||
- Built-in RP ignores this field
|
||||
|
||||
**Best Practice**: Always create cameras via Unity Editor when using URP
|
||||
|
||||
## URP Project Setup
|
||||
|
||||
### 1. Install URP Package
|
||||
|
||||
**Via Package Manager**:
|
||||
1. **Window → Package Manager**
|
||||
2. Search for "Universal RP"
|
||||
3. Click **Install**
|
||||
|
||||
**Via manifest.json**:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.render-pipelines.universal": "14.0.8"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create URP Asset
|
||||
|
||||
**Create Pipeline Asset**:
|
||||
1. Right-click in Project → **Create → Rendering → URP Asset (with Universal Renderer)**
|
||||
2. Names generated:
|
||||
- `UniversalRenderPipelineAsset.asset` (main asset)
|
||||
- `UniversalRenderPipelineAsset_Renderer.asset` (renderer)
|
||||
|
||||
**Or create separately**:
|
||||
```
|
||||
Create → Rendering → URP Asset
|
||||
Create → Rendering → URP Forward Renderer
|
||||
```
|
||||
|
||||
### 3. Assign URP Asset
|
||||
|
||||
**Edit → Project Settings → Graphics**
|
||||
- Scriptable Render Pipeline Settings: Assign your URP asset
|
||||
|
||||
**Edit → Project Settings → Quality**
|
||||
- For each quality level: Assign URP asset (or leave default)
|
||||
|
||||
### 4. Verify Setup
|
||||
|
||||
Check that:
|
||||
- ✅ URP package installed
|
||||
- ✅ URP asset created
|
||||
- ✅ Graphics settings point to URP asset
|
||||
- ✅ Renderer assigned in URP asset
|
||||
- ✅ Scene has at least one light (URP handles lighting differently)
|
||||
|
||||
## Camera Setup in URP
|
||||
|
||||
### Via Unity Editor (Recommended)
|
||||
|
||||
**Create Main Camera**:
|
||||
1. **GameObject → Camera**
|
||||
2. Unity automatically adds:
|
||||
- `Camera` component
|
||||
- `UniversalAdditionalCameraData` component
|
||||
- Correct renderer index
|
||||
|
||||
**Properties**:
|
||||
```csharp
|
||||
// UniversalAdditionalCameraData added automatically
|
||||
// m_RendererIndex set to 0 by default
|
||||
// Camera configured for URP
|
||||
```
|
||||
|
||||
### Manual Camera Configuration
|
||||
|
||||
If creating cameras programmatically:
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public class CameraSetup : MonoBehaviour
|
||||
{
|
||||
void CreateURPCamera()
|
||||
{
|
||||
GameObject cameraObj = new GameObject("Main Camera");
|
||||
Camera cam = cameraObj.AddComponent<Camera>();
|
||||
|
||||
// Add URP camera data (required for URP)
|
||||
UniversalAdditionalCameraData cameraData = cameraObj.AddComponent<UniversalAdditionalCameraData>();
|
||||
|
||||
// Configure camera
|
||||
cam.clearFlags = CameraClearFlags.Skybox;
|
||||
cam.backgroundColor = new Color(0.19f, 0.30f, 0.47f);
|
||||
|
||||
// Tag as main camera
|
||||
cameraObj.tag = "MainCamera";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Camera Stacking in URP
|
||||
|
||||
URP supports camera stacking for overlay effects:
|
||||
|
||||
```csharp
|
||||
// Base camera
|
||||
Camera baseCamera = mainCameraObject.GetComponent<Camera>();
|
||||
UniversalAdditionalCameraData baseCameraData = baseCamera.GetUniversalAdditionalCameraData();
|
||||
baseCameraData.renderType = CameraRenderType.Base;
|
||||
|
||||
// Overlay camera (UI, effects, etc.)
|
||||
Camera overlayCamera = uiCameraObject.GetComponent<Camera>();
|
||||
UniversalAdditionalCameraData overlayCameraData = overlayCamera.GetUniversalAdditionalCameraData();
|
||||
overlayCameraData.renderType = CameraRenderType.Overlay;
|
||||
|
||||
// Add overlay to base camera stack
|
||||
baseCameraData.cameraStack.Add(overlayCamera);
|
||||
```
|
||||
|
||||
**Use Cases**:
|
||||
- UI rendering on separate layer
|
||||
- Post-processing effects
|
||||
- Split-screen multiplayer
|
||||
- Picture-in-picture
|
||||
|
||||
## URP Renderer Settings
|
||||
|
||||
### Forward Renderer
|
||||
|
||||
**Location**: URP Renderer Asset
|
||||
|
||||
**Key Settings**:
|
||||
- **Rendering Path**: Forward/Deferred
|
||||
- **Depth Texture**: Enable for effects that need depth
|
||||
- **Opaque Texture**: Enable for effects that need scene color
|
||||
- **Native RenderPass**: Performance optimization (mobile)
|
||||
|
||||
**Example**:
|
||||
```csharp
|
||||
// Access renderer settings
|
||||
UniversalRenderPipelineAsset urpAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
|
||||
ScriptableRenderer renderer = urpAsset.scriptableRenderer;
|
||||
```
|
||||
|
||||
### Render Features
|
||||
|
||||
Add custom render passes:
|
||||
|
||||
1. Open **URP Renderer Asset**
|
||||
2. **Add Renderer Feature**
|
||||
3. Choose feature (SSAO, Decals, etc.)
|
||||
|
||||
**Common Features**:
|
||||
- Screen Space Ambient Occlusion (SSAO)
|
||||
- Decals
|
||||
- Custom render passes
|
||||
|
||||
## Lighting in URP
|
||||
|
||||
### Light Types
|
||||
|
||||
| Light Type | Use Case | Performance |
|
||||
|-----------|----------|-------------|
|
||||
| Directional | Sun/Moon, outdoor scenes | Low cost |
|
||||
| Point | Torches, lamps, explosions | Medium cost |
|
||||
| Spot | Flashlights, streetlights | Medium cost |
|
||||
| Area | Baked lighting only | N/A (baked) |
|
||||
|
||||
### Shadows
|
||||
|
||||
**Per-Light Settings**:
|
||||
```csharp
|
||||
Light light = GetComponent<Light>();
|
||||
light.shadows = LightShadows.Soft; // None, Hard, Soft
|
||||
light.shadowStrength = 1.0f; // 0-1
|
||||
light.shadowBias = 0.05f; // Reduce shadow acne
|
||||
light.shadowNormalBias = 0.4f; // Reduce peter-panning
|
||||
```
|
||||
|
||||
**Global Settings** (URP Asset):
|
||||
- Main Light: Casts shadows
|
||||
- Additional Lights: Per-pixel or per-vertex
|
||||
- Shadow Resolution: 256/512/1024/2048/4096
|
||||
- Shadow Distance: Max distance for shadows
|
||||
|
||||
### Baked Lighting
|
||||
|
||||
For static objects:
|
||||
|
||||
1. Mark objects as **Static** (Lighting Static flag)
|
||||
2. **Window → Rendering → Lighting**
|
||||
3. Configure:
|
||||
- Lightmap Resolution
|
||||
- Lightmap Size
|
||||
- Directional Mode
|
||||
4. Click **Generate Lighting**
|
||||
|
||||
**Benefits**:
|
||||
- Better performance
|
||||
- High-quality shadows/GI
|
||||
- No runtime cost
|
||||
|
||||
## Post-Processing in URP
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Install Post-Processing package** (if not included):
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.postprocessing": "3.2.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add Volume component**:
|
||||
```
|
||||
GameObject → Volume → Global Volume
|
||||
```
|
||||
|
||||
3. **Create Volume Profile**:
|
||||
```
|
||||
Create → Volume Profile (URP)
|
||||
```
|
||||
|
||||
4. **Add Overrides**:
|
||||
- Bloom
|
||||
- Color Grading
|
||||
- Vignette
|
||||
- Depth of Field
|
||||
- Motion Blur
|
||||
- etc.
|
||||
|
||||
### Example: Bloom
|
||||
|
||||
```csharp
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public class PostProcessingSetup : MonoBehaviour
|
||||
{
|
||||
void SetupBloom()
|
||||
{
|
||||
Volume volume = GetComponent<Volume>();
|
||||
if (volume.profile.TryGet<Bloom>(out var bloom))
|
||||
{
|
||||
bloom.intensity.value = 0.3f;
|
||||
bloom.threshold.value = 1.0f;
|
||||
bloom.scatter.value = 0.7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Material Upgrade
|
||||
|
||||
When switching from Built-in to URP:
|
||||
|
||||
### Automatic Upgrade
|
||||
|
||||
**Edit → Render Pipeline → URP → Upgrade Project Materials to URP Materials**
|
||||
|
||||
- Converts Standard shaders to URP Lit
|
||||
- May need manual adjustment
|
||||
|
||||
### Manual Material Setup
|
||||
|
||||
```csharp
|
||||
// Built-in Standard shader
|
||||
Shader "Standard"
|
||||
|
||||
// URP equivalent
|
||||
Shader "Universal Render Pipeline/Lit"
|
||||
```
|
||||
|
||||
**Common Shaders**:
|
||||
- `Standard` → `URP/Lit`
|
||||
- `Standard (Specular)` → `URP/Lit` (change workflow)
|
||||
- `Unlit` → `URP/Unlit`
|
||||
- `Particles/Standard` → `URP/Particles/Lit`
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### URP Asset Settings
|
||||
|
||||
**Rendering**:
|
||||
- **Render Scale**: <1.0 for performance (0.5 = half resolution)
|
||||
- **MSAA**: Off/2x/4x/8x (mobile: 2x max)
|
||||
- **HDR**: Disable if not needed
|
||||
- **Depth Texture**: Disable if not needed
|
||||
|
||||
**Lighting**:
|
||||
- **Main Light**: Shadow casting
|
||||
- **Additional Lights**: Per-vertex is cheaper
|
||||
- **Max Lights**: Reduce for better performance
|
||||
- **Shadow Distance**: Lower is better
|
||||
|
||||
**Shadows**:
|
||||
- **Shadow Resolution**: Lower for performance
|
||||
- **Cascade Count**: 1 or 2 for mobile
|
||||
- **Soft Shadows**: Disable for performance
|
||||
|
||||
### Mobile Optimization
|
||||
|
||||
```csharp
|
||||
// Mobile-specific settings
|
||||
urpAsset.renderScale = 0.75f; // Slight resolution reduction
|
||||
urpAsset.msaaSampleCount = 2; // 2x MSAA (not 4x/8x)
|
||||
urpAsset.shadowDistance = 30f; // Shorter shadow distance
|
||||
urpAsset.shadowCascadeCount = 1; // Single cascade
|
||||
urpAsset.supportsHDR = false; // Disable HDR
|
||||
```
|
||||
|
||||
## Common URP Issues
|
||||
|
||||
### Scene Too Dark
|
||||
|
||||
**Causes**:
|
||||
- No lights in scene
|
||||
- Lights not configured for URP
|
||||
- Environment lighting disabled
|
||||
- Materials not URP-compatible
|
||||
|
||||
**Solutions**:
|
||||
1. Add Directional Light (sun)
|
||||
2. Enable **Window → Rendering → Lighting → Environment**
|
||||
3. Set Environment Lighting Source
|
||||
4. Upgrade materials to URP shaders
|
||||
|
||||
### Pink Materials
|
||||
|
||||
**Cause**: Shader not compatible with URP
|
||||
|
||||
**Solution**:
|
||||
1. Select material
|
||||
2. Change shader to URP equivalent
|
||||
3. Or run automatic upgrade tool
|
||||
|
||||
### Performance Issues
|
||||
|
||||
**Check**:
|
||||
- Render scale too high
|
||||
- Too many lights
|
||||
- Shadows too detailed
|
||||
- Post-processing too expensive
|
||||
- MSAA too high
|
||||
|
||||
**Profile**:
|
||||
- **Window → Analysis → Profiler**
|
||||
- Check "Rendering" section
|
||||
- Identify bottlenecks
|
||||
|
||||
## URP vs HDRP
|
||||
|
||||
| Feature | URP | HDRP |
|
||||
|---------|-----|------|
|
||||
| **Target** | Mobile, cross-platform | High-end PC/Console |
|
||||
| **Performance** | Optimized | Demanding |
|
||||
| **Features** | Essential | Advanced (ray tracing, etc.) |
|
||||
| **Complexity** | Moderate | High |
|
||||
| **File Size** | Smaller | Larger |
|
||||
|
||||
**Choose URP when**:
|
||||
- Targeting mobile
|
||||
- Want good performance
|
||||
- Need cross-platform support
|
||||
- Moderate visual quality sufficient
|
||||
|
||||
**Choose HDRP when**:
|
||||
- High-end PC/console only
|
||||
- Need cutting-edge visuals
|
||||
- Ray tracing required
|
||||
- Performance secondary
|
||||
|
||||
## Debugging URP
|
||||
|
||||
### Check URP Status
|
||||
|
||||
```csharp
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public class URPDebug : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
// Check if URP active
|
||||
if (GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset urpAsset)
|
||||
{
|
||||
Debug.Log("URP is active");
|
||||
Debug.Log($"Renderer: {urpAsset.scriptableRenderer}");
|
||||
Debug.Log($"MSAA: {urpAsset.msaaSampleCount}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("URP is not active!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Camera Diagnostics
|
||||
|
||||
```csharp
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
void DiagnoseCamera(Camera cam)
|
||||
{
|
||||
UniversalAdditionalCameraData cameraData = cam.GetUniversalAdditionalCameraData();
|
||||
|
||||
if (cameraData == null)
|
||||
{
|
||||
Debug.LogError($"Camera {cam.name} missing UniversalAdditionalCameraData!");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"Camera: {cam.name}");
|
||||
Debug.Log($"Render Type: {cameraData.renderType}");
|
||||
Debug.Log($"Render Post Processing: {cameraData.renderPostProcessing}");
|
||||
Debug.Log($"Anti-aliasing: {cameraData.antialiasing}");
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [URP Documentation](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest)
|
||||
- [URP vs HDRP Comparison](https://unity.com/srp/choosing-a-render-pipeline)
|
||||
- [Upgrading to URP](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/manual/InstallURPIntoAProject.html)
|
||||
- [Camera Stacking](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/manual/camera-stacking.html)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
Unity3D Enhanced Scripts
|
||||
|
||||
Utility scripts for Unity project maintenance and debugging.
|
||||
"""
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
GUID Validator for Unity Projects
|
||||
|
||||
Validates GUIDs in Unity asset files and meta files, identifying common issues:
|
||||
- Invalid GUID format (non-hexadecimal characters)
|
||||
- Duplicate GUIDs across meta files
|
||||
- Missing or orphaned meta files
|
||||
- Broken asset references
|
||||
|
||||
Usage:
|
||||
python guid_validator.py <unity_project_path>
|
||||
python guid_validator.py <unity_project_path> --fix-orphaned
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def is_valid_guid(guid):
|
||||
"""Check if GUID is valid 32-character hexadecimal string."""
|
||||
return bool(re.match(r"^[0-9a-f]{32}$", guid))
|
||||
|
||||
|
||||
def find_meta_files(project_path):
|
||||
"""Find all .meta files in the Assets directory."""
|
||||
assets_path = Path(project_path) / "Assets"
|
||||
if not assets_path.exists():
|
||||
raise ValueError(f"Assets directory not found: {assets_path}")
|
||||
return list(assets_path.rglob("*.meta"))
|
||||
|
||||
|
||||
def extract_guid_from_meta(meta_path):
|
||||
"""Extract GUID from a .meta file."""
|
||||
try:
|
||||
with open(meta_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if line.startswith("guid:"):
|
||||
guid = line.split(":", 1)[1].strip()
|
||||
return guid
|
||||
except Exception as e:
|
||||
print(f"Error reading {meta_path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def find_asset_references(project_path, guid):
|
||||
"""Find all references to a GUID in scene and prefab files."""
|
||||
assets_path = Path(project_path) / "Assets"
|
||||
references = []
|
||||
|
||||
for ext in ["*.unity", "*.prefab", "*.asset", "*.mat"]:
|
||||
for file_path in assets_path.rglob(ext):
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if f"guid: {guid}" in content:
|
||||
references.append(file_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return references
|
||||
|
||||
|
||||
def check_orphaned_meta_files(meta_files):
|
||||
"""Find meta files without corresponding assets."""
|
||||
orphaned = []
|
||||
for meta_path in meta_files:
|
||||
asset_path = Path(str(meta_path)[:-5]) # Remove .meta extension
|
||||
if not asset_path.exists():
|
||||
orphaned.append(meta_path)
|
||||
return orphaned
|
||||
|
||||
|
||||
def check_missing_meta_files(project_path):
|
||||
"""Find assets without corresponding meta files."""
|
||||
assets_path = Path(project_path) / "Assets"
|
||||
missing = []
|
||||
|
||||
# Skip directories that shouldn't have meta files
|
||||
skip_dirs = {".git", ".vs", "Library", "Temp", "obj", "Logs"}
|
||||
|
||||
for asset_path in assets_path.rglob("*"):
|
||||
if asset_path.is_file() and not asset_path.name.endswith(".meta"):
|
||||
# Skip files in excluded directories
|
||||
if any(skip_dir in asset_path.parts for skip_dir in skip_dirs):
|
||||
continue
|
||||
|
||||
meta_path = Path(str(asset_path) + ".meta")
|
||||
if not meta_path.exists():
|
||||
missing.append(asset_path)
|
||||
|
||||
return missing
|
||||
|
||||
|
||||
def validate_guids(project_path, fix_orphaned=False):
|
||||
"""Validate all GUIDs in a Unity project."""
|
||||
print(f"Validating Unity project: {project_path}\n")
|
||||
|
||||
meta_files = find_meta_files(project_path)
|
||||
print(f"Found {len(meta_files)} meta files\n")
|
||||
|
||||
# Track GUIDs
|
||||
guid_to_files = defaultdict(list)
|
||||
invalid_guids = []
|
||||
|
||||
# Check each meta file
|
||||
for meta_path in meta_files:
|
||||
guid = extract_guid_from_meta(meta_path)
|
||||
if guid:
|
||||
if is_valid_guid(guid):
|
||||
guid_to_files[guid].append(meta_path)
|
||||
else:
|
||||
invalid_guids.append((meta_path, guid))
|
||||
|
||||
# Report invalid GUIDs
|
||||
if invalid_guids:
|
||||
print("❌ INVALID GUIDs FOUND:")
|
||||
for meta_path, guid in invalid_guids:
|
||||
print(f" {meta_path}")
|
||||
print(f" Invalid GUID: {guid}")
|
||||
print(f" Must be 32 hexadecimal characters (0-9, a-f)")
|
||||
print()
|
||||
else:
|
||||
print("✓ All GUIDs have valid format\n")
|
||||
|
||||
# Report duplicate GUIDs
|
||||
duplicates = {
|
||||
guid: files for guid, files in guid_to_files.items() if len(files) > 1
|
||||
}
|
||||
if duplicates:
|
||||
print("❌ DUPLICATE GUIDs FOUND:")
|
||||
for guid, files in duplicates.items():
|
||||
print(f" GUID: {guid}")
|
||||
for file_path in files:
|
||||
print(f" - {file_path}")
|
||||
print()
|
||||
else:
|
||||
print("✓ No duplicate GUIDs\n")
|
||||
|
||||
# Check for orphaned meta files
|
||||
orphaned = check_orphaned_meta_files(meta_files)
|
||||
if orphaned:
|
||||
print(f"⚠ ORPHANED META FILES ({len(orphaned)}):")
|
||||
for meta_path in orphaned:
|
||||
print(f" {meta_path}")
|
||||
guid = extract_guid_from_meta(meta_path)
|
||||
if guid:
|
||||
refs = find_asset_references(project_path, guid)
|
||||
if refs:
|
||||
print(f" Referenced by: {len(refs)} file(s)")
|
||||
print()
|
||||
|
||||
if fix_orphaned:
|
||||
print("Removing orphaned meta files...")
|
||||
for meta_path in orphaned:
|
||||
meta_path.unlink()
|
||||
print(f" Deleted: {meta_path}")
|
||||
print()
|
||||
else:
|
||||
print("✓ No orphaned meta files\n")
|
||||
|
||||
# Check for missing meta files
|
||||
missing = check_missing_meta_files(project_path)
|
||||
if missing:
|
||||
print(f"⚠ MISSING META FILES ({len(missing)}):")
|
||||
for asset_path in missing[:10]: # Show first 10
|
||||
print(f" {asset_path}")
|
||||
if len(missing) > 10:
|
||||
print(f" ... and {len(missing) - 10} more")
|
||||
print("\nFix: Open project in Unity Editor to regenerate meta files\n")
|
||||
else:
|
||||
print("✓ No missing meta files\n")
|
||||
|
||||
# Summary
|
||||
has_errors = bool(invalid_guids or duplicates or orphaned or missing)
|
||||
if has_errors:
|
||||
print("=" * 60)
|
||||
print("VALIDATION FAILED - Issues found")
|
||||
print("=" * 60)
|
||||
return 1
|
||||
else:
|
||||
print("=" * 60)
|
||||
print("✓ VALIDATION PASSED - No issues found")
|
||||
print("=" * 60)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
project_path = sys.argv[1]
|
||||
fix_orphaned = "--fix-orphaned" in sys.argv
|
||||
|
||||
try:
|
||||
exit_code = validate_guids(project_path, fix_orphaned=fix_orphaned)
|
||||
sys.exit(exit_code)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Meta File Synchronization Checker
|
||||
|
||||
Ensures meta files are properly synchronized with assets in version control.
|
||||
Helps prevent common Unity git workflow issues.
|
||||
|
||||
Usage:
|
||||
python meta_file_checker.py <unity_project_path>
|
||||
python meta_file_checker.py <unity_project_path> --check-git
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def run_git_command(cmd, cwd):
|
||||
"""Run a git command and return output."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, cwd=cwd, capture_output=True, text=True, check=True
|
||||
)
|
||||
return result.stdout
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
|
||||
def check_git_status(project_path):
|
||||
"""Check git status for meta file sync issues."""
|
||||
# Check if git repo
|
||||
git_dir = Path(project_path) / ".git"
|
||||
if not git_dir.exists():
|
||||
print("⚠ Not a git repository")
|
||||
return []
|
||||
|
||||
print("Checking git status for meta file issues...\n")
|
||||
|
||||
# Get staged files
|
||||
staged = run_git_command(["git", "diff", "--cached", "--name-only"], project_path)
|
||||
if not staged:
|
||||
print("✓ No staged changes\n")
|
||||
return []
|
||||
|
||||
staged_files = set(staged.strip().split("\n")) if staged.strip() else set()
|
||||
|
||||
issues = []
|
||||
|
||||
# Check for assets without meta files
|
||||
for file_path in staged_files:
|
||||
if file_path.startswith("Assets/") and not file_path.endswith(".meta"):
|
||||
full_path = Path(project_path) / file_path
|
||||
if full_path.exists() and full_path.is_file():
|
||||
meta_file = file_path + ".meta"
|
||||
if meta_file not in staged_files:
|
||||
issues.append(
|
||||
{
|
||||
"type": "missing_meta_staged",
|
||||
"file": file_path,
|
||||
"meta": meta_file,
|
||||
}
|
||||
)
|
||||
|
||||
# Check for meta files without assets
|
||||
for file_path in staged_files:
|
||||
if file_path.endswith(".meta"):
|
||||
asset_file = file_path[:-5] # Remove .meta
|
||||
full_asset_path = Path(project_path) / asset_file
|
||||
|
||||
if not full_asset_path.exists():
|
||||
issues.append(
|
||||
{
|
||||
"type": "orphaned_meta_staged",
|
||||
"file": file_path,
|
||||
"asset": asset_file,
|
||||
}
|
||||
)
|
||||
elif asset_file not in staged_files:
|
||||
issues.append(
|
||||
{
|
||||
"type": "meta_without_asset_staged",
|
||||
"file": file_path,
|
||||
"asset": asset_file,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_deleted_files(project_path):
|
||||
"""Check for deleted assets that still have meta files."""
|
||||
git_dir = Path(project_path) / ".git"
|
||||
if not git_dir.exists():
|
||||
return []
|
||||
|
||||
# Get deleted files from git status
|
||||
status = run_git_command(["git", "status", "--short"], project_path)
|
||||
if not status:
|
||||
return []
|
||||
|
||||
issues = []
|
||||
for line in status.split("\n"):
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
parts = line.strip().split(maxsplit=1)
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
|
||||
status_code, file_path = parts
|
||||
|
||||
# Check for deleted assets
|
||||
if (
|
||||
"D" in status_code
|
||||
and file_path.startswith("Assets/")
|
||||
and not file_path.endswith(".meta")
|
||||
):
|
||||
meta_file = Path(project_path) / (file_path + ".meta")
|
||||
if meta_file.exists():
|
||||
issues.append(
|
||||
{
|
||||
"type": "deleted_asset_has_meta",
|
||||
"file": file_path,
|
||||
"meta": str(meta_file.relative_to(project_path)),
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def print_issues(issues, deleted_issues):
|
||||
"""Print all detected issues."""
|
||||
if not issues and not deleted_issues:
|
||||
print("✓ No meta file synchronization issues found\n")
|
||||
return False
|
||||
|
||||
has_errors = False
|
||||
|
||||
if issues:
|
||||
print("❌ GIT STAGING ISSUES FOUND:\n")
|
||||
has_errors = True
|
||||
|
||||
for issue in issues:
|
||||
if issue["type"] == "missing_meta_staged":
|
||||
print(f" Asset staged without meta file:")
|
||||
print(f" Asset: {issue['file']}")
|
||||
print(f" Missing: {issue['meta']}")
|
||||
print(f" Fix: git add {issue['meta']}\n")
|
||||
|
||||
elif issue["type"] == "orphaned_meta_staged":
|
||||
print(f" Meta file staged without asset:")
|
||||
print(f" Meta: {issue['file']}")
|
||||
print(f" Missing asset: {issue['asset']}")
|
||||
print(f" Fix: git rm {issue['file']}\n")
|
||||
|
||||
elif issue["type"] == "meta_without_asset_staged":
|
||||
print(f" Meta file staged without corresponding asset:")
|
||||
print(f" Meta: {issue['file']}")
|
||||
print(f" Asset not staged: {issue['asset']}")
|
||||
print(f" Fix: git add {issue['asset']}\n")
|
||||
|
||||
if deleted_issues:
|
||||
print("❌ DELETED ASSET ISSUES:\n")
|
||||
has_errors = True
|
||||
|
||||
for issue in deleted_issues:
|
||||
print(f" Asset deleted but meta file remains:")
|
||||
print(f" Deleted: {issue['file']}")
|
||||
print(f" Orphaned meta: {issue['meta']}")
|
||||
print(f" Fix: git rm {issue['meta']}\n")
|
||||
|
||||
return has_errors
|
||||
|
||||
|
||||
def check_meta_sync(project_path, check_git=False):
|
||||
"""Check meta file synchronization."""
|
||||
project_path = Path(project_path)
|
||||
|
||||
if not project_path.exists():
|
||||
raise ValueError(f"Project path not found: {project_path}")
|
||||
|
||||
assets_path = project_path / "Assets"
|
||||
if not assets_path.exists():
|
||||
raise ValueError(f"Assets directory not found: {assets_path}")
|
||||
|
||||
print(f"Checking meta file synchronization: {project_path}\n")
|
||||
|
||||
issues = []
|
||||
deleted_issues = []
|
||||
|
||||
if check_git:
|
||||
issues = check_git_status(project_path)
|
||||
deleted_issues = check_deleted_files(project_path)
|
||||
|
||||
has_errors = print_issues(issues, deleted_issues)
|
||||
|
||||
# Summary
|
||||
if has_errors:
|
||||
print("=" * 60)
|
||||
print("SYNCHRONIZATION ISSUES FOUND")
|
||||
print("=" * 60)
|
||||
print("\nRecommended actions:")
|
||||
print(" 1. Always stage asset and meta file together")
|
||||
print(" 2. Use: git add Assets/path/to/file.ext Assets/path/to/file.ext.meta")
|
||||
print(" 3. Or: git add Assets/ (to stage all changes in Assets)")
|
||||
print(" 4. When deleting, remove both files: git rm file.ext file.ext.meta")
|
||||
return 1
|
||||
else:
|
||||
print("=" * 60)
|
||||
print("✓ META FILES SYNCHRONIZED")
|
||||
print("=" * 60)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
project_path = sys.argv[1]
|
||||
check_git = "--check-git" in sys.argv
|
||||
|
||||
try:
|
||||
exit_code = check_meta_sync(project_path, check_git=check_git)
|
||||
sys.exit(exit_code)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unity Project Health Check
|
||||
|
||||
Comprehensive health check for Unity projects covering:
|
||||
- GUID validation
|
||||
- Meta file synchronization
|
||||
- Project settings validation
|
||||
- Common configuration issues
|
||||
|
||||
Usage:
|
||||
python project_health_check.py <unity_project_path>
|
||||
python project_health_check.py <unity_project_path> --verbose
|
||||
"""
|
||||
|
||||
import sys
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def check_project_structure(project_path):
|
||||
"""Verify essential Unity project directories exist."""
|
||||
required_dirs = ["Assets", "ProjectSettings"]
|
||||
issues = []
|
||||
|
||||
for dir_name in required_dirs:
|
||||
dir_path = project_path / dir_name
|
||||
if not dir_path.exists():
|
||||
issues.append(f"Missing required directory: {dir_name}")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_input_system_config(project_path):
|
||||
"""Check Input System configuration in ProjectSettings."""
|
||||
settings_file = project_path / "ProjectSettings" / "ProjectSettings.asset"
|
||||
|
||||
if not settings_file.exists():
|
||||
return ["ProjectSettings.asset not found"]
|
||||
|
||||
issues = []
|
||||
|
||||
try:
|
||||
with open(settings_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Check activeInputHandler setting
|
||||
if "activeInputHandler:" in content:
|
||||
for line in content.split("\n"):
|
||||
if "activeInputHandler:" in line:
|
||||
value = line.split(":")[1].strip()
|
||||
if value == "0":
|
||||
issues.append(
|
||||
"Input System: Set to 'Legacy' (0). "
|
||||
"Consider using 'Both' (2) for compatibility"
|
||||
)
|
||||
elif value == "1":
|
||||
issues.append(
|
||||
"Input System: Set to 'New' (1). "
|
||||
"Legacy Input API will not work"
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
issues.append(f"Error reading ProjectSettings.asset: {e}")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_urp_configuration(project_path):
|
||||
"""Check URP (Universal Render Pipeline) configuration."""
|
||||
issues = []
|
||||
|
||||
# Check if URP package exists
|
||||
packages_file = project_path / "Packages" / "manifest.json"
|
||||
if not packages_file.exists():
|
||||
return []
|
||||
|
||||
try:
|
||||
import json
|
||||
|
||||
with open(packages_file, "r", encoding="utf-8") as f:
|
||||
manifest = json.load(f)
|
||||
dependencies = manifest.get("dependencies", {})
|
||||
|
||||
if "com.unity.render-pipelines.universal" in dependencies:
|
||||
# URP is installed, check for common issues
|
||||
graphics_settings = (
|
||||
project_path / "ProjectSettings" / "GraphicsSettings.asset"
|
||||
)
|
||||
if not graphics_settings.exists():
|
||||
issues.append("URP: GraphicsSettings.asset not found")
|
||||
else:
|
||||
# Check if render pipeline asset is assigned
|
||||
with open(graphics_settings, "r", encoding="utf-8") as gs:
|
||||
gs_content = gs.read()
|
||||
if "m_CustomRenderPipeline: {fileID: 0}" in gs_content:
|
||||
issues.append(
|
||||
"URP: No render pipeline asset assigned in Graphics Settings. "
|
||||
"Create and assign a URP asset"
|
||||
)
|
||||
except Exception as e:
|
||||
pass # Silent if packages not available
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_physics_settings(project_path):
|
||||
"""Check physics configuration for common issues."""
|
||||
issues = []
|
||||
|
||||
# Check Physics settings
|
||||
physics_file = project_path / "ProjectSettings" / "DynamicsManager.asset"
|
||||
if physics_file.exists():
|
||||
try:
|
||||
with open(physics_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Check collision matrix
|
||||
if "m_DefaultContactOffset:" in content:
|
||||
for line in content.split("\n"):
|
||||
if "m_DefaultContactOffset:" in line:
|
||||
value = line.split(":")[1].strip()
|
||||
try:
|
||||
offset = float(value)
|
||||
if offset < 0.001:
|
||||
issues.append(
|
||||
f"Physics: Contact offset very small ({offset}). "
|
||||
"May cause collision detection issues"
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_version_control_settings(project_path):
|
||||
"""Check version control configuration."""
|
||||
issues = []
|
||||
|
||||
editor_settings = project_path / "ProjectSettings" / "EditorSettings.asset"
|
||||
if not editor_settings.exists():
|
||||
return issues
|
||||
|
||||
try:
|
||||
with open(editor_settings, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Check asset serialization mode
|
||||
if "m_SerializationMode:" in content:
|
||||
for line in content.split("\n"):
|
||||
if "m_SerializationMode:" in line:
|
||||
value = line.split(":")[1].strip()
|
||||
if value != "2": # 2 = Force Text
|
||||
issues.append(
|
||||
f"Version Control: Asset Serialization Mode not set to 'Force Text'. "
|
||||
f"Current: {value}. Recommended: 2 (Force Text) for better git diffs"
|
||||
)
|
||||
break
|
||||
|
||||
# Check if visible meta files enabled
|
||||
if "m_ExternalVersionControlSupport:" in content:
|
||||
for line in content.split("\n"):
|
||||
if "m_ExternalVersionControlSupport:" in line:
|
||||
value = line.split(":", 1)[1].strip()
|
||||
if "Visible Meta Files" not in value:
|
||||
issues.append(
|
||||
"Version Control: 'Visible Meta Files' not enabled. "
|
||||
"Enable for proper git integration"
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
issues.append(f"Error checking version control settings: {e}")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_gitignore(project_path):
|
||||
"""Check if .gitignore is properly configured."""
|
||||
issues = []
|
||||
|
||||
gitignore = project_path / ".gitignore"
|
||||
if not gitignore.exists():
|
||||
issues.append(
|
||||
"No .gitignore found. Create one to exclude Library/, Temp/, etc."
|
||||
)
|
||||
return issues
|
||||
|
||||
try:
|
||||
with open(gitignore, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
required_patterns = ["Library/", "Temp/", "obj/", "Logs/"]
|
||||
missing = []
|
||||
|
||||
for pattern in required_patterns:
|
||||
if pattern not in content:
|
||||
missing.append(pattern)
|
||||
|
||||
if missing:
|
||||
issues.append(f".gitignore: Missing patterns: {', '.join(missing)}")
|
||||
except Exception as e:
|
||||
issues.append(f"Error reading .gitignore: {e}")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def run_health_check(project_path, verbose=False):
|
||||
"""Run comprehensive health check."""
|
||||
project_path = Path(project_path)
|
||||
|
||||
if not project_path.exists():
|
||||
raise ValueError(f"Project path not found: {project_path}")
|
||||
|
||||
print(f"Unity Project Health Check: {project_path}\n")
|
||||
print("=" * 60)
|
||||
|
||||
all_issues = []
|
||||
|
||||
# Run all checks
|
||||
checks = [
|
||||
("Project Structure", check_project_structure),
|
||||
("Input System Configuration", check_input_system_config),
|
||||
("URP Configuration", check_urp_configuration),
|
||||
("Physics Settings", check_physics_settings),
|
||||
("Version Control Settings", check_version_control_settings),
|
||||
(".gitignore", check_gitignore),
|
||||
]
|
||||
|
||||
for check_name, check_func in checks:
|
||||
print(f"\nChecking {check_name}...")
|
||||
issues = check_func(project_path)
|
||||
|
||||
if issues:
|
||||
print(f" ⚠ {len(issues)} issue(s) found:")
|
||||
for issue in issues:
|
||||
print(f" - {issue}")
|
||||
all_issues.extend(issues)
|
||||
else:
|
||||
print(f" ✓ No issues")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
if all_issues:
|
||||
print(f"\n❌ HEALTH CHECK FOUND {len(all_issues)} ISSUE(S)")
|
||||
print("\nReview the issues above and address them to improve project health.")
|
||||
return 1
|
||||
else:
|
||||
print("\n✓ HEALTH CHECK PASSED")
|
||||
print("Project configuration looks good!")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
project_path = sys.argv[1]
|
||||
verbose = "--verbose" in sys.argv
|
||||
|
||||
try:
|
||||
exit_code = run_health_check(project_path, verbose=verbose)
|
||||
sys.exit(exit_code)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Loading…
Reference in New Issue