unity-2d-level

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

unity-2d-level — a level is data with invariants, and the Tilemap is one renderer of it

unity-2d-level — 关卡是带不变量约束的数据,而 Tilemap 只是它的一种渲染器

A level that exists only as painted Tilemaps can be looked at and played, and nothing else. It cannot be diffed, generated, converted from another editor, validated outside the Editor, or asked "can the runner reach that pickup". Asking a Tilemap "is this cell solid" is a native call per cell; its bounds grow when painted and never shrink when erased; and its collider catches up with its tiles later in the frame. None of that is a bug — a Tilemap is a renderer. The level is the small data structure it was painted from.
一个仅以绘制后的 Tilemap 形式存在的关卡,只能被查看和游玩,此外别无他用。 它无法进行差异比较、生成、从其他编辑器转换、在 Editor 外验证,也无法被询问“runner 能否到达该 pickup”。询问 Tilemap“此单元格是否为实体”会对每个单元格执行一次 native 调用;绘制时它的边界会增长,擦除后却永不收缩;它的 collider 还要在本帧稍后才与 tiles 同步。这些都不是 bug——Tilemap 是渲染器。关卡则是绘制它时所依据的小型数据结构。

The situation → what to read

当前情况 → 阅读内容

The situationRead
"Which Tilemap layers do I need": which Tilemaps a level needs, which collide, where each sorts; painting entities as marker tiles
reference/layer-stack.md
"Load this level from JSON": the level file's keys (tile ids, spawn, gate, pickups, entities), cell indexing, ids instead of asset references, versions, asset or JSON
reference/level-data.md
SetTile
or
SetTiles
or
SetTilesBlock
; a block that painted nothing; bounds that will not shrink; cell to world; "no floor collider right after SetTile", the floor missing for one step after a load; breaking tiles in play
reference/runtime-tilemap.md
Rooms and doors, their enter event, a room graph, soft locks, "stitch chunks for an endless runner", the difficulty ramp, additive room scenes
reference/rooms-and-chunks.md
"Import my Tiled map"; Tiled and LDtk: what they export, the row flip, global tile ids and flip bits, object anchors, converter or importer package
reference/external-editors.md
SpriteShape terrain: hills, caves and bridges as splines; corner sprites; the collider a shape generates
reference/sprite-shape.md
"A pickup is unreachable", "generate a level from a seed": the reach rule the validator applies; generating so the gate is reachable by construction; seeds and determinism
reference/procedural.md
Creating a Tile Palette, building a rule tile and its rule order, hex and isometric palette sizing
unity-tilemap
Composite and tilemap collider settings, one-way effectors and their arcs, a body that tunnels or snags on seams
unity-physics-2d
→
reference/tilemap-colliders.md
Sorting layers and order across the stack, sorting by position, 2D lights, parallax; the runner behind and in front of one wall
unity-2d-rendering
→
reference/sort-order.md
Confining the camera to the current room; a runner camera; blends between rooms
unity-camera
→
reference/follow-2d.md
Gap, ledge, step and door sizes from the controller's numbers
unity-gameplay
→
reference/level-metrics.md
The motor that walks the level: grounding, slopes, one-way drop-through
unity-2d-controller
→
reference/platformer.md
Cell coordinates, neighbours, paths and turn order on a board
unity-2d-grid
→
reference/coordinates.md
Invariant shapes and ids; the brief's invariants; the lane-run generator this pattern extends
unity-game-brief
→
reference/invariants.md
A journey that plays the level; recorders, situations; the 2D never-inside invariant
unity-play-harness
→
reference/journeys.md
Slicing the tileset, pivots, 9-slice borders
unity-2d-sprites
Frame cost of a load or a tile edit
unity-profiling
Tiles with hairline gaps or shimmer between them, pixel snapping
unity-2d-pixel-perfect
Copy
resources/LevelData.cs
,
TilemapLevelLoader.cs
,
RoomBounds.cs
and
ChunkStitcher.cs
into a Runtime assembly, and
resources/Editor/TilemapLayerStackBuilder.cs
into an Editor one. They need the built-in Tilemap and 2D physics modules and no package. The two scripts are Python 3, standard library only.
Tiled .tmj ── tiled_to_level.py ──┐
a generator, from a seed ─────────┼──► level JSON ══ LevelData ──► level_validate.py   exit 1: unreachable
a painted stack ── Export JSON ───┘                      │
                                                         └──► TilemapLevelLoader ──► six Tilemaps + entities
