Тема 4 из 6

Камера от первого лица

Камера

Камера в Ursina — тоже Entity: camera. Её можно двигать, поворачивать и делать дочерней.

Игра от первого лица устроена так: есть невидимый «игрок» на высоте глаз, камера — его ребёнок. Поворот налево-направо (rotation_y) крутят у игрока — вместе с ним поворачивается и направление ходьбы. Наклон вверх-вниз (rotation_x) — только у камеры, чтобы игрок не «ложился» на пол.

player = Entity(y=1.8)              # высота глаз
camera.parent = player
camera.position = (0, 0, 0)
mouse.locked = True                 # курсор спрятан и не упирается в края

def update():
    player.rotation_y += mouse.velocity[0] * 40
    camera.rotation_x -= mouse.velocity[1] * 40
    camera.rotation_x = clamp(camera.rotation_x, -90, 90)
  • mouse.velocity — насколько сдвинулась мышь за кадр;
  • положительный rotation_x — взгляд вниз, поэтому движение мыши вверх вычитают;
  • наклон ограничивают [-90°, 90°], иначе камера перевернётся.

Ходьба по взгляду

Если игрок повёрнут на угол yaw, то по полу:

  • «вперёд» — (sin yaw, cos yaw);
  • «вправо» — (cos yaw, -sin yaw).
r = math.radians(player.rotation_y)
fwd = held_keys["w"] - held_keys["s"]
side = held_keys["d"] - held_keys["a"]
x = fwd * math.sin(r) + side * math.cos(r)
z = fwd * math.cos(r) - side * math.sin(r)

Направление нормализуют, чтобы по диагонали не идти быстрее. У каждой Entity есть и готовые векторы forward и right — с ними то же самое: player.position += (player.forward * fwd + player.right * side).normalized() * speed * time.dt.

Готовый контроллер

В Ursina уже есть игрок от первого лица — с ходьбой, мышью, прыжком и гравитацией:

from ursina.prefabs.first_person_controller import FirstPersonController

player = FirstPersonController(speed=5, mouse_sensitivity=Vec2(40, 40))

Ему нужен пол с коллайдером: Entity(model="plane", scale=40, collider="box"). В последних темах курса вы соберёте игру именно на нём, а пока полезно понимать, что у него внутри.

Поле зрения

Враг «видит» игрока, если тот близко и попадает в конус зрения:

  1. расстояние не больше дальности (сравнивают квадраты — без корня);
  2. угол на игрока: math.degrees(math.atan2(dx, dz)) — так же, как rotation_y;
  3. разница с углом взгляда, приведённая к [-180, 180), по модулю не больше половины угла обзора.

Если ещё нужно, чтобы между врагом и игроком не было стен, добавляют луч — о нём в следующей теме.

Итог

  • камера — Entity camera; от первого лица: camera.parent = player, mouse.locked = True;
  • yaw — у игрока, pitch — у камеры, ограничен [-90, 90]; мышь вверх — pitch уменьшается;
  • вперёд (sin, cos), вправо (cos, -sin); forward, right;
  • FirstPersonController; поле зрения — расстояние и угол с половиной fov.