Token导航 LogoToken导航

Agent Skill · 开发

unity-level-design(Unity level 设计)

用于辅助界面设计、视觉规范、排版、配色、布局和交互体验优化。它适合让 Agent 根据产品场景整理页面结构、生成 UI 方案、检查视觉一致性或改进组件层级。使用时需要结合现有品牌、设计系统和用户任务,不应只堆装饰元素;涉及真实页面改动时,应通过截图或浏览器预览检查文本溢出、对齐和响应式表现。

unity-level-design 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为执行命令;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 18,535。

总安装 329周安装 14GitHub Stars 14下载 115核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:unity-level-design(Unity level 设计)
来源仓库:https://github.com/nice-wolf-studio/unity-claude-skills
仓库路径:skills/unity-level-design
安装命令:
npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-level-design
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-level-design

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

用于辅助 Unity 关卡设计的界面优化、视觉规范和交互体验改进。适合让 Agent 根据场景需求整理布局结构、生成设计方案或检查一致性。使用时需结合品牌系统和用户任务,避免仅添加装饰元素。涉及实际页面改动时应通过截图或浏览器预览验证文本和对齐效果。

unity-level-design 属于开发类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/unity-level-design

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill u…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要执行命令,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

unity-level-design 怎么安装?

在终端执行 npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-level…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

unity-level-design 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

unity-level-design 需要什么权限?

权限级别是执行命令。安装流程涉及命令执行,可能通过 npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-level-design 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

unity-level-design 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

unity-level-design 用的人多吗?

公开数据是累计安装 329 次,近一周安装 14 次,下载 115 次,GitHub 14 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

unity-level-design 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 需要执行命令,装到常用环境前要先评估影响面。
  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 不能接受 Skill 在你机器上执行命令的人。
  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
unity-level-design

Unity level 设计 · 本页

32914执行命令未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Level Design -- Design Translation Patterns

Prerequisite skills: unity-scene-assets (additive scenes, Addressables), unity-async-patterns (async loading, cancellation), unity-game-architecture (SO events, bootstrap)

Claude treats levels as static scene files with no contract layer between level designers and gameplay programmers. Triggers are one-off scripts with game logic jammed into OnTriggerEnter, encounters are hardcoded spawn sequences, and checkpoints break on scene reload because nobody thought about what state needs to survive. These patterns establish the translation layer: designers author intent through data assets and Inspector configuration, programmers provide the systems that honor that intent.


PATTERN 1: Trigger & Event Architecture

DESIGN INTENT: Level designers place trigger volumes in the editor that fire game events -- spawn enemies, play dialogue, open a door. The trigger is a generic, reusable component; the response is wired in the Inspector via ScriptableObject event channels.

WRONG:

// Each trigger is a bespoke script -- 47 of these in the project
public class Door3Trigger : MonoBehaviour
{
    public GameObject door;
    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
            door.GetComponent<Animator>().SetTrigger("Open");
    }
}

Every trigger is a unique MonoBehaviour. No reuse, no designer control, no way to disable or reconfigure without code changes.

RIGHT: Generic TriggerZone component with configurable activation rules and SO event channel output. Designer wires event in Inspector -- zero per-trigger code.

using UnityEngine;

/// <summary>
/// Generic trigger volume that raises a GameEvent SO channel on activation.
/// Configurable activation count, tag filter, cooldown, and delay.
/// </summary>
[RequireComponent(typeof(Collider), typeof(Rigidbody))]
public class TriggerZone : MonoBehaviour
{
    public enum ActivationMode { Once, Repeating, NTimes }

    [Header("Activation Rules")]
    [SerializeField] ActivationMode _mode = ActivationMode.Once;
    [SerializeField] int _maxActivations = 1;
    [SerializeField] float _cooldownSeconds;
    [SerializeField] float _activationDelay;
    [SerializeField] string _requiredTag = "Player";
    [SerializeField] LayerMask _requiredLayer = ~0;