PieceKnows aboutNever knows about
resources/LevelData.cs
size, layers of tile ids, spawn, gate, pickups, entities with fields, its version
TileBase
, prefabs, scenes
scripts/level_validate.py
the level file and three jump numbersUnity
resources/TilemapLevelLoader.cs
the id → tile table, the six layer names, a spawn delegatewhat a sentry does
the game's
PickupObjectPool
(
unity-scene-flow
),
SentryBrain
(
unity-game-ai
)
an entity's type, position and fieldswhich editor the level came from
Everything that holds levels to their word — "every pickup is reachable", "the same seed gives the same level", "the gate is never behind a gap wider than the jump" — is a statement about the data, checked without opening a scene.

当前情况阅读内容
“我需要哪些 Tilemap 层级”一个关卡需要哪些 Tilemap、哪些参与碰撞、每层如何排序;将实体绘制为标记 Tile
“从 JSON 加载此关卡”关卡文件的键(tile id、spawn、gate、pickup、entity)、单元格索引、以 id 代替 asset 引用、版本、asset 或 JSON
SetTile
、
SetTiles
或
SetTilesBlock
;未绘制任何内容的 block;不会收缩的边界;单元格到世界坐标的转换;调用
SetTile
后立即没有地面 collider;加载后有一帧缺少地面;在游戏过程中破坏 tiles
reference/runtime-tilemap.md
房间与门、进入事件、房间图、软锁、“为 endless runner 拼接区块”、难度递增、叠加式房间场景
reference/rooms-and-chunks.md
“导入我的 Tiled map”;Tiled 和 LDtk 的导出内容、行翻转、全局 tile id 与翻转位、对象锚点、转换器或 importer package
reference/external-editors.md
SpriteShape 地形:用 splines 描绘丘陵、洞穴和桥梁;角落 sprites;shape 生成的 collider
reference/sprite-shape.md
“pickup 不可达”、“根据 seed 生成关卡”:validator 应用的可达规则;通过构造保证 gate 可达;seed 与确定性
reference/procedural.md
创建 Tile Palette、构建 Rule Tile 及其规则顺序、六边形和等距 Tile Palette 尺寸设置
unity-tilemap
Composite 和 Tilemap Collider 设置、单向 effector 及其作用弧、刚体在接缝处穿隧或卡住
unity-physics-2d
→
reference/tilemap-colliders.md
层级栈中的 sorting layer 与顺序、按位置排序、2D 灯光、视差;runner 位于墙后和墙前
unity-2d-rendering
→
reference/sort-order.md
将相机限制在当前房间内;runner camera;房间之间的过渡
unity-camera
→
reference/follow-2d.md
根据 controller 的数值确定 gap、ledge、step 和 door 的尺寸
unity-gameplay
→
reference/level-metrics.md
驱动角色穿行关卡的 motor:落地检测、斜坡、单向下穿
unity-2d-controller
→
reference/platformer.md
棋盘上的单元格坐标、邻居、路径和行动顺序
unity-2d-grid
→
reference/coordinates.md
不变量形状和 id;brief 中的不变量;该模式扩展的 lane-run 生成器
unity-game-brief
→
reference/invariants.md
自动通关关卡的 journey;记录器、situations;2D never-inside 不变量
unity-play-harness
→
reference/journeys.md
切割 tileset、pivots、9-slice 边框
unity-2d-sprites
加载或编辑 tile 的帧开销
unity-profiling
tiles 之间的发丝级缝隙、闪烁、pixel snapping
unity-2d-pixel-perfect
将
resources/LevelData.cs
、
TilemapLevelLoader.cs
、
RoomBounds.cs
和
ChunkStitcher.cs
复制到一个 Runtime 程序集,并将
resources/Editor/TilemapLayerStackBuilder.cs
复制到一个 Editor 程序集。它们需要内置的 Tilemap 和 2D Physics 模块,不需要任何 package。这两个脚本使用 Python 3,仅依赖标准库。
Tiled .tmj ── tiled_to_level.py ──┐
a generator, from a seed ─────────┼──► level JSON ══ LevelData ──► level_validate.py   exit 1: unreachable
a painted stack ── Export JSON ───┘                      │
                                                         └──► TilemapLevelLoader ──► six Tilemaps + entities
