unity-csharp について
unity-csharp は、VR やモバイルを含む Unity の C# コードを書く開発者向けのスキルです。コンポーネントやライフサイクルのパターンに加え、プロファイリングと最適化の指針を扱います。
主な機能
- 01MonoBehaviour のライフサイクルとコンポーネント設計
- 02キャッシュ、イベント、コルーチン、null 安全性
- 03ScriptableObject によるデータ設計
- 04ドローコール、バッチング、LOD の最適化
- 05VR とモバイル向けのプロファイリング指針
活用シーン
- 01役割を絞った Unity コンポーネントでスクリプトを構成
- 02コンポーネント検索や Update 処理の反復を削減
- 03VR またはモバイル向けの描画を最適化
- 04CPU、メモリ、ドローコールの問題を分析
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ alexanderstephenthompson/ claude-hub --skill unity-csharpインストール後、Claude Code で/unity-csharpを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/component-checklist.mdvr-performance-limits.mdreferences/lifecycle.mdprofiling.md詳細
Unity C# スキル
バージョン: 2.0 スタック: Unity、C#
クリーンでパフォーマンスに優れた Unity C# コードを書くためのパターンです。VR/モバイル向けの最適化も含みます。
対象範囲と境界
このスキルの対象:
- MonoBehaviour のライフサイクルとコンポーネントアーキテクチャ
- Unity 固有の C# パターン(キャッシュ、イベント、コルーチン、null 安全性)
- パフォーマンス最適化(ドローコール、バッチング、LOD、プーリング)
- VR/モバイルのパフォーマンス目標とプロファイリング
- ScriptableObject の使用
他のスキルに委ねる内容:
vrc-udon:VRChat 固有のスクリプティング(UdonSharp)vrc-worlds:VRChat ワールドの設定と制限vrc-avatars:VRChat アバターの設定と制限
このスキルを使用する場面: Unity 用の C# スクリプトを書く場合、または VR/モバイル向けに Unity のパフォーマンスを最適化する場合です。
基本原則
- 継承よりコンポジション — 小さく、目的を絞ったコンポーネントにします。
- 可能な限り Update() を避ける — 代わりにイベント駆動またはコルーチンを使います。
- 参照をキャッシュする — GetComponent はコストが高いため、Awake でキャッシュします。
- データには ScriptableObject を使う — データと動作を分離します。
- null 安全なアクセス — Unity オブジェクトはいつでも破棄される可能性があります。
- まず計測する — 最適化の前にプロファイリングします。勘は当てになりません。
- 積極的にバッチ化する — 同じマテリアルならバッチ化できる可能性があります。VR ではドローコールが最も重要です。
パターン
参照のキャッシュ
public class PlayerController : MonoBehaviour
{
private Rigidbody _rb;
private Animator _animator;
[SerializeField] private Transform _cameraTarget;
private void Awake()
{
_rb = GetComponent<Rigidbody>();
_animator = GetComponent<Animator>();
}
private void FixedUpdate()
{
_rb.AddForce(Vector3.up);
}
}
イベントシステム(ScriptableObject)
[CreateAssetMenu(menuName = "Events/Game Event")]
public class GameEvent : ScriptableObject
{
private readonly List<GameEventListener> _listeners = new();
public void Raise()
{
for (int i = _listeners.Count - 1; i >= 0; i--)
_listeners[i].OnEventRaised();
}
public void RegisterListener(GameEventListener listener) =>
_listeners.Add(listener);
public void UnregisterListener(GameEventListener listener) =>
_listeners.Remove(listener);
}
public class GameEventListener : MonoBehaviour
{
[SerializeField] private GameEvent _event;
[SerializeField] private UnityEvent _response;
private void OnEnable() => _event.RegisterListener(this);
private void OnDisable() => _event.UnregisterListener(this);
public void OnEventRaised() => _response.Invoke();
}
null 安全なパターン
// Unity overloads == for destroyed objects
if (_target != null)
{
_target.DoSomething();
}
// Best: explicit destroyed check
if (_target != null && !_target.Equals(null))
{
_target.DoSomething();
}
コルーチンのパターン
private IEnumerator FadeOut(float duration)
{
float elapsed = 0f;
Color startColor = _renderer.material.color;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
_renderer.material.color = Color.Lerp(startColor, Color.clear, t);
yield return null;
}
gameObject.SetActive(false);
}
オブジェクトプーリング
public class ObjectPool : MonoBehaviour
{
[SerializeField] private GameObject _prefab;
[SerializeField] private int _initialSize = 10;
private Queue<GameObject> _pool = new();
private void Awake()
{
for (int i = 0; i < _initialSize; i++)
{
var obj = Instantiate(_prefab);
obj.SetActive(false);
_pool.Enqueue(obj);
}
}
public GameObject Get()
{
if (_pool.Count == 0)
return Instantiate(_prefab);
var pooled = _pool.Dequeue();
pooled.SetActive(true);
return pooled;
}
public void Return(GameObject obj)
{
obj.SetActive(false);
_pool.Enqueue(obj);
}
}
静的バッチング
gameObject.isStatic = true;
// Or specific flags
GameObjectUtility.SetStaticEditorFlags(gameObject,
StaticEditorFlags.BatchingStatic |
StaticEditorFlags.OcclusionStatic);
VR のパフォーマンス目標
| 指標 | Quest 2 | Quest 3 | PC VR |
|---|---|---|---|
| ドローコール | <100 | <150 | <200 |
| 三角形数 | <100K | <150K | <1M |
| フレーム時間 | <14ms (72fps) | <11ms (90fps) | <11ms (90fps) |
| テクスチャメモリ | <200MB | <500MB | <1GB |
LOD の設定
LOD 0: 100% triangles (0-10m)
LOD 1: 50% triangles (10-25m)
LOD 2: 25% triangles (25-50m)
Culled: 0 triangles (50m+)
マテリアルアトラス化
Before: 20 objects x 20 materials = 20 draw calls (no batching)
After: 20 objects x 1 atlas material = 1 draw call (batched)
アンチパターン
| アンチパターン | 問題 | 対処法 |
|---|---|---|
Update 内での GetComponent | フレームごとにコストがかかる | Awake でキャッシュする |
Find または FindObjectOfType | 遅く、壊れやすい | 参照を注入するか、イベントを使う |
| 負荷の高い Update ループ | パフォーマンスを低下させる | イベント、コルーチン、または FixedUpdate を使う |
| タグの文字列比較 | タイプミスが起きやすく、遅い | CompareTag または定数を使う |
| すべてを public フィールドにする | カプセル化されない | [SerializeField] private を使う |
| オブジェクトごとに異なるマテリアル | バッチ化できない | マテリアルを共有し、アトラスを使う |
| LOD がない | 距離にかかわらず最高詳細度で表示される | LOD グループを追加する |
| ゲームプレイ中の Instantiate/Destroy | GC スパイクやカクつきが発生する | オブジェクトプーリングを使う |
| あらゆる場所でのリアルタイムライト | シャドウのコストが高い | ライティングをベイクし、リアルタイムライトを制限する |
| オクルージョンカリングがない | 隠れたオブジェクトもレンダリングされる | オクルージョンデータをベイクする |
チェックリスト
コード品質
- 参照を Awake でキャッシュしている
- Update/FixedUpdate 内で GetComponent を使っていない
- 実行時コードで Find 系のメソッドを使っていない
- 共有データに ScriptableObject を使っている
- 疎結合な通信にイベントを使っている
- 破棄される可能性のあるオブジェクトに null チェックを行っている
パフォーマンス
- 静的オブジェクトに static を設定している
- 可能な範囲でマテリアルを共有している
- 小さな小道具にテクスチャアトラスを使っている
- 重要なメッシュに LOD グループを設定している
- オクルージョンカリングをベイクしている
- 生成されるオブジェクトにオブジェクトプーリングを使っている
- ライティングをベイクしている(すべてをリアルタイムにしていない)
プロファイリング
- Frame Debugger でドローコールを確認した
- Profiler を実行して CPU スパイクを確認した
- Memory Profiler でリークを確認した
- 対象デバイスでテストした(エディターだけで済ませていない)
参考資料
references/lifecycle.md— MonoBehaviour のライフサイクルと実行順序references/profiling.md— Unity Profiler の使用方法と結果の解釈
アセット
assets/component-checklist.md— Unity コンポーネント設計チェックリストassets/vr-performance-limits.md— VR プラットフォームのパフォーマンス制限と目標
コメント
Markdown 対応