Stage 6: Прочее
Спрайт предмета
Данная страница еще в разработке, уверены, что хотите ознакомиться?
2.5D Renderer
2D Renderer
Управление камерой WASD и ZX
На предыдущем шаге мы уже научились находить прямоугольную область, в которой должен появиться предмет: экранная X-координата берется из угла до предмета, а нижняя граница привязана к высоте пола сектора. Теперь вместо одноцветной заливки растянем внутри этой области простую bitmap-текстуру.
Для аптечки используем textures.health_sprite. Это маленькая сетка 8 на 8. Нулевой цвет прозрачный, единичный цвет красный:
health_sprite: {
scale: 1,
width: 8,
height: 8,
bitmap: [
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
],
colors: [
{ r: 0, g: 0, b: 0, a: 0 },
{ r: 255, g: 80, b: 80, a: 255 },
],
}item.radius остается размером в мире, тем же радиусом, которым предмет отмечен на карте. Поэтому сначала переводим этот радиус в угловой размер черезMath.atan2(radius, distance), а затем в экранные пиксели. Если предмет занимает почти ширину коридора в мире, близко к камере он тоже будет занимать большую область на экране. Даже когда камера оказывается внутри радиуса предмета, дистанция для масштаба все равно считается до центра окружности: так спрайт не переключается в отдельный режим и плавно растет по мере приближения к центру. Высоту не берем из сектора: она должна сохранять пропорции самой текстуры. Для квадратной аптечки 8 на 8 высота будет равна ширине, а для вытянутого спрайта изменится по соотношениюtexture.height / texture.width.
const texture = textures.health_sprite;
const itemAngularRadius = Math.atan2(item.radius, distance) * 180 / Math.PI;
const screenHalfWidth =
(itemAngularRadius / camera.fov.degrees) * camera.screen.width;
const ceilZ = sector.ceilHeight ?? floorZ;
const screenBottomY = screenCenterY - (floorZ - camera.z) * projectionScale;
const screenCeilY = screenCenterY - (ceilZ - camera.z) * projectionScale;
const textureRect =
scaleTextureToWidth(texture, screenX, screenBottomY, screenHalfWidth * 2);
const itemBounds = {
x1: textureRect.x1,
x2: textureRect.x2,
y1: Math.max(textureRect.y1, Math.ceil(screenCeilY)),
y2: textureRect.y2,
};
const visibleBounds = fitTextureIntoBounds(itemBounds, screenBounds);При рисовании каждого пикселя внутри полученного прямоугольника переводим его локальную позицию в координаты текстуры. Если texel прозрачный, ничего не пишем в буфер. Если texel видимый, затемняем его яркостью сектора и рисуем. Голубая рамка показывает bounding этой текстуры: bitmap прижимается к левому и правому краю внутренней области, а высота вычисляется из пропорции texture.height / texture.width. Поэтому спрайт не растягивается до верхней границы, если его собственные пропорции этого не требуют. Если raw-bounding спрайта оказывается шире или выше canvas, его размер не сжимается до экрана. Цикл рисования обрезается одновременно по экрану и по item bounding, но координаты текстуры продолжают считаться от исходных raw-границ, поэтому видимая часть не растягивается. Верхняя граница дополнительно обрезается проекцией потолка текущего сектора только в видимой области рисования, так что полная высота спроецированного item не уменьшается, но спрайт не может залезть вышеceilHeight.
const textureInnerRect = {
x1: textureRect.x1 + 1,
x2: textureRect.x2 - 1,
y1: textureRect.y1 + 1,
y2: textureRect.y2 - 1,
};
const itemInnerBounds = {
x1: itemBounds.x1 + 1,
x2: itemBounds.x2 - 1,
y1: itemBounds.y1 + 1,
y2: itemBounds.y2 - 1,
};
const drawBounds = fitTextureIntoBounds(textureInnerRect, itemInnerBounds);
if (!drawBounds) return;
const visibleDrawBounds = fitTextureIntoBounds(drawBounds, screenBounds);
if (!visibleDrawBounds) return;
const textureWidth = textureInnerRect.x2 - textureInnerRect.x1;
const textureHeight = textureInnerRect.y2 - textureInnerRect.y1;
const tx =
Math.floor(((x - textureInnerRect.x1) / textureWidth) * texture.width);
const ty =
Math.floor(((y - textureInnerRect.y1) / textureHeight) * texture.height);
const color = getTextureColor(texture, tx, ty);
if (color.a === 0) {
continue;
}
drawPixel(buffer, x, y, applyBrightness(color, projected.sector.brightness));Пока все предметы рисуются одной текстурой аптечки. Дальше можно будет выбирать текстуру поitem.type или хранить имя спрайта прямо вItem.