Вектор движения и скорость

Скорость — это не одно число, а стрелка: и куда, и насколько быстро. Вектор движения делает физику игры понятной и честной.
Суть: вектор скорости (vx, vy) хранит направление и величину движения. Позиция каждый кадр меняется на скорость, умноженную на dt. Pygame даёт готовый Vector2.

До сих пор мы двигали героя «по осям»: влево уменьшает x, вниз увеличивает y. Это работает, но становится неуклюжим, когда движение идёт по диагонали или под углом. Профессиональный подход — думать о движении как о векторе: стрелке, у которой есть направление и длина.

Вектор скорости — это пара чисел (vx, vy). Положительный vx — движемся вправо, отрицательный — влево. Положительный vy — вниз, отрицательный — вверх. Каждый кадр мы прибавляем к позиции скорость, умноженную на dt: x += vx * dt. Это та же формула «расстояние = скорость × время», просто по обеим осям сразу.

Pygame даёт удобный класс pygame.math.Vector2. С ним позицию и скорость можно складывать как обычные числа: pos += vel * dt. Внутри он сам разложит по осям. А ещё Vector2 умеет считать длину, нормализоваться и поворачиваться — пригодится для диагоналей и врагов, летящих к игроку.

Как работает под капотом

Вектор — это стрелка из точки (0,0) в точку (vx, vy). Его длина — это скорость по теореме Пифагора. Сложение позиции и скорости двигает объект вдоль стрелки:

        vy
        ^
        |       . (vx, vy)
        |      /
        |     /  длина = sqrt(vx*vx + vy*vy)
        |    /
        |   /
        +--/----------------> vx
       (0,0)

   новая_позиция = позиция + вектор_скорости * dt

Vector2 в pygame (читаем):

pos = pygame.math.Vector2(100, 100)
vel = pygame.math.Vector2(150, -80)   # вправо и вверх

while running:
    dt = clock.tick(60) / 1000.0
    pos += vel * dt          # двигаем по вектору
    player_rect.center = (round(pos.x), round(pos.y))

Векторную математику можно полностью проверить без графики. Реализуем мини-вектор и подвигаем точку. Попробуй сам:

import math

def move(pos, vel, dt):
    return (pos[0] + vel[0] * dt, pos[1] + vel[1] * dt)

def length(vel):
    return math.hypot(vel[0], vel[1])   # sqrt(vx^2 + vy^2)

pos = (100.0, 100.0)
vel = (150.0, -80.0)   # вправо и вверх

print("скорость (длина вектора):", round(length(vel), 1))
for frame in range(3):
    pos = move(pos, vel, 0.1)
    print(f"кадр {frame+1}: позиция = ({pos[0]:.0f}, {pos[1]:.0f})")

Нормализация: честная диагональ

Вот коварный баг почти каждого новичка. Если при нажатии «вправо» и «вверх» одновременно ты просто прибавишь полную скорость к обеим осям, по диагонали герой поедет быстрее, чем по прямой — примерно в 1.41 раза (это корень из двух). Игроки чувствуют такое сразу: «по диагонали быстрее». Лечится нормализацией: вектор направления приводят к длине 1, а потом умножают на скорость. Тогда длина итогового вектора всегда равна скорости, в любую сторону.

У Vector2 для этого есть метод normalize (и безопасный normalize_ip), но помни про нулевой вектор — нормализовать «никуда» нельзя, будет ошибка, поэтому сначала проверяй длину. Это та же осторожность, что и при выстреле в курсор. Нормализация — один из тех маленьких приёмов, который отличает «вроде работает» от «ощущается правильно». Освоив его, ты будешь замечать кривую диагональ в чужих играх и точно знать, как её починить.

Думать векторами выгодно ещё и потому, что у Vector2 есть готовая богатая математика. Метод distance_to мгновенно даёт расстояние до другой точки — пригодится для ИИ и коллизий по дальности. length возвращает скорость как число, rotate поворачивает вектор на угол (круговое движение, разлёт осколков), а lerp плавно интерполирует между двумя векторами для мягкого следования камеры за героем. Всё это ты получаешь бесплатно, стоит лишь хранить позиции и скорости как Vector2 вместо отдельных x и y. Векторное мышление — один из тех сдвигов в голове, после которого многие игровые задачи внезапно становятся простыми и однотипными, а код — заметно чище и короче.

Частые ошибки

  • Двигать по диагонали быстрее — если просто прибавлять и vx, и vy, диагональ выходит длиннее. Нужна нормализация (следующий урок коснётся).
  • Забыть dt при сложении векторов — снова привязка к FPS.
  • Путать позицию и скорость — это разные векторы: где объект и куда он летит.

Best practices

  • Храни позицию и скорость как Vector2 — код становится чистым.
  • Скорость задавай в пикселях в секунду, прибавляй vel * dt.
  • Для Rect округляй позицию только при отрисовке, точность держи в векторе.

Итог: движение — это вектор скорости, прибавляемый к позиции каждый кадр. Думай стрелками, а не отдельными осями, и физика станет честной.

Проверьте себя
1. Что хранит вектор скорости (vx, vy)?
AТолько направление
BТолько величину
CИ направление, и величину движения
DЦвет объекта
2. Как обновляется позиция объекта каждый кадр?
Aпозиция = скорость
Bпозиция += скорость * dt
Cпозиция -= dt
Dпозиция = dt * dt