    [Header("Event Output")]
    [SerializeField] GameEvent _onActivated;

    int _activationCount;
    float _lastActivationTime = float.NegativeInfinity;

    // See unity-game-architecture for GameEvent SO channel pattern

    void Reset()
    {
        // Guarantee Rigidbody is kinematic and collider is trigger
        var rb = GetComponent<Rigidbody>();
        rb.isKinematic = true;
        GetComponent<Collider>().isTrigger = true;
    }

    void OnTriggerEnter(Collider other)
    {
        if (!PassesFilter(other)) return;
        if (!CanActivate()) return;
        _ = ActivateAsync();
    }

    bool PassesFilter(Collider other)
    {
        if (!string.IsNullOrEmpty(_requiredTag) && !other.CompareTag(_requiredTag))
            return false;
        if ((_requiredLayer & (1 << other.gameObject.layer)) == 0)
            return false;
        return true;
    }

    bool CanActivate()
    {
        if (_mode == ActivationMode.Once && _activationCount >= 1) return false;
        if (_mode == ActivationMode.NTimes && _activationCount >= _maxActivations) return false;
        if (Time.time - _lastActivationTime < _cooldownSeconds) return false;
        return true;
    }

    async Awaitable ActivateAsync()
    {
        if (_activationDelay > 0f)
            await Awaitable.WaitForSecondsAsync(_activationDelay, destroyCancellationToken);

        _activationCount++;
        _lastActivationTime = Time.time;
        _onActivated?.Raise();
    }
}

SCAFFOLD:

  • TriggerZone MonoBehaviour (above) -- the generic trigger component
  • GameEvent ScriptableObject channel -- cross-ref unity-game-architecture
  • TriggerConfig fields -- activation mode, count, cooldown, delay, tag/layer filter

DESIGN HOOK: Designers place trigger volumes in the scene, assign a GameEvent SO in the Inspector, and configure activation rules. Zero per-trigger code. New trigger behaviors = new GameEvent listeners, not new trigger scripts.

GOTCHA: OnTriggerEnter requires one collider to have isTrigger = true and at least one Rigidbody in the pair. The TriggerZone adds its own kinematic Rigidbody via [RequireComponent] and sets it up in Reset() to guarantee this works regardless of what enters. Without this, triggers silently fail when the entering object has no Rigidbody.


PATTERN 2: Encounter Design Contracts

DESIGN INTENT: Designers define encounters (enemy waves, arena lockdowns, puzzles) as data assets and place them in levels via prefabs. The encounter definition is separate from the encounter instance.

WRONG:

// Hardcoded encounter -- can't reuse, can't tune without code changes
public class ArenaEncounter : MonoBehaviour
{
    public GameObject zombiePrefab;
    public GameObject skeletonPrefab;

    async Awaitable Start()
    {
        // Wave 1
        Instantiate(zombiePrefab, transform.position, Quaternion.identity);
        Instantiate(zombiePrefab, transform.position + Vector3.right * 3, Quaternion.identity);
        await Awaitable.WaitForSecondsAsync(10f);
        // Wave 2
        Instantiate(skeletonPrefab, transform.position, Quaternion.identity);
    }
}

Every encounter is a unique script. Wave timing, enemy types, and spawn positions are all hardcoded.

RIGHT: EncounterConfig SO defines waves as data. EncounterController prefab reads the config and manages spawning.

using System;
using UnityEngine;

/// <summary>
/// Defines a single wave within an encounter: enemy types, counts, and timing.
/// </summary>
[Serializable]
public struct WaveDefinition
{
    /// <summary>Enemy prefab to spawn in this wave.</summary>
    public GameObject EnemyPrefab;
    /// <summary>Number of enemies to spawn.</summary>
    public int Count;
    /// <summary>Seconds to wait before starting this wave (after previous completes).</summary>
    public float DelayBeforeWave;
    /// <summary>If true, all enemies must be defeated before next wave starts.</summary>
    public bool WaitForClear;
}

