Тема 3 из 6

Движение и повороты

Каждый кадр — update()

Если в программе есть функция update(), Ursina вызывает её каждый кадр. То же — метод update(self) у своего класса-наследника Entity.

from ursina import *

app = Ursina()
player = Entity(model="cube", color=color.orange)

def update():
    speed = 5 * time.dt                        # 5 единиц в секунду
    player.x += (held_keys["d"] - held_keys["a"]) * speed
    player.z += (held_keys["w"] - held_keys["s"]) * speed

def input(key):                                # один раз в момент нажатия
    if key == "space":
        player.y += 1

app.run()
  • time.dt — длительность кадра в секундах. Скорость × time.dt — шаг, одинаковый по скорости на любом компьютере.
  • held_keys["w"] — 1, если клавиша зажата, иначе 0.
  • input(key) получает нажатия: "space", "a", "left mouse down", а отпускание — "a up".

Векторы

Vec3 — тройка чисел: положение или направление.

d = target.position - player.position       # вектор «от игрока к цели»
print(d.length())                           # расстояние
direction = d.normalized()                  # то же направление, длина 1
player.position += direction * 5 * time.dt
print(distance(player, target))             # расстояние между Entity

Чтобы не проскочить цель, сравнивают расстояние с длиной шага: если ближе — встают ровно в цель.

Повороты

rotation_y — поворот вокруг вертикальной оси в градусах. Угол «закольцован»: 370° — то же, что 10°. Чтобы повернуться по кратчайшему пути, разницу углов приводят к отрезку [-180, 180):

diff = (target - current + 180) % 360 - 180      # от −180 до 180

Знак diff подсказывает сторону поворота, а модуль — сколько осталось. Угол на цель по горизонтали: math.degrees(math.atan2(dx, dz)).

Повернуть Entity лицом к точке можно и готовым методом: tower.look_at(target). А «вперёд» для объекта — entity.forward, «вправо» — entity.right.

Маршруты

Патруль по точкам — это движение к цели, только целей несколько и они идут по кругу. Если за кадр объект проходит больше, чем осталось до точки, остаток шага нужно потратить на путь к следующей — иначе объект будет «спотыкаться» на каждой точке.

Итог

  • update() каждый кадр, time.dt; held_keys[...] — зажата ли клавиша; input(key) — нажатия;
  • Vec3: length(), normalized(), distance(a, b); не проскакивать цель;
  • углы: (target - current + 180) % 360 - 180, итог — % 360;
  • маршрут по кругу: остаток шага переносится на следующий отрезок.