본문으로 건너뛰기
cccskills
무료GitHub에 공개

unity-project-architecture

Use when writing or restructuring Unity C# code - assembly definitions and dependency direction, small components with cached references, ScriptableObjects for data and events, DI instead of singletons, Input System, Addressables, serialization safety and Unity 6 API changes.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구Unity
설치 방법 보기

포함된 파일(1)

  • SKILL.md6.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Unity Project Architecture

Overview

Unity makes it easy to put everything in one MonoBehaviour that finds its collaborators with FindObjectOfType and a static Instance. That works for a jam and collapses after a few months: nothing is testable, compile times grow, and scene order decides behaviour. Structure the project as assemblies with a dependency direction, plain C# for rules, thin components for engine glue, and data in assets.

Core principle: Rules in plain C# assemblies, components as adapters, data in ScriptableObjects, dependencies passed in. The repository's existing layout and DI choice win.

Assemblies and direction

Game.Core      (noEngineReferences: rules, math, state machines, save DTOs)
   ↑
Game.Runtime   (MonoBehaviours, ScriptableObjects, adapters; references Core)
   ↑
Game.UI        (views and presenters; references Runtime/Core, never the reverse)
Game.Editor    (includePlatforms: Editor; tools, validators)
Tests.EditMode / Tests.PlayMode (test assemblies; see unity-testing)
{
  "name": "Game.Core",
  "rootNamespace": "Game.Core",
  "references": [],
  "noEngineReferences": true,
  "autoReferenced": false
}
  • An assembly per layer keeps compile times down and makes a wrong-way reference a compile error instead of a review comment.
  • Game.Core with noEngineReferences: true can be compiled and tested by plain dotnet test when the editor is missing (testable-game-logic).
  • Gameplay never references UI; UI observes gameplay through events.

Components

// ❌ lookups every frame, public mutable fields, rules inside the component
public class Enemy : MonoBehaviour {
    public int hp = 100;
    void Update() {
        var player = GameObject.Find("Player");
        if (Vector3.Distance(transform.position, player.transform.position) < 5) GetComponent<Animator>().SetTrigger("Attack");
    }
}

// ✅ cached references, serialized private fields, rule delegated to plain C#
[RequireComponent(typeof(Animator))]
public sealed class EnemyView : MonoBehaviour {
    static readonly int AttackHash = Animator.StringToHash("Attack");
    [SerializeField] EnemyDefinition definition;
    Animator _animator; EnemyBrain _brain; ITargetProvider _targets;

    public void Construct(ITargetProvider targets) => _targets = targets;
    void Awake() { _animator = GetComponent<Animator>(); _brain = new EnemyBrain(definition.AttackRange); }
    void Update() {
        if (_brain.ShouldAttack(transform.position, _targets.PlayerPosition)) _animator.SetTrigger(AttackHash);
    }
}
  • [SerializeField] private over public fields; public API through properties and methods.
  • Awake initialises the object itself; OnEnable/Start wire up to others; OnDisable unsubscribes everything OnEnable subscribed.
  • [RequireComponent] for hard dependencies on the same GameObject; TryGetComponent instead of GetComponent + null check.
  • Physics in FixedUpdate through the Rigidbody API (MovePosition, AddForce, linearVelocity), never transform.position on a dynamic body.

Data and events in ScriptableObjects

  • Definitions (weapons, enemies, levels) are ScriptableObjects (data-driven-gameplay-design); runtime state is a separate object built from them. Changes made to a ScriptableObject in Play mode persist in the editor — never write gameplay state into one.
  • Event channels (GameEvent : ScriptableObject with Raise() and listeners) decouple scenes, but keep them few and named for what happened (PlayerDied), not what should happen.

Dependencies, not singletons

  • Use the repository's container if it has one (VContainer, Zenject/Extenject): register services in a LifetimeScope/installer and inject via constructor (plain classes) or [Inject] method (components).
  • Without one: a bootstrap scene or composition root creates services and passes them in (Construct(...) as above).
  • No new static Instance for gameplay state. Engine-wide stateless helpers are fine as static classes.

Input, assets, async

  • Input System, not the legacy Input class — actions from the .inputactions asset, rebinding and device glyphs (game-input-and-accessibility). A project still on the legacy Input Manager stays on it unless the task migrates it.
  • Addressables for content loaded at runtime (game-asset-pipeline); no new content in Resources/. Every LoadAssetAsync handle is released.
  • Async: Awaitable (Unity 6) or UniTask where the repo uses it; cancel with destroyCancellationToken so a destroyed object's continuation does not run. async void only for event handlers, with try/catch.

Serialization safety

  • Renaming a serialized field loses its data in every scene and prefab unless you add [FormerlySerializedAs("oldName")].
  • Polymorphic serialized fields need [SerializeReference]; plain [SerializeField] slices to the base type.
  • Keep Asset Serialization on Force Text so scenes and prefabs diff and merge (UnityYAMLMerge); prefer prefabs and nested prefabs over large scene edits so changes stay reviewable.

Unity 6 API notes (follow the pinned version)

  • Rigidbody.velocity → linearVelocity; drag/angularDrag → linearDamping/angularDamping.
  • FindObjectsOfType/FindObjectOfType → FindObjectsByType(FindObjectsSortMode.None) / FindAnyObjectByType (and avoid both in gameplay paths).
  • Object.InstantiateAsync for large prefabs; Awaitable.NextFrameAsync, Awaitable.WaitForSecondsAsync.
  • Unity 6 LTS lines: 6000.0 LTS support ends in October 2026, 6000.3 LTS is supported to the end of 2027 — never upgrade the project on your own.

Common Mistakes

  • GameObject.Find, FindAnyObjectByType or GetComponent in Update.
  • A MonoBehaviour holding the rule logic, making it testable only in PlayMode.
  • static Instance managers that depend on scene load order.
  • Renaming serialized fields without FormerlySerializedAs.
  • Subscribing in OnEnable without unsubscribing in OnDisable — leaks and calls on destroyed objects.

Red Flags

  • A UI assembly referenced by gameplay code.
  • A new Resources.Load call.
  • transform.position = on an object with a non-kinematic Rigidbody.
  • A component longer than a few hundred lines with rules, input and presentation mixed.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고