/// <summary>
/// Data asset defining an entire encounter: sequence of waves and completion rules.
/// </summary>
[CreateAssetMenu(menuName = "Level Design/Encounter Config")]
public class EncounterConfig : ScriptableObject
{
    /// <summary>Ordered list of waves in this encounter.</summary>
    public WaveDefinition[] Waves;
    /// <summary>Event raised when the encounter begins.</summary>
    public GameEvent OnEncounterStart;
    /// <summary>Event raised when all waves are cleared.</summary>
    public GameEvent OnEncounterComplete;
}
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

/// <summary>
/// Runs an encounter defined by an EncounterConfig SO.
/// Place in scene with spawn point transforms as children.
/// </summary>
public class EncounterController : MonoBehaviour
{
    [SerializeField] EncounterConfig _config;
    [SerializeField] Transform[] _spawnPoints;
    [SerializeField] GameEvent _triggerEvent; // Listens for this to start

    readonly List<GameObject> _activeEnemies = new();

    // Full implementation in references/level-system-scaffolds.md
}

SCAFFOLD:

  • EncounterConfig SO -- wave definitions, completion events
  • WaveDefinition serializable struct -- enemy prefab, count, timing, clear condition
  • EncounterController MonoBehaviour -- async wave progression, spawn management

DESIGN HOOK: Designers create EncounterConfig SOs per encounter and place EncounterController prefabs at encounter locations. Spawn points are child Transforms on the controller. Tuning wave counts, timing, and enemy types requires zero code changes.

GOTCHA: Spawn points must be defined per-encounter in the scene (Transform array on the controller), not in the SO. ScriptableObjects cannot reference scene objects -- they are project-level assets. If you put Transform references in the SO, they will be null at runtime.


PATTERN 3: Checkpoint & Respawn

DESIGN INTENT: Players die and respawn at the last activated checkpoint. Checkpoint state survives across attempts -- doors stay opened, keys stay collected, enemies stay dead.

WRONG:

// Full scene reload on death -- all progress lost
public class DeathHandler : MonoBehaviour
{
    public void OnPlayerDied()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

Reloading the scene resets everything. Doors re-lock, enemies respawn, puzzles reset. The player must redo the entire level.

RIGHT: CheckpointSystem captures a CheckpointSnapshot on activation. On death, the snapshot is restored without a scene reload. Objects that need save/restore implement ICheckpointable.

using UnityEngine;

/// <summary>
/// Implement on any object that needs to save/restore state at checkpoints.
/// </summary>
public interface ICheckpointable
{
    /// <summary>Unique ID for this object (use a serialized GUID).</summary>
    string CheckpointId { get; }
    /// <summary>Capture current state as serializable data.</summary>
    object CaptureState();
    /// <summary>Restore state from previously captured data.</summary>
    void RestoreState(object state);
}
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Snapshot of all checkpointable state at a specific checkpoint.
/// </summary>
public class CheckpointSnapshot
{
    /// <summary>Player position at checkpoint activation.</summary>
    public Vector3 PlayerPosition;
    /// <summary>Player rotation at checkpoint activation.</summary>
    public Quaternion PlayerRotation;
    /// <summary>Saved state per checkpointable object, keyed by CheckpointId.</summary>
    public Dictionary<string, object> ObjectStates = new();
}
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

/// <summary>
/// Manages checkpoint save/restore. Singleton, lives in persistent scene.
/// </summary>
public class CheckpointSystem : MonoBehaviour
{
    public static CheckpointSystem Instance { get; private set; }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void ResetStatics() => Instance = null;

    CheckpointSnapshot _currentSnapshot;

    void Awake()
    {
        if (Instance != null && Instance != this) { Destroy(gameObject); return; }
        Instance = this;
    }