组成部分了解什么从不了解什么
resources/LevelData.cs
尺寸、tile id 层级、spawn、gate、pickup、带字段的 entity、版本
TileBase
、prefab、scene
scripts/level_validate.py
关卡文件和三个跳跃数值Unity
resources/TilemapLevelLoader.cs
id → tile 映射表、六个层级名称、spawn delegatesentry 的行为逻辑
游戏的
PickupObjectPool
(
unity-scene-flow
)、
SentryBrain
(
unity-game-ai
)
entity 的类型、位置和字段关卡来自哪个编辑器
所有要求关卡兑现承诺的机制——“每个 pickup 都可达”、“同一个 seed 产生同一个关卡”、 “gate 永远不会位于比跳跃距离更宽的缺口之后”——都是关于数据的陈述,无需打开场景即可检查。

1. Start here

1. 从这里开始

  1. Build the layer stack.
    resources/Editor/TilemapLayerStackBuilder.cs
    → Tools ▸ Unity Dev ▸ 2D Level ▸ Build Layer Stack (Side View) or (Top-Down). It reports the Grid, the six Tilemaps, their colliders and sorting, names any sorting or physics layer the project lacks, and waits. →
    reference/layer-stack.md
  2. Get one level into data. Start from
    templates/sample-level.json
    (a 40 × 12 side run: four roofs, a hazard, a ledge, a launch pad, a sentry, three pickups, a gate); or convert a Tiled map; or paint the stack and export it. →
    reference/level-data.md
  3. Validate it before it is ever loaded:
    bash
    python3 scripts/level_validate.py templates/sample-level.json \
        --jump-height 2.85 --jump-distance 5.17 --fall-multiplier 1.5
    Exit 0 with
    "verdict":"ok"
    . Delete the
    launch-1
    entity and it exits 1 naming
    pickup-2
    ,
    pickup-3
    and the gate — the third roof is four cells up and the apex is 2.85. The jump numbers come from the controller, never from the level:
    --metrics metrics.json
    reads what
    unity-gameplay
    →
    scripts/level_metrics.py
    prints.
  4. Load it:
    csharp
    loader.spawnEntity = (entity, position) => _spawner.Spawn(entity.type, position, entity);
    loader.Load(LevelData.FromJson(levelJson.text));    // throws on a bad shape or an unbound tile id
    _runner.position = loader.SpawnPosition;
    One
    SetTilesBlock
    per layer, colliders synchronised before
    Load
    returns, the marker layer hidden. →
    reference/runtime-tilemap.md
  5. Prove the rendering is the data:
    loader.Verify(level)
    returns 0 mismatched cells. Then write the level's rules into
    templates/LEVEL_INVARIANTS.md
    , one id each.
  1. 构建层级栈。
    resources/Editor/TilemapLayerStackBuilder.cs
    → Tools ▸ Unity Dev ▸ 2D Level ▸ Build Layer Stack (Side View) 或 (Top-Down)。它会报告 Grid、六个 Tilemap、它们的 collider 和排序设置,指出项目缺少的 sorting 或 physics layer,然后等待操作。→
    reference/layer-stack.md
  2. 将一个关卡转换为数据。 从
    templates/sample-level.json
    开始(一个 40 × 12 的侧视跑道: 四个屋顶、一处危险区、一个平台边缘、一个发射台、一个哨兵、三个 pickup 和一道 gate); 也可以转换 Tiled map,或者绘制层级栈后导出。→
    reference/level-data.md
  3. 在加载前进行验证:
    bash
    python3 scripts/level_validate.py templates/sample-level.json \
        --jump-height 2.85 --jump-distance 5.17 --fall-multiplier 1.5
    退出码为 0,并返回
    "verdict":"ok"
    。删除
    launch-1
    entity 后,脚本会返回退出码 1, 并指出
    pickup-2
    、
    pickup-3
    和 gate 不可达——第三个屋顶高出四格,而跳跃最高点为 2.85。跳跃数值来自 controller,而不是关卡:
    --metrics metrics.json
    会读取
    unity-gameplay
    →
    scripts/level_metrics.py
    输出的内容。
  4. 加载关卡:
    csharp
    loader.spawnEntity = (entity, position) => _spawner.Spawn(entity.type, position, entity);
    loader.Load(LevelData.FromJson(levelJson.text));    // throws on a bad shape or an unbound tile id
    _runner.position = loader.SpawnPosition;
    每层只调用一次
    SetTilesBlock
    ,在
    Load
    返回前同步 collider,并隐藏标记层。 →
    reference/runtime-tilemap.md
  5. 证明渲染结果与数据一致:
    loader.Verify(level)
    返回 0 个不匹配的单元格。随后在
    templates/LEVEL_INVARIANTS.md
    中写明关卡规则,每个规则对应一个 id。

