2.5D Renderer

Stage 4: Улучшаем движение

Учитываем высоту сектора

До этого шага камера двигалась по карте почти как точка на плоскости: менялись x и y, а высота взгляда оставалась условно постоянной. Но в уровне уже есть сектора с разной высотой пола, поэтому при переходе на ступеньку камера должна оказаться выше, а при спуске - ниже.

2.5D Renderer

2D Renderer

Для этого у камеры появляется собственная высота height: это расстояние от пола сектора до глаз игрока. Само положение глаз хранится в camera.z. Когда камера переходит в другой сектор, мы берем высоту пола этого сектора и прибавляем к ней высоту камеры:


  function moveCamera(camera: Camera, direction: number, sector: Sector) {
    const playerCos = camera.angle.cos * camera.moveSpeed;
    const playerSin = camera.angle.sin * camera.moveSpeed;
    const newX = camera.x + direction * playerCos;
    const newY = camera.y + direction * playerSin;
    const height = camera.height || DEFAULT_CAMERA_HEIGHT;
    const newZ = sector.floorHeight! + height;

    return {
      ...camera,
      x: newX,
      y: newY,
      z: newZ,
    };
  }

Главная строка здесь sector.floorHeight + height и она говорит, что глаза игрока всегда находятся на одной и той же высоте относительно пола текущего сектора. Поэтому при движении по лестнице меняется не только положение на карте, но и вертикальное положение камеры.

Чтобы понять, какой пол сейчас под камерой, перед движением ищем сектор, в котором находится камера. Здесь нам уже помогает BSP-дерево: findCameraSector возвращает текущий сектор, а движение пересчитывается с учетом его высоты.


  setSettings((prev) => {
    const sector = findCameraSector(bspTree, prev.camera)!;

    return {
      ...prev,
      camera: moveCamera(prev.camera, isMoving(), sector),
    };
  });

Виджет карты, а именно клавиши Z и X позволяют поднять или опустить камеру вручную. Но даже такое движение нельзя оставлять без границ: камера не должна проваливаться ниже пола и не должна подниматься выше потолка сектора.


  function riseCamera(camera: Camera, direction: number, sector: Sector) {
    const speed = camera.riseSpeed || DEFAULT_CAMERA_VERTICAL_SPEED;
    const height = camera.height || DEFAULT_CAMERA_HEIGHT;
    let newZ = camera.z! + direction * speed;
    
    newZ = Math.max(
      sector.floorHeight! + height,
      Math.min(sector.ceilHeight! - height, newZ)
    );

    return {
      ...camera,
      z: newZ,
    };
  }

По итогу горизонтальное перемещение остается прежним, но высота взгляда теперь зависит от сектора. Благодаря этому ступени и разные уровни пола начинают ощущаться не только в геометрии стен, но и в поведении камеры.

Реализация шага на github