2D-сцены (.scene2d.ts)
2D-сцены как данные: один файл экспортирует по умолчанию defineScene2d({...}) — узлы по
ключам-именам, у каждого один блок-источник (sprite / tilemap / camera / make, или ни
одного = групповой узел), трансформ, аспекты use(...) и children. Та же грамматика и те же
экранирования, что у 3D-файлов .scene.ts (asset, use, ref, make, $expr переживает
круговой обход); визуальный редактор 2D-сцен (lecodes-2d-editor) читает и пишет эти файлы.
Обзор
// forest.scene2d.ts — листы те же, что на странице спрайт-листов
import props from './props.sprite'
import hero from './hero.sprite'
export default defineScene2d({
env: { background: '#5a6b3b', pixelPerfect: true, gravity: [0, 0] },
layers: { 1: { ySort: true } },
nodes: {
ground: {
tilemap: {
texture: asset('./tiles.png'), tile: 32, atlas: [8, 4],
cells: cells('BgAAAAQAAAAHAAAAAAAAAAQAAAABAAAAAgAAAAAAAAABAAAAAQAAAAIAAAACAAAAAQAAAAEAAAAHAAAAAAAAAA=='),
},
layer: 0,
},
tree1: { sprite: { sheet: props, name: 'tree' }, position: [160, 96], layer: 1 },
player: {
sprite: { sheet: hero, clip: 'idle' },
position: [80, 64], layer: 1,
aspects: [use(CameraFollow, { smooth: 0.12 })],
},
},
})// main.ts
import forest from './forest.scene2d'
forest.open().then(({ scene, nodes }) => { // nodes.player — типизированный Sprite (+ .anim)
nodes.player.anim.play('walk', [1, 0])
})Экспорт по умолчанию — Scene2dHandle: load() инстанцирует один раз (идемпотентно — следующие
вызовы возвращают те же { scene, nodes }), open() загружает и делает сцену активной 2D-сценой,
def — определение. nodes держит каждый узел по его ключу, включая детей; name каждого узла —
его ключ.
Блоки-источники
sprite— одна именованная запись импортированного листа.sprite.ts:{ sheet, name?, clip?, direction? }.nameнеобязателен, когда лист объявляет ровно один спрайт. Листы — только АРТ: узел, который должен сталкиваться, получаетaspects: [use(Shape2D, { box: [14, 9] }), use(Physics2D, { motion: 'static' })](семантика мира принадлежит сцене).tilemap—{ texture, tile, atlas, cells }.cells('…')— непрозрачный однострочный payload (размеры + RLE-ячейки Int32, base64); рукописный код может вместо него передать любое значение{ cols, rows, data }, аencodeCells(cols, rows, data)собирает строку payload (генераторы карт). Строка 0 — верхняя; позиция узла — левый нижний угол карты. С аспектомAutotile2Dна узле ячейки — это id материалов, а не индексы атласа.camera— маркер, чья мировая позиция (+zoom) задаёт стартовый вид. Он ни за чем не следует; для этого прикрепиCameraFollowк отслеживаемому узлу. Верхнеуровневый пропcamera: { position, zoom }— альтернатива без узла (узел камеры побеждает).make—make(factoryFn, { ...literal args }): поддерево, собранное кодом (фабрика возвращаетNode2Dили промис с ним), запускается после того, как все узлы существуют, чтобы аргументыref()разрешились. Возвращённый узел становится дочерним этому и добавляется в сцену вместе со своими детьми.- ни одного — обычный групповой узел / маркер (зоны
Trigger2D, точки спавна).
cells(payload: string): { cols, rows, data: Int32Array } // декодирует; бросает на повреждённом payload
encodeCells(cols, rows, data: ArrayLike<number>): string // кодирует; data.length должен быть cols*rowsПропы узла
position · rotation (градусы, против часовой) · scale (число или [x, y]) · layer · z · visible ·
color / opacity / flipX / flipY (узлы-спрайты) · locked (только для редактора) · aspects ·
children.
env, layers, physics
env.background,env.filter— передаются вScene2D.env.gravity— наличие вызываетPhysics2D.configure({ gravity })до создания любого тела ([0, 0]для вида сверху). Опусти, чтобы не трогать гравитацию мира.env.pixelPerfect— привязывать камеру к целым экранным пикселям: задаётCameraFollow.pixelSnap, а стартовая позиция узла камеры округляется до целых единиц.layers: { 1: { ySort: true } }— настройка по слоям; Y-сортировка заставляет спрайты ниже на экране перекрывать те, что выше (глубина вида сверху).
CameraFollow
node.aspect(CameraFollow, {
scene?: Scene2D // чья камера следует — загрузчик файла сцены подключает её сам
smooth?: number // постоянная времени сглаживания, секунды; по умолчанию 0.12; 0 = жёстко к узлу
offset?: [x, y] // смещение камеры от узла, мировые единицы; по умолчанию [0, 0]
pixelSnap?: boolean // целые экранные пиксели; по умолчанию true (env.pixelPerfect в файле сцены)
})Встроенная следящая камера — обычный аспект, прикреплённый к узлу, за которым камера должна
следить. Работает в поздней фазе (update), после физики, так что камера попадает на позицию,
нарисованную в этом кадре. pixelSnap измеряется по реальной проекции, поэтому держится при любом
зуме. Из рукописного кода передай сцену сам: hero.aspect(CameraFollow, { scene }).
проверка типов проекта сейчас отвергает node.aspect(CameraFollow, …) с ошибкой this-контекста
(дефект типизации SDK); код компилируется и работает. use(CameraFollow, …) в файле сцены проверку
типов проходит.
В режиме редактирования аспекты хранятся как данные (при редактировании ничего не падает и не стреляет); воспроизведение клипов продолжается. Префабов (сцена в сцене) в 2D-грамматике пока нет.