2. Pick the branch

2. 选择分支

The level isWhat is differentRead
Hand-built and linear (side run)Nothing — the five steps above
reference/layer-stack.md
A graph of rooms with gated doorsRooms are rectangles at least one screen large; doors are data; the camera listens to a room event
reference/rooms-and-chunks.md
An endless runChunks whose entry height equals the last exit height, pooled, ordered by a seed
reference/rooms-and-chunks.md
Seen from above (depot)
Ground
is floor and carries no collider;
Walls
is the only solid layer; reachability is a flood fill
reference/layer-stack.md
Drawn in Tiled or LDtkThe editor's file is the source; a converter flips the rows once and decodes tile flags
reference/external-editors.md
GeneratedThe path is built first and decorated after; every level is checked, not a sample
reference/procedural.md
Curved, organic groundA spline with a generated collider, outside the cell search
reference/sprite-shape.md
A board of rect, isometric or hexagonal cells (sorting yard)Cells are the gameplay, not just the art
unity-2d-grid
关卡类型不同之处阅读内容
手工构建且线性(侧视跑道)无需额外处理——执行上述五个步骤
reference/layer-stack.md
带门控门的房间图房间是至少一屏大小的矩形;门是数据;camera 监听房间事件
reference/rooms-and-chunks.md
无尽跑酷区块的入口高度等于上一个出口高度;区块采用对象池,并按 seed 排序
reference/rooms-and-chunks.md
俯视视角(depot)
Ground
是地面且不带 collider;
Walls
是唯一实体层;可达性通过 flood fill 计算
reference/layer-stack.md
使用 Tiled 或 LDtk 绘制编辑器文件是数据源;转换器只翻转一次行,并解码 tile 标志
reference/external-editors.md
生成式关卡先生成路径,再进行装饰;每个关卡都接受检查,而非只抽样检查
reference/procedural.md
弯曲的有机地形使用 spline 和生成的 collider,不纳入单元格搜索
reference/sprite-shape.md
由矩形、等距或六边形单元格组成的棋盘(sorting yard)单元格本身就是玩法,而不只是美术表现
unity-2d-grid

Discipline

