Stage 6: Прочее
Движение между секторами
Данная страница еще в разработке, уверены, что хотите ознакомиться?
2.5D Renderer
2D Renderer
Управление камерой WASD и ZX
До этого предмет был статичным: мы положили его в sector.itemsи при отрисовке сектора доставали его оттуда. Для аптечки на полу этого достаточно. Но враг, ракета или подвижный бонус могут перейти через портал в соседний сектор, и тогда хранить его только в исходном секторе уже нельзя.
Для движущихся сущностей удобнее думать не о “предмете сектора”, а об actor с текущей мировой позицией. Сектор становится вычисляемым состоянием: после движения мы снова определяем, в каком секторе находится точкаx/y, и берем оттудаfloorHeight,ceilHeight и яркость.
interface Actor extends Item {
sector?: Sector;
floorZ?: number;
ceilZ?: number;
vx: number;
vy: number;
}При движении actor сначала проверяет новую позицию: можно ли туда пройти, не пересекается ли радиус со стеной, хватает ли высоты между полом и потолком. Если позиция допустима, actor меняет координаты и перепривязывается к сектору, найденному под его центром.
function moveActor(actor: Actor, dx: number, dy: number, level: Level) {
const nextX = actor.x + dx;
const nextY = actor.y + dy;
const nextSector = findSectorAt(level, nextX, nextY);
if (!nextSector) {
return;
}
actor.x = nextX;
actor.y = nextY;
actor.sector = nextSector;
actor.floorZ = nextSector.floorHeight ?? 0;
actor.ceilZ = nextSector.ceilHeight ?? actor.floorZ;
}Для рендера есть два рабочих варианта. Первый — каждый кадр собирать временную таблицуactorsBySector из глобального списка actors. Второй — хранить двусвязные списки actors прямо в секторах и обновлять эти списки только при переходе между секторами. В обоих случаях во время обхода BSP мы рисуем actors того сектора, который сейчас стал видимым.
const actorsBySector = new Map<Sector, Actor[]>();
for (const actor of level.actors) {
const sector = actor.sector ?? findSectorAt(level, actor.x, actor.y);
if (!sector) continue;
const actors = actorsBySector.get(sector) ?? [];
actors.push(actor);
actorsBySector.set(sector, actors);
}В DOOM использовалась очень похожая идея, только в более практичной форме. Подвижные объекты называлисьmobj_t: у них былиx/y/z, радиус, высота, текущий subsector и ссылки для списков. Сектор находился по текущим координатам черезR_PointInSubsector(x, y), а уже из subsector брали sector.
Важное разделение: секторные списки DOOM были нужны рендеру, а не игровой симуляции. Для столкновений и поиска соседних объектов actor дополнительно связывался с blockmap — сеткой мира, где каждая ячейка знала о линиях и объектах внутри нее. Поэтому переход врага между секторами был не “перекладыванием между комнатами” вручную, а обновлением пространственных ссылок после изменения координат.
Для нашего рендера это означает: статичные предметы могут оставаться вsector.items, но движущиеся сущности лучше хранить отдельно, например в level.actors. Каждый тик обновляет их позицию и текущий сектор, а рендер использует этот сектор только как источник высоты, освещения и принадлежности к видимой области.
Источники по DOOM:комментарии к mobj_tисоздание mobj и P_SetThingPosition.