    /// <summary>
    /// Captures a checkpoint snapshot from the player and all ICheckpointable objects.
    /// </summary>
    public void SaveCheckpoint(Transform player)
    {
        _currentSnapshot = new CheckpointSnapshot
        {
            PlayerPosition = player.position,
            PlayerRotation = player.rotation
        };

        foreach (var obj in FindCheckpointables())
            _currentSnapshot.ObjectStates[obj.CheckpointId] = obj.CaptureState();
    }

    /// <summary>
    /// Restores the last checkpoint snapshot. Returns false if no checkpoint exists.
    /// </summary>
    public bool RestoreCheckpoint(Transform player)
    {
        if (_currentSnapshot == null) return false;

        player.position = _currentSnapshot.PlayerPosition;
        player.rotation = _currentSnapshot.PlayerRotation;

        foreach (var obj in FindCheckpointables())
        {
            if (_currentSnapshot.ObjectStates.TryGetValue(obj.CheckpointId, out var state))
                obj.RestoreState(state);
        }
        return true;
    }

    IEnumerable<ICheckpointable> FindCheckpointables() =>
        FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None).OfType<ICheckpointable>();
}

SCAFFOLD:

  • CheckpointSystem manager (singleton, persistent scene) -- save/restore orchestration
  • CheckpointSnapshot -- player transform + dictionary of object states
  • ICheckpointable interface -- CaptureState() / RestoreState(object)
  • CheckpointTrigger -- extends TriggerZone pattern (Pattern 1) to call CheckpointSystem.SaveCheckpoint

DESIGN HOOK: Objects implement ICheckpointable to participate in checkpoint save/restore (doors, switches, destructibles). Designers place checkpoint trigger volumes in the scene. The system handles the rest.

GOTCHA: The checkpoint must save ALL mutable state. If a door was opened between checkpoints, the opened state must be in the snapshot. Audit every gameplay object for ICheckpointable -- a single missing implementation means that object resets on death while everything else restores, creating impossible states. Also: FindObjectsByType is expensive -- cache the results if checkpoints are frequent.


PATTERN 4: Cinematic & Scripted Sequences

DESIGN INTENT: In-game cutscenes, camera moves, NPC dialogue, and timed events as authored sequences that designers compose in the Inspector without writing code.

WRONG:

// Coroutine chain -- fragile, not reusable, not skippable
IEnumerator PlayCutscene()
{
    camera.transform.position = new Vector3(10, 5, 0);
    yield return new WaitForSeconds(2f);
    npc.GetComponent<Animator>().SetTrigger("Talk");
    dialogueUI.Show("Welcome, hero!");
    yield return new WaitForSeconds(3f);
    door.SetActive(false);
}

Hardcoded positions, no cancellation, not skippable, uses deprecated coroutine pattern.

RIGHT: ISequenceStep interface with async ExecuteAsync(CancellationToken). SequencePlayer runs steps in order. Steps are composed in the Inspector via [SerializeReference].

using System.Threading;

/// <summary>
/// A single step in a scripted sequence. Implement for each step type.
/// </summary>
public interface ISequenceStep
{
    /// <summary>
    /// Execute this step. Must respect cancellation for skip support.
    /// When cancelled, the step should immediately apply its end state.
    /// </summary>
    Awaitable ExecuteAsync(CancellationToken ct);
}
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

/// <summary>
/// Plays a sequence of ISequenceStep instances in order.
/// Steps are composed via [SerializeReference] in the Inspector.
/// </summary>
public class SequencePlayer : MonoBehaviour
{
    [SerializeReference] List<ISequenceStep> _steps = new();
    [SerializeField] bool _playOnStart;
    [SerializeField] GameEvent _onSequenceComplete;

    bool _isPlaying;