约束规范

  • The data is the truth; the Tilemap is painted from it. Gameplay asks
    LevelData.IsSolid
    , not
    Tilemap.GetTile
    . A runtime change goes through
    TilemapLevelLoader.SetCell
    , which writes the data and then the tile, so the two cannot drift.
  • Ids, never asset references. A tile is an integer bound in one table; an entity is a type string the game maps to a prefab. A level that references assets cannot be read by a script, survive a rename, or arrive after release.
  • Row 0 is the bottom. That is Tilemap cell space. A converter from an editor that counts rows downwards flips once; a level that loads upside down was flipped zero times or twice.
  • Six layers, fixed names. Collision, drawing in front of the runner and one-way behaviour are properties of a layer, not of a tile. A seventh Tilemap is one the loader never paints and the validator never reads.
  • Validate the file, then load it.
    LevelData.Problems()
    for the shape and
    scripts/level_validate.py
    for reachability, in CI, over every level and every generated seed. A level that loads with a hole is found by a player.
  • The jump belongs to the controller. The validator takes its numbers from the metrics file. A level that passes only with a wider jump is a level bug, not a reason to widen it.
  • Paint in blocks, then synchronise the collider. One
    SetTilesBlock
    per layer, a z size of 1,
    ProcessTilemapChanges()
    before a body is placed on the result.
  • Generate the path first. Reachability that holds by construction needs no repair pass; the check confirms the construction. One failing seed in a thousand is a generator bug.
  • One seeded
    System.Random
    , passed down.
    Never
    UnityEngine.Random
    , never the iteration order of a set, never the clock. The seed goes in the level id and in every bug report.
  • Check the pair, not the chunk. Two fair chunks form a join nobody checked; every chunk has a flat run-in and run-out, and entry height equals the previous exit height.
  • A room is at least one screen, on tile boundaries, and names no camera. It raises one event; the camera, the spawner and the checkpoint listen.
  • What the cell search cannot see gets a journey. Curved ground, a wall jump, a pickup collected mid-arc: list it as waived with the journey that proves it.
  • 数据才是真实来源;Tilemap 是根据数据绘制出来的。 玩法逻辑应调用
    LevelData.IsSolid
    , 而不是
    Tilemap.GetTile
    。运行时变更必须通过
    TilemapLevelLoader.SetCell
    完成;它会先写入 数据,再写入 tile,因此二者不会产生偏差。
  • 只使用 id,绝不使用 asset 引用。 一个 tile 是在映射表中绑定的整数;一个 entity 是游戏 映射到 prefab 的类型字符串。引用 asset 的关卡无法被脚本读取,无法在重命名后继续使用, 也无法在发布后加载。
  • 第 0 行位于底部。 这是 Tilemap 的单元格空间。从按向下方向计行的编辑器转换时,需要 翻转一次;如果关卡加载后上下颠倒,说明翻转了零次或两次。
  • 固定六个层级,并使用固定名称。 碰撞、绘制在 runner 前方以及单向行为都是层级的属性, 而不是 tile 的属性。第七个 Tilemap 既不会被 loader 绘制,也不会被 validator 读取。
  • 先验证文件,再加载。 使用
    LevelData.Problems()
    检查形状,使用
    scripts/level_validate.py
    检查可达性;在 CI 中对每个关卡和每个生成的 seed 都执行检查。 一个加载时带有缺口的关卡,最终只会被玩家发现。
  • 跳跃能力属于 controller。 validator 从 metrics 文件获取相应数值。一个只有在扩大跳跃 距离后才能通过的关卡属于关卡 bug,而不是扩大跳跃距离的理由。
  • 按 block 绘制,然后同步 collider。 每层只调用一次
    SetTilesBlock
    ,z size 设为 1, 在结果上放置 body 之前调用
    ProcessTilemapChanges()
    。
  • 先生成路径。 如果可达性在构造时就已经成立,就不需要修复流程;检查只是确认构造正确。 一千个 seed 中只要有一个失败,就是生成器 bug。
  • 向下传递同一个带 seed 的
    System.Random
    。
    绝不使用
    UnityEngine.Random
    ,绝不依赖 set 的 迭代顺序,也绝不使用时钟。seed 要写入关卡 id,并记录在每个 bug 报告中。
  • 检查组合后的区块,而不只是单个区块。 两个单独合理的区块拼接后,可能形成无人检查过 的连接;每个区块都必须具备平坦的入口和出口,且入口高度等于上一个区块的出口高度。
  • 一个 room 至少占一屏、位于 tile 边界上,并且不指定 camera。 它只发出一个 event; camera、spawner 和 checkpoint 负责监听。
  • 单元格搜索无法发现的部分,需要通过 journey 验证。 曲面地面、蹬墙跳、跳跃弧线中途 收集的 pickup:将它们列为豁免项,并附上用于证明其行为的 journey。

Scope — what this skill does NOT do

范围——本 skill 不负责的内容

Every neighbouring system — palettes and rule tiles (
unity-tilemap
), colliders (
unity-physics-2d
), draw order (
unity-2d-rendering
), the motor (
unity-2d-controller
), cell boards (
unity-2d-grid
), cameras, metrics, journeys — is a row of the table at the top. What is left is the part no single-system skill holds: what a level is when nothing is drawing it, and the checks that can therefore run on it.
所有相邻系统——Tile Palette 和 Rule Tile(
unity-tilemap
)、collider (
unity-physics-2d
)、绘制顺序(
unity-2d-rendering
)、motor(
unity-2d-controller
)、 单元格棋盘(
unity-2d-grid
)、camera、metrics、journeys——都对应顶部表格中的一行。 剩下的部分,是任何单一系统 skill 都不负责的内容:当没有任何东西绘制时,关卡是什么, 以及因此可以针对关卡数据运行的检查。