Мини-игра от первого лица
Игрок от первого лица
Соберём всё вместе. Основа — готовый FirstPersonController: ходьба на WASD, обзор мышью, прыжок на пробел, гравитация.
from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model="plane", scale=40, texture="grass", collider="box")
wall = Entity(model="cube", texture="brick", position=(3, 1, 5), scale=(4, 2, 1), collider="box")
player = FirstPersonController(speed=5)
app.run()
Контроллеру нужны коллайдеры: пол — чтобы стоять, стены — чтобы в них упираться. Без collider= игрок провалится сквозь пол.
Предметы
Кристаллы — обычные Entity в списке. Каждый кадр проверяем расстояние до игрока:
for c in list(crystals): # копия: из списка будем удалять
if distance(player.position + Vec3(0, 1, 0), c.position) < 1.5:
crystals.remove(c)
destroy(c)
score += 1
destroy(entity)убирает Entity из сцены;- из своего списка объект нужно убрать отдельно — и делать это при обходе копии списка;
player.position— это ноги игрока; кристаллы висят на высоте, поэтому прибавляем рост.
Текст и время
label = Text("Кристаллы: 0", position=(-0.85, 0.45)) # координаты экрана, а не мира
label.text = f"Кристаллы: {score}"
Обратный отсчёт округляют вверх (math.ceil): пока остаётся хоть доля секунды, таймер показывает 0:01. Минуты и секунды — f"{s // 60}:{s % 60:02d}".
Отложить событие: invoke(функция, delay=2) — вызвать через 2 секунды.
Лабиринт из карты
Как и в 2D, уровень удобно задать строками. Клетка (row, col) → положение (col, 1, row):
for row, line in enumerate(MAZE):
for col, ch in enumerate(line):
if ch == "#":
Entity(model="cube", texture="brick", position=(col, 1, row), scale=(1, 2, 1), collider="box")
Поиск пути
Чтобы найти кратчайший путь по клеткам с учётом стен, используют поиск в ширину:
from collections import deque
dist = {start: 0}
queue = deque([start])
while queue:
r, c = queue.popleft() # клетки выходят в порядке удалённости
for nr, nc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
if клетка в карте, не стена и ещё не посещена:
dist[(nr, nc)] = dist[(r, c)] + 1
queue.append((nr, nc))
Очередь гарантирует, что клетки обрабатываются по возрастанию расстояния, — поэтому первая найденная цель и есть ближайшая. Так же ищут путь враги в играх: от игрока «волной» по клеткам.
Итог
FirstPersonControllerи коллайдеры у пола и стен;- сбор: расстояние,
destroy, удаление из своего списка по копии; Text, таймер сmath.ceilи:02d,invoke;- лабиринт из строк, кратчайший путь — поиск в ширину с
deque.
Вы собрали мини-игру от первого лица: сцена, движение, камера, столкновения и цель на время. Попробуйте добавить в неё врага из темы про камеру — с полем зрения, который идёт к игроку по пути из поиска в ширину.