    /// <summary>Play the full sequence. Cancellation skips remaining steps.</summary>
    public async Awaitable PlayAsync(CancellationToken ct = default)
    {
        if (_isPlaying) return;
        _isPlaying = true;

        using var linked = CancellationTokenSource.CreateLinkedTokenSource(
            ct, destroyCancellationToken);

        foreach (var step in _steps)
        {
            if (linked.Token.IsCancellationRequested) break;

            try
            {
                await step.ExecuteAsync(linked.Token);
            }
            catch (OperationCanceledException)
            {
                break;
            }
        }

        _isPlaying = false;
        _onSequenceComplete?.Raise();
    }

    async void Start()
    {
        if (_playOnStart)
            await PlayAsync(destroyCancellationToken);
    }
}

Example step implementations:

using System;
using System.Threading;
using UnityEngine;

/// <summary>Wait for a specified duration. Skippable via cancellation.</summary>
[Serializable]
public class WaitStep : ISequenceStep
{
    [SerializeField] float _duration = 1f;

    public async Awaitable ExecuteAsync(CancellationToken ct)
    {
        await Awaitable.WaitForSecondsAsync(_duration, ct);
    }
}

/// <summary>Enable or disable a GameObject.</summary>
[Serializable]
public class SetActiveStep : ISequenceStep
{
    [SerializeField] GameObject _target;
    [SerializeField] bool _active = true;

    public Awaitable ExecuteAsync(CancellationToken ct)
    {
        if (_target != null) _target.SetActive(_active);
        return Awaitable.NextFrameAsync(ct);
    }
}

SCAFFOLD:

  • ISequenceStep interface -- ExecuteAsync(CancellationToken)
  • SequencePlayer MonoBehaviour -- runs [SerializeReference] step list in order
  • Step types: CameraMoveStep, DialogueStep, WaitStep, SetActiveStep

DESIGN HOOK: New sequence step = implement ISequenceStep. Designers compose sequences in the Inspector by adding/removing/reordering steps in the [SerializeReference] list. No code changes to create new cutscenes.

GOTCHA: [SerializeReference] requires concrete types in the same assembly or an assembly that references the interface assembly. Unity's default Inspector does not provide a nice UI for [SerializeReference] -- you need a custom PropertyDrawer or use a package like Odin Inspector / SerializeReferenceDropdown. Steps must handle cancellation gracefully: when cancelled (skip), apply the end state immediately (e.g., snap camera to final position) rather than leaving the step half-complete.


PATTERN 5: Environmental Storytelling Hooks

DESIGN INTENT: Objects react to player proximity or interaction -- notes to read, audio logs to play, environmental animations to trigger. All interactive objects share a consistent UX pattern.

WRONG:

// Each interactive object is a unique script with bespoke logic
public class Note47 : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player") && Input.GetKeyDown(KeyCode.E))
            UIManager.Instance.ShowNote("Note 47 text...");
    }
}

No shared interaction UX. Each object checks input differently. Prompt display is inconsistent. New interaction types require new scripts from scratch.

RIGHT: Interactable component with IInteraction interface. Shared InteractionDetector on the player handles proximity detection and input prompting.

using UnityEngine;

/// <summary>
/// Interface for interaction behaviors. Implement per interaction type.
/// </summary>
public interface IInteraction
{
    /// <summary>Text shown on the interaction prompt (e.g., "Read Note").</summary>
    string PromptText { get; }
    /// <summary>Execute the interaction.</summary>
    void Execute(GameObject interactor);
}
using UnityEngine;

/// <summary>
/// Marks a GameObject as interactable. Holds an IInteraction and detection range.
/// </summary>
public class Interactable : MonoBehaviour
{
    [SerializeField] float _interactionRange = 2f;
    [SerializeReference] IInteraction _interaction;

    /// <summary>Maximum distance for interaction.</summary>
    public float InteractionRange => _interactionRange;
    /// <summary>The interaction behavior attached to this object.</summary>
    public IInteraction Interaction => _interaction;
}
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

