Вектор движения и скорость
Скорость — это не одно число, а стрелка: и куда, и насколько быстро. Вектор движения делает физику игры понятной и честной.
Суть: вектор скорости (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 округляй позицию только при отрисовке, точность держи в векторе.
Итог: движение — это вектор скорости, прибавляемый к позиции каждый кадр. Думай стрелками, а не отдельными осями, и физика станет честной.