/// <summary>
/// Player-side component. Detects nearby Interactables and handles input prompting.
/// Cross-ref unity-input-correctness for input handling.
/// </summary>
public class InteractionDetector : MonoBehaviour
{
    [SerializeField] float _detectionRadius = 5f;
    [SerializeField] LayerMask _interactableLayer;

    Interactable _currentTarget;
    readonly Collider[] _overlapBuffer = new Collider[16];

    void Update()
    {
        _currentTarget = FindClosestInteractable();

        if (_currentTarget != null)
        {
            ShowPrompt(_currentTarget.Interaction.PromptText);
            // Input check -- see unity-input-correctness for Input System usage
        }
        else
        {
            HidePrompt();
        }
    }

    Interactable FindClosestInteractable()
    {
        int count = Physics.OverlapSphereNonAlloc(
            transform.position, _detectionRadius, _overlapBuffer, _interactableLayer);

        Interactable closest = null;
        float closestDist = float.MaxValue;

        for (int i = 0; i < count; i++)
        {
            if (!_overlapBuffer[i].TryGetComponent<Interactable>(out var interactable))
                continue;

            float dist = Vector3.Distance(transform.position, interactable.transform.position);
            if (dist > interactable.InteractionRange) continue;
            if (dist < closestDist)
            {
                closest = interactable;
                closestDist = dist;
            }
        }
        return closest;
    }

    void ShowPrompt(string text) { /* UI Toolkit prompt -- see implementation in scaffolds */ }
    void HidePrompt() { /* Hide prompt UI */ }
}

SCAFFOLD:

  • Interactable MonoBehaviour -- holds IInteraction via [SerializeReference], detection range
  • IInteraction interface -- PromptText, Execute(GameObject interactor)
  • InteractionDetector on player -- proximity detection, prompt display, input handling
  • Example: ReadNoteInteraction implements IInteraction

DESIGN HOOK: New interaction type = implement IInteraction. Designers configure prompt text, range, and interaction type on the prefab. Consistent UX across all interactive objects.

GOTCHA: Interaction prompt must use world-to-screen positioning (Camera.main.WorldToScreenPoint) and handle off-screen clamping. Use Physics.OverlapSphereNonAlloc with a pre-allocated buffer -- do not allocate every frame. Camera.main is a FindObjectWithTag call under the hood; cache the camera reference.


PATTERN 6: Level Streaming & Loading Seams

DESIGN INTENT: Large levels load/unload sections as the player moves -- open world, long corridors, hub with connected areas. The player never sees a loading screen during gameplay.

WRONG:

// Entire world in one scene
// 500 MB memory usage, 45-second load time, untestable

Everything in one scene. Artists can't work in parallel (merge conflicts). Memory usage is unbounded. Initial load time grows forever.

RIGHT: StreamingVolume trigger zones that additively load/unload scene chunks via Addressables (cross-ref unity-scene-assets). Preloading based on player proximity. Hysteresis to prevent thrashing.

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.ResourceProviders;

/// <summary>
/// Configuration for a streamable level chunk.
/// </summary>
[CreateAssetMenu(menuName = "Level Design/Level Chunk Config")]
public class LevelChunkConfig : ScriptableObject
{
    /// <summary>Addressable scene asset reference for this chunk.</summary>
    public AssetReference SceneReference;
    /// <summary>Distance at which to begin preloading this chunk.</summary>
    public float PreloadDistance = 50f;
    /// <summary>Distance at which to unload this chunk (must be > PreloadDistance).</summary>
    public float UnloadDistance = 80f;
}
using System.Threading;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;

/// <summary>
/// Manages loading/unloading a level chunk based on player proximity.
/// Place at the seam between level sections.
/// Cross-ref unity-scene-assets for Addressable scene loading patterns.
/// </summary>
public class StreamingVolume : MonoBehaviour
{
    [SerializeField] LevelChunkConfig _chunkConfig;
    [SerializeField] Transform _playerTransform;

    AsyncOperationHandle<SceneInstance> _loadHandle;
    bool _isLoaded;
    bool _isLoading;

    void Update()
    {
        if (_playerTransform == null) return;
        float distance = Vector3.Distance(transform.position, _playerTransform.position);

        if (!_isLoaded && !_isLoading && distance < _chunkConfig.PreloadDistance)
            _ = LoadChunkAsync(destroyCancellationToken);
        else if (_isLoaded && distance > _chunkConfig.UnloadDistance)
            _ = UnloadChunkAsync();
    }

    async Awaitable LoadChunkAsync(CancellationToken ct)
    {
        _isLoading = true;
        _loadHandle = Addressables.LoadSceneAsync(
            _chunkConfig.SceneReference, LoadSceneMode.Additive);

        while (!_loadHandle.IsDone)
        {
            ct.ThrowIfCancellationRequested();
            await Awaitable.NextFrameAsync(ct);
        }

        _isLoaded = true;
        _isLoading = false;
    }

    async Awaitable UnloadChunkAsync()
    {
        if (!_loadHandle.IsValid()) return;
        var unload = Addressables.UnloadSceneAsync(_loadHandle);

        while (!unload.IsDone)
            await Awaitable.NextFrameAsync(destroyCancellationToken);

        _isLoaded = false;
    }

    void OnDestroy()
    {
        if (_isLoaded && _loadHandle.IsValid())
            Addressables.UnloadSceneAsync(_loadHandle);
    }
}

SCAFFOLD:

  • StreamingVolume MonoBehaviour -- proximity-based load/unload with hysteresis
  • LevelChunkConfig SO -- scene reference, preload distance, unload distance
  • Async Addressable scene loading/unloading (cross-ref unity-scene-assets)

DESIGN HOOK: Designers place streaming volumes at level seams and assign chunk configs. Chunk configs define which Addressable scene to load and at what distances. Preload distance < unload distance provides hysteresis.

GOTCHA: Adjacent chunks must share a small overlap zone. Objects at the seam (e.g., a bridge between two areas) must exist in both chunks or in a persistent scene that is never unloaded. The unload distance must be strictly greater than the preload distance (hysteresis buffer) -- otherwise the system will thrash between loading and unloading every frame. Test with Profiler.GetTotalAllocatedMemoryLong() to verify chunks are actually freeing memory on unload.


Anti-Patterns Summary

Anti-PatternProblemPattern Fix
Bespoke trigger scriptsNo reuse, no designer controlPattern 1: Generic TriggerZone + SO events
Hardcoded spawn sequencesCan't tune without code changesPattern 2: EncounterConfig SO + EncounterController
Scene reload on deathAll progress lostPattern 3: CheckpointSystem + ICheckpointable
Coroutine cutscene chainsNot skippable, not composablePattern 4: ISequenceStep + SequencePlayer
One-off interactive objectsInconsistent UX, no reusePattern 5: Interactable + IInteraction
Monolithic single sceneMemory, performance, collaborationPattern 6: StreamingVolume + Addressables

Related Skills

  • unity-game-architecture -- GameEvent SO channel pattern, bootstrap scene, SO-based config
  • unity-scene-assets -- Addressable asset loading, additive scene management
  • unity-async-patterns -- Awaitable usage, cancellation tokens, async lifecycle
  • unity-input-correctness -- Input System integration for interaction prompts
  • unity-save-system -- Persistent save data (checkpoint snapshots are transient; save system is permanent)
  • unity-physics -- Trigger collider setup, layer-based filtering, Rigidbody requirements

Additional Resources

平台分布

Codex

33.29%
按下载量换算38

Claude

32.05%
按下载量换算37

Cursor

17.62%
按下载量换算20

Gemini CLI

8.89%
按下载量换算10

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

执行命令

安装流程涉及命令执行,可能通过 npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-level-design 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills