Бесплатный урок с нашим репетитором!

Узнать подробнее

Новая Школа - онлайн-школа подготовки к ЕГЭ
При поддержке
Посмотреть все вопросы нейросети
Бесплатный пробный урок с репетитором

Узнай больше про репетиторов Новой Школы и запишись на бесплатный пробный урок. Мы проверим твой уровень знаний и составим план обучения по любому предмету и классу

Вопрос от Анонимного юзера 10 мая 2026 14:31

From ursina import * from ursina.prefabs.first_person_controller import FirstPersonController import random import math app = Ursina() # ——— НАСТРОЙКИ ——— NOTES_TOTAL = 10 FOREST_SIZE = 60 TREE_COUNT = 180 # ——— СОСТОЯНИЕ ИГРЫ ——— game_state = { 'notes_collected': 0, 'alive': True, 'won': False, 'flashlight_on': True, 'monster_speed': 2.5, 'monster_aggro': False, } # ——— НЕБО И ТУМАН ——— window.color = color.black window.title = 'Forest Horror' window.borderless = False scene.fog_density = 0.04 scene.fog_color = color.rgba(5, 5, 10, 255) # ——— ЗЕМЛЯ ——— ground = Entity( model='plane', scale=(FOREST_SIZE * 2, 1, FOREST_SIZE * 2), color=color.rgba(15, 25, 10, 255), texture='grass', texture_scale=(FOREST_SIZE, FOREST_SIZE), collider='box', ) # ——— ДЕРЕВЬЯ ——— trees = [] def spawn_trees(): for _ in range(TREE_COUNT): x = random.uniform(-FOREST_SIZE, FOREST_SIZE) z = random.uniform(-FOREST_SIZE, FOREST_SIZE) if abs(x) < 5 and abs(z) < 5: continue h = random.uniform(8, 18) trunk = Entity( model='cylinder', color=color.rgba(40, 25, 15, 255), scale=(0.5, h, 0.5), position=(x, h / 2, z), collider='box', ) crown_y = h + random.uniform(1.5, 3) crown = Entity( model='sphere', color=color.rgba(10, random.randint(35, 60), 10, 255), scale=(random.uniform(3, 5), random.uniform(3, 5), random.uniform(3, 5)), position=(x, crown_y, z), ) trees.append(trunk) trees.append(crown) spawn_trees() # ——— ЗАПИСКИ ——— notes_entities = [] def spawn_notes(): placed = 0 attempts = 0 while placed < NOTES_TOTAL and attempts < 1000: attempts += 1 x = random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5) z = random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5) if abs(x) < 6 and abs(z) < 6: continue note = Entity( model='quad', color=color.rgba(220, 210, 180, 255), scale=(0.6, 0.8, 1), position=(x, 1.2, z), billboard=True, collider='box', ) note.note_id = placed notes_entities.append(note) placed += 1 spawn_notes() # ——— ВЫХОД (активируется после всех записок) ——— exit_entity = Entity( model='cube', color=color.rgba(0, 200, 100, 180), scale=(2, 3, 2), position=(0, 1.5, -FOREST_SIZE + 8), collider='box', enabled=False, ) exit_label = Entity( model='quad', color=color.rgba(0, 255, 100, 200), scale=(3, 1), position=(0, 4, -FOREST_SIZE + 8), billboard=True, enabled=False, ) # ——— ИГРОК ——— player = FirstPersonController() player.position = (0, 1, 0) player.speed = 5 player.mouse_sensitivity = Vec2(60, 60) # ——— ФОНАРИК ——— flashlight = PointLight( parent=camera, position=(0.3, -0.2, 1.5), color=color.rgba(255, 240, 200, 255), ) flashlight.attenuation = (0.1, 0.05, 0.02) ambient = AmbientLight() ambient.color = color.rgba(8, 8, 15, 255) # ——— МОНСТР ——— monster_body = Entity( model='cube', color=color.rgba(80, 0, 0, 255), scale=(1.2, 2.4, 1.2), position=(random.uniform(20, 40), 1.2, random.uniform(20, 40)), collider='box', ) monster_head = Entity( model='sphere', color=color.rgba(60, 0, 0, 255), scale=(1.0, 1.0, 1.0), position=(0, 0, 0), parent=monster_body, ) monster_head.y = 1.6 monster_eye_l = Entity( model='sphere', color=color.rgba(255, 0, 0, 255), scale=(0.18, 0.18, 0.18), parent=monster_head, position=(-0.22, 0.1, 0.45), ) monster_eye_r = Entity( model='sphere', color=color.rgba(255, 0, 0, 255), scale=(0.18, 0.18, 0.18), parent=monster_head, position=(0.22, 0.1, 0.45), ) monster_state = { 'patrol_target': Vec3(random.uniform(-30, 30), 1.2, random.uniform(-30, 30)), 'patrol_timer': 0, 'chase_timer': 0, 'roar_timer': 0, } # ——— UI ——— notes_ui = Text( text='Записки: 0 / 10', position=(-0.85, 0.47), scale=1.4, color=color.rgba(220, 210, 180, 255), background=True, ) hint_ui = Text( text='Найди 10 записок и выберись из леса!', position=(-0.0, -0.35), origin=(0, 0), scale=1.2, color=color.rgba(200, 200, 200, 200), ) flashlight_ui = Text( text='[F] Фонарик: ВКЛ', position=(-0.85, 0.42), scale=1.1, color=color.rgba(255, 240, 150, 200), background=True, ) overlay = Entity( parent=camera.ui, model='quad', scale=(2, 1), color=color.rgba(0, 0, 0, 0), z=-1, ) # ——— ЭФФЕКТ СТРАХА (красный экран) ——— fear_overlay = Entity( parent=camera.ui, model='quad', scale=(2, 1), color=color.rgba(150, 0, 0, 0), z=-0.9, ) # ——— CROSSHAIR ——— crosshair = Text(text='+', origin=(0,0), scale=2, color=color.rgba(200,200,200,180)) # ——— ЭКРАН СМЕРТИ / ПОБЕДЫ ——— end_screen = Entity( parent=camera.ui, model='quad', scale=(2, 1), color=color.rgba(0, 0, 0, 0), z=-0.5, enabled=False, ) end_text = Text( text='', origin=(0, 0), scale=3, color=color.white, enabled=False, ) hint_timer = 3.0 # ——— ЗВУК ШАГОВ МОНСТРА (текстовый эффект) ——— monster_sound_text = Text( text='', position=(0, -0.42), origin=(0, 0), scale=1.3, color=color.rgba(255, 50, 50, 0), ) sound_timer = 0 def show_monster_sound(msg): monster_sound_text.text = msg monster_sound_text.color = color.rgba(255, 50, 50, 230) def update_monster_sound(dt): global sound_timer if monster_sound_text.color.a > 0: a = max(0, monster_sound_text.color.a - dt * 120) monster_sound_text.color = color.rgba(255, 50, 50, int(a)) ROAR_MESSAGES = [ '...хруст веток...', '...низкое рычание...', '...ОНО РЯДОМ...', '...тяжёлые шаги...', '...ты слышишь дыхание...', ] def game_over(won=False): game_state['alive'] = False game_state['won'] = won end_screen.enabled = True end_screen.color = color.rgba(0, 0, 0, 180) end_text.enabled = True if won: end_text.text = 'ТЫ ВЫЖИЛ!\nНажми R чтобы начать заново' end_text.color = color.rgba(0, 255, 100, 255) else: end_text.text = 'ТЫ ПОЙМАН...\nНажми R чтобы начать заново' end_text.color = color.rgba(255, 30, 30, 255) mouse.locked = False def update(): global hint_timer, sound_timer if not game_state['alive']: if held_keys['r']: import os, sys os.execv(sys.executable, ['python'] + sys.argv) return dt = time.dt # Прячем подсказку if hint_timer > 0: hint_timer -= dt if hint_timer <= 0: hint_ui.color = color.rgba(200, 200, 200, 0) # ——— ФОНАРИК ——— if held_keys['f']: pass # обрабатывается в input() # ——— ПОДБОР ЗАПИСОК ——— for note in notes_entities[:]: if note.enabled: dist = distance(player, note) if dist < 2.2: note.enabled = False game_state['notes_collected'] += 1 n = game_state['notes_collected'] notes_ui.text = f'Записки: {n} / 10' show_monster_sound(f'Записка #{n} найдена!') monster_sound_text.color = color.rgba(200, 220, 180, 230) # Монстр становится быстрее game_state['monster_speed'] = 2.5 + n * 0.35 game_state['monster_aggro'] = n >= 3 if n == NOTES_TOTAL: notes_ui.text = 'Все записки найдены! Найди ВЫХОД!' exit_entity.enabled = True exit_label.enabled = True exit_label.text = '>>> ВЫХОД <<<' # ——— ВЫХОД ——— if exit_entity.enabled: if distance(player, exit_entity) < 3: game_over(won=True) return # ——— МОНСТР ——— monster_pos = monster_body.position player_pos = player.position dist_to_player = distance(monster_body, player) # Направление к игроку dir_to_player = (player_pos - monster_pos) dir_to_player.y = 0 if dir_to_player.length() > 0.01: dir_to_player = dir_to_player.normalized() # Агрессия: если видит (близко) или агро-режим seeing_player = dist_to_player < 18 chasing = seeing_player or game_state['monster_aggro'] speed = game_state['monster_speed'] if chasing: if dist_to_player > 1.5: monster_body.position += dir_to_player * speed * dt # Поворот к игроку angle = math.degrees(math.atan2(dir_to_player.x, dir_to_player.z)) monster_body.rotation_y = angle else: # Патруль monster_state['patrol_timer'] -= dt patrol_dir = (monster_state['patrol_target'] - monster_pos) patrol_dir.y = 0 if patrol_dir.length() < 2 or monster_state['patrol_timer'] <= 0: monster_state['patrol_target'] = Vec3( random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5), 1.2, random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5), ) monster_state['patrol_timer'] = random.uniform(4, 10) elif patrol_dir.length() > 0.01: monster_body.position += patrol_dir.normalized() * (speed * 0.5) * dt monster_body.y = 1.2 # Рычание monster_state['roar_timer'] -= dt if monster_state['roar_timer'] <= 0: if chasing: show_monster_sound(random.choice(ROAR_MESSAGES[-2:])) monster_state['roar_timer'] = random.uniform(3, 6)

Ответ нейросети

10 мая 2026 14:31

Ниже подробное решение и пошаговое объяснение данного кода игры. Цель здесь — понять, как всё работает, какие сущности создаются, как взаимодействуют объекты и как движется монстр. Это помогает закрепить представления о кооперации объектов, скриптах обновления кадра и простой логике ИИ.

Краткое резюме задачи

  • Игра в лесной хоррор: игрок собирает заметки, после сбора всех заметок открывается выход.
  • Есть монстр, который патрулирует и гонится за игроком в зависимости от условий.
  • Мир генерируется случайно: деревья, заметки, выход появляется после последней заметки.
  • Визуальные эффекты и UI поддерживают сюжет: фон, туман, экран страха, подсказки, счётчик заметок.
  1. Что здесь за глобальные элементы кода
  • Константы настройки окружения
    • NOTES_TOTAL = 10: всего заметок в мире.
    • FOREST_SIZE = 60 и TREE_COUNT = 180: размеры леса и количество деревьев.
  • Состояние игры (game_state)
    • notes_collected, alive, won, flashlight_on, monster_speed, monster_aggro: основная логика геймплея (сколько заметок собрано, жив ли игрок, достиг ли он победы, скорость монстра, угроза монстра).
  • Набор сцен и объектов
    • Небо/туман, земля (ground), деревья (spawn_trees)
    • Заметки (spawn_notes)
    • Выход (exit_entity, exit_label)
    • Игрок (FirstPersonController)
    • Фонарик (PointLight) и освещение сцены (AmbientLight)
    • Монстр (monster_body, monster_head, глаза монстра)
    • Монстр в состоянии патруля/атаки (monster_state)
    • UI элементы: счётчик заметок, подсказка, подсветка фонаря, страха/overlay, крестик, экран окончания
    • Звуковой текст «рычания» монстра (monster_sound_text) и таймеры
  • Основная логика обновления (update)
    • Обработка сбора заметок
    • Разблокировка выхода после сборa всех заметок
    • Логика монстра: поведение (патруль vs агрессия), поворот к игроку, скорость перемещения
    • Взаимодействие с UI и звуком
  1. Как создаются мир и объекты (генерация)
  • Земля
    • Entity с моделью 'plane', масштабом, текстурой 'grass', коллайдером 'box'.
    • Цвет и текстура задают внешний вид поверхности.
  • Деревья
    • spawn_trees(): для каждого дерева создаются два объекта:
      • Ствол: цилиндр, цвет коричневый, масштаб (0.5, h, 0.5), высота h рандомная [8,18], position случайный в пределах FOREST_SIZE, чтобы не пересекаться с игроком.
      • Крона: сфера над стволом, зеленоватый оттенок, размер головы рандомизирован.
    • Добавляются в массив trees (хотя по коду они не используются далее, но это визуальная часть сцены).
  • Заметки
    • spawn_notes(): размещаются на случайных координатах в пределах леса, но вне области около центра (чтобы не создать моментальный сбор).
    • Модель 'quad', цвет светло-коричневый/бежевый, размер и положение так, чтобы заметки были видны и устойчивы к движению игрока (billboard=True, чтобы они всегда смотрели на камеру).
    • Каждая заметка получает note_id и хранится в notes_entities.
  • Выход
    • Изначально отключён (enabled=False). После сборa всех заметок он становится активным.
    • exit_label: визуальная подсказка на экране, помогающая понять, что выход доступен.
  • Монстр
    • Базовая форма: monster_body (куб) и monster_head (сфера) — частично визуальные элементы, которые следят за игроком.
    • Глаза монстра - маленькие сферы, прикреплённые к голове.
    • monster_state хранит целевые точки патруля, таймеры на патруль, агрессию и рычание.
    • Монстр двигается по миру и имеет возможность быстро приближаться к игроку, если он в зоне агрессии.
  • UI и эффекты
    • UI тексты: счетчик заметок, подсказка, статус фонаря.
    • fear_overlay - красный экран для эффекта страха/уровня опасности.
    • end_screen и end_text показывают победу/побочный экран.
    • crosshair и overlay для визуальной прицельной метки.
  • Звук и текстовые уведомления монстра
    • monster_sound_text показывается на экране, когда монстр рычит или что-то говорит.
    • ROAR_MESSAGES — набор возможных рычаний.
  1. Логика игрового цикла (обновление кадра)
  • Общий рабочий цикл update():
    • Проверка жив ли игрок. Если игрок не жив (game_state['alive'] = False):
      • Нажатием R можно перезапустить игру (через вызов перезапуска скрипта).
      • Функция возвращает управление, чтобы не продолжать обновление логики.
    • Таймер подсказки (hint_timer): после истечения подсказка пропадает (меняется прозрачность текста).
    • Фонарик: в коде есть задел на обработку F, но конкретная логика переключения не представлена (пустой pass в условии held_keys['f']). Вероятно, реальная обработка в input() не показана здесь.
    • Подбор заметок:
      • Для каждой заметки в notes_entities, если заметка включена (enabled):
        • Расстояние до игрока: distance(player, note).
        • Если расстояние меньше 2.2 единицы: заметка исчезает (enabled = False), счетчик заметок увеличивается.
        • Обновляется UI: notes_ui.text.
        • Показывается сообщение рычания (монстру): обновляется текст и цвет.
        • Монстру увеличивается скорость (monster_speed) и возможность агро-режима (monster_aggro) становится активной после трёх заметок.
        • Если собраны ВСЕ заметки: экран аналогичный выходу активируется, сообщает игроку, что можно выйти, и включаются выход и подпись.
    • Выход:
      • Если exit_entity включён и игрок подходит к выходу на距离 < 3: вызывается game_over(won=True).
    • Монстр:
      • dist_to_player: расстояние от монстра до игрока.
      • dir_to_player: направление к игроку, обнуление по оси Y (dir_to_player.y = 0) для движения по плоскости.
      • seen/chasing: монстр начинает преследовать, если игрок в радиусе видимости (часть упрощённой логики) или если активирован режим агрессии.
      • Если сейчас преследуют (chasing):
        • Если монстр ещё слишком далеко от игрока, он движется в направлении игрока: monster_body.position += dir_to_player * speed * dt.
        • Монстр разворачивается по направлению к игроку через rotation_y, используя угол направления.
      • Если не преследуют (patrolling):
        • Патруль: monstru_state['patrol_timer'] уменьшается; patrol_dir = целевая точка минус текущая позиция монстра.
        • Если монстр близко к патрульной точке или таймер истёк: выбирается новая патрульная цель и задаётся новый таймер.
        • Если патрульная дистанция велика, монстр движется к цели с половиной скорости (speed * 0.5).
      • Горизонтальное положение монстра: monster_body.y = 1.2 (фиксирование высоты, чтобы не "плавать" по нулям).
      • Рычание: roar_timer уменьшается, если истёк, и монстр рычит, когда он в атаке (показ уведомления на экране). Затем устанавливается новый рандомный интервал рычания.
  • Примечания по поведению
    • Условие «видит игрок» здесь упрощено: dist_to_player < 18 — монстр становится агрессивным и начинает преследование.
    • Агро-режим «monster_aggro» активируется после сбора трёх заметок, что делает монстра быстрее и более опасным.
    • Патрульный режим задаётся целочисленно-случайными целями внутри границ FOREST_SIZE.
  1. Как работают ключевые элементы кода (пункты с ответами на ключевые вопросы)
  • Как монстр находит игрока и движется к нему?
    • dir_to_player = (player_pos - monster_pos); обнуляется ось Y (dir_to_player.y = 0) для движения по плоскости.
    • Если dist_to_player в пределах радиуса видимости, или включен агро-режим, монстр считается «chasing».
    • В режиме преследования монстр двигается по направлению к игроку: monster_body.position += dir_to_player.normalized() * speed * dt; и поворачивается в направлении игрока (rotation_y соответствует углу направления).
  • Что происходит, когда все заметки найдены?
    • exit_entity.enabled и exit_label.enabled становятся True.
    • exit_label получает текст '>>> ВЫХОД <<<', и игрок должен прийти к выходу, чтобы победить.
    • При соприкосновении с выходом вызывается game_over(won=True).
  • Как патруль монстра выбирает точку?
    • Когда монстр не в режиме преследования, он идёт к patrol_target.
    • Если расстояние до patrol_target меньше 2 единиц или patrol_timer <= 0, выбирается новая цель случайно в диапазоне [-FOREST_SIZE+5, FOREST_SIZE-5], и устанавливается новый patrol_timer (4–10 секунд).
    • Движение к патрульной точке идёт со скоростью speed * 0.5 (медленнее, чем в преследовании).
  • Как реализована "страх" и визуальные эффекты?
    • fear_overlay: полупрозрачный красный прямоугольник на камере UI, который может менять прозрачность (не виден в этом фрагменте напрямую, но задуман как эффект).
    • monster_sound_text и функция show_monster_sound(msg): показывает сообщение о рычании на экране, цветом красным с высокой непрозрачностью. Функция update_monster_sound(dt) уменьшает прозрачность по времени.
  • Как работают UI элементы?
    • notes_ui показывает текущий счёт заметок, обновляется после каждого взятия заметки.
    • hint_ui — подсказка, которая постепенно исчезает по таймеру hint_timer.
    • flashlight_ui — текстовый indikатор состояния фонаря (индикатор включён/выключен) и обновление в реальном коде (в текущем фрагменте — заглушка).
    • end_screen и end_text — экран победы/поражения с текстом и цветом согласно результату.
  • Как работает перезапуск?
    • При нажатии R, вызывается os.execv с текущим интерпретатором Python и аргументами скрипта, что делает перезапуск программы.
  1. Потенциальные улучшения и замечания
  • Реализм видимости монстра:
    • Сейчас видимость определяется на расстоянии, без учёта препятствий. Можно добавить простой лучевой трассинг (raycast) от монстра к игроку, чтобы проверить, есть ли прямой путь без дерева/объектов.
  • Игровая динамика столкновений:
    • В текущем коде нет явной обработки столкновения монстра с игроком (фрагмент о смерти игрока не показан). Можно добавить логику: при пересечении монстра с игроком — игрок умирает или теряет здоровье.
  • Управление фонарём:
    • В коде есть заготовка на обработку нажатия F, но реальная логика выключения/включения фонаря не реализована здесь. Можно добавить toggling и изменение color/интенсивности фонаря.
  • Эффекты страха:
    • fear_overlay существует, но активация не полностью реализована в видимом фрагменте. Можно добавить постепенное усиление эффекта при приближении монстра или при достаточной агрессии.
  • Оптимизация:
    • Генерация множества деревьев и заметок может быть более эффективной через уровни LOD или кэширование. Сейчас нет вопросов по производительности, но в больших сценах стоит подумать о оптимизации.
  1. Практические задачи для закрепления материала
  • Задача 1: Объясните, зачем dir_to_player.y обнуляют (dir_to_player.y = 0). Ответ: чтобы движение монстра происходило только по горизонтальной плоскости, избежав вертикального подъёма/падения и сохранения устойчивого направления к игроку по земле.
  • Задача 2: Что произойдёт, если NOTES_TOTAL увеличить до 20? Ответ: будет больше заметок, требуют большего времени на сбор; скорость монстра может вырасти по достижению порога агро (если рост монстра зависит от количества заметок).
  • Задача 3: Как монстр выбирает новую точку патруля? Ответ: когда таймер патруля истекает или монстр слишком близко подходит к текущей целевой точке, выбирается новая цель в пределах леса и устанавливается новый patrol_timer (4–10 секунд).
  • Задача 4: Как активируется выход и что он делает в игре? Ответ: выход активируется после сбора всех заметок (enabled = True для exit_entity и exit_label). Игрок должен подойти к выходу на расстояние менее 3 единиц, чтобы выиграть (game_over(won=True)).
  1. Пример вопросов к тесту по коду
  • Какие значения влияют на скорость монстра и как они изменяются во времени?
  • Как изменение переменной NOTES_TOTAL влияет на игровой цикл?
  • Что делает функция update_monster_sound(dt) и когда она вызывается?
  • Какие элементы UI обновляются после сбора заметки?
  • Как можно расширить игру, добавив например оружие игрока или здоровье монстра, и как это повлияет на huidige цикл?

Если нужно, могу привести упрощённую «пошаговую» версию кода с пометками, чтобы легче было переписать или изменить отдельные части (например, добавить видимую прямую видимости монстра к игроку, добавить логику убийства при контакте, включать/выключать фонарь по клавише F и т. п.). Также могу предложить набор тестов/практических заданий на тему, например, «модифицируй патруль монстра так, чтобы он избегал заранее заданных зон» или «сделай так, чтобы заметки располагались в чаще леса и труднее попались на виду».

Задай свой вопрос

Напиши любую задачу или вопрос, а нейросеть её решит

Похожие вопросы 15

Бесплатно Мобильное приложение ГДЗ
Мобильное приложение ГДЗ

Задавай вопросы искуcственному интеллекту текстом или фотографиями в приложении на iPhone или Android

qr-codegoogle-playapp-store

Саша — ассистент в телеграмме

from ursina import * from ursina.prefabs.first_person_controller import FirstPersonController import random import math app = Ursina() # ——— НАСТРОЙКИ ——— NOTES_TOTAL = 10 FOREST_SIZE = 60 TREE_COUNT = 180 # ——— СОСТОЯНИЕ ИГРЫ ——— game_state = { 'notes_collected': 0, 'alive': True, 'won': False, 'flashlight_on': True, 'monster_speed': 2.5, 'monster_aggro': False, } # ——— НЕБО И ТУМАН ——— window.color = color.black window.title = 'Forest Horror' window.borderless = False scene.fog_density = 0.04 scene.fog_color = color.rgba(5, 5, 10, 255) # ——— ЗЕМЛЯ ——— ground = Entity( model='plane', scale=(FOREST_SIZE * 2, 1, FOREST_SIZE * 2), color=color.rgba(15, 25, 10, 255), texture='grass', texture_scale=(FOREST_SIZE, FOREST_SIZE), collider='box', ) # ——— ДЕРЕВЬЯ ——— trees = [] def spawn_trees(): for _ in range(TREE_COUNT): x = random.uniform(-FOREST_SIZE, FOREST_SIZE) z = random.uniform(-FOREST_SIZE, FOREST_SIZE) if abs(x) < 5 and abs(z) < 5: continue h = random.uniform(8, 18) trunk = Entity( model='cylinder', color=color.rgba(40, 25, 15, 255), scale=(0.5, h, 0.5), position=(x, h / 2, z), collider='box', ) crown_y = h + random.uniform(1.5, 3) crown = Entity( model='sphere', color=color.rgba(10, random.randint(35, 60), 10, 255), scale=(random.uniform(3, 5), random.uniform(3, 5), random.uniform(3, 5)), position=(x, crown_y, z), ) trees.append(trunk) trees.append(crown) spawn_trees() # ——— ЗАПИСКИ ——— notes_entities = [] def spawn_notes(): placed = 0 attempts = 0 while placed < NOTES_TOTAL and attempts < 1000: attempts += 1 x = random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5) z = random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5) if abs(x) < 6 and abs(z) < 6: continue note = Entity( model='quad', color=color.rgba(220, 210, 180, 255), scale=(0.6, 0.8, 1), position=(x, 1.2, z), billboard=True, collider='box', ) note.note_id = placed notes_entities.append(note) placed += 1 spawn_notes() # ——— ВЫХОД (активируется после всех записок) ——— exit_entity = Entity( model='cube', color=color.rgba(0, 200, 100, 180), scale=(2, 3, 2), position=(0, 1.5, -FOREST_SIZE + 8), collider='box', enabled=False, ) exit_label = Entity( model='quad', color=color.rgba(0, 255, 100, 200), scale=(3, 1), position=(0, 4, -FOREST_SIZE + 8), billboard=True, enabled=False, ) # ——— ИГРОК ——— player = FirstPersonController() player.position = (0, 1, 0) player.speed = 5 player.mouse_sensitivity = Vec2(60, 60) # ——— ФОНАРИК ——— flashlight = PointLight( parent=camera, position=(0.3, -0.2, 1.5), color=color.rgba(255, 240, 200, 255), ) flashlight.attenuation = (0.1, 0.05, 0.02) ambient = AmbientLight() ambient.color = color.rgba(8, 8, 15, 255) # ——— МОНСТР ——— monster_body = Entity( model='cube', color=color.rgba(80, 0, 0, 255), scale=(1.2, 2.4, 1.2), position=(random.uniform(20, 40), 1.2, random.uniform(20, 40)), collider='box', ) monster_head = Entity( model='sphere', color=color.rgba(60, 0, 0, 255), scale=(1.0, 1.0, 1.0), position=(0, 0, 0), parent=monster_body, ) monster_head.y = 1.6 monster_eye_l = Entity( model='sphere', color=color.rgba(255, 0, 0, 255), scale=(0.18, 0.18, 0.18), parent=monster_head, position=(-0.22, 0.1, 0.45), ) monster_eye_r = Entity( model='sphere', color=color.rgba(255, 0, 0, 255), scale=(0.18, 0.18, 0.18), parent=monster_head, position=(0.22, 0.1, 0.45), ) monster_state = { 'patrol_target': Vec3(random.uniform(-30, 30), 1.2, random.uniform(-30, 30)), 'patrol_timer': 0, 'chase_timer': 0, 'roar_timer': 0, } # ——— UI ——— notes_ui = Text( text='Записки: 0 / 10', position=(-0.85, 0.47), scale=1.4, color=color.rgba(220, 210, 180, 255), background=True, ) hint_ui = Text( text='Найди 10 записок и выберись из леса!', position=(-0.0, -0.35), origin=(0, 0), scale=1.2, color=color.rgba(200, 200, 200, 200), ) flashlight_ui = Text( text='[F] Фонарик: ВКЛ', position=(-0.85, 0.42), scale=1.1, color=color.rgba(255, 240, 150, 200), background=True, ) overlay = Entity( parent=camera.ui, model='quad', scale=(2, 1), color=color.rgba(0, 0, 0, 0), z=-1, ) # ——— ЭФФЕКТ СТРАХА (красный экран) ——— fear_overlay = Entity( parent=camera.ui, model='quad', scale=(2, 1), color=color.rgba(150, 0, 0, 0), z=-0.9, ) # ——— CROSSHAIR ——— crosshair = Text(text='+', origin=(0,0), scale=2, color=color.rgba(200,200,200,180)) # ——— ЭКРАН СМЕРТИ / ПОБЕДЫ ——— end_screen = Entity( parent=camera.ui, model='quad', scale=(2, 1), color=color.rgba(0, 0, 0, 0), z=-0.5, enabled=False, ) end_text = Text( text='', origin=(0, 0), scale=3, color=color.white, enabled=False, ) hint_timer = 3.0 # ——— ЗВУК ШАГОВ МОНСТРА (текстовый эффект) ——— monster_sound_text = Text( text='', position=(0, -0.42), origin=(0, 0), scale=1.3, color=color.rgba(255, 50, 50, 0), ) sound_timer = 0 def show_monster_sound(msg): monster_sound_text.text = msg monster_sound_text.color = color.rgba(255, 50, 50, 230) def update_monster_sound(dt): global sound_timer if monster_sound_text.color.a > 0: a = max(0, monster_sound_text.color.a - dt * 120) monster_sound_text.color = color.rgba(255, 50, 50, int(a)) ROAR_MESSAGES = [ '...хруст веток...', '...низкое рычание...', '...ОНО РЯДОМ...', '...тяжёлые шаги...', '...ты слышишь дыхание...', ] def game_over(won=False): game_state['alive'] = False game_state['won'] = won end_screen.enabled = True end_screen.color = color.rgba(0, 0, 0, 180) end_text.enabled = True if won: end_text.text = 'ТЫ ВЫЖИЛ!\nНажми R чтобы начать заново' end_text.color = color.rgba(0, 255, 100, 255) else: end_text.text = 'ТЫ ПОЙМАН...\nНажми R чтобы начать заново' end_text.color = color.rgba(255, 30, 30, 255) mouse.locked = False def update(): global hint_timer, sound_timer if not game_state['alive']: if held_keys['r']: import os, sys os.execv(sys.executable, ['python'] + sys.argv) return dt = time.dt # Прячем подсказку if hint_timer > 0: hint_timer -= dt if hint_timer <= 0: hint_ui.color = color.rgba(200, 200, 200, 0) # ——— ФОНАРИК ——— if held_keys['f']: pass # обрабатывается в input() # ——— ПОДБОР ЗАПИСОК ——— for note in notes_entities[:]: if note.enabled: dist = distance(player, note) if dist < 2.2: note.enabled = False game_state['notes_collected'] += 1 n = game_state['notes_collected'] notes_ui.text = f'Записки: {n} / 10' show_monster_sound(f'Записка #{n} найдена!') monster_sound_text.color = color.rgba(200, 220, 180, 230) # Монстр становится быстрее game_state['monster_speed'] = 2.5 + n * 0.35 game_state['monster_aggro'] = n >= 3 if n == NOTES_TOTAL: notes_ui.text = 'Все записки найдены! Найди ВЫХОД!' exit_entity.enabled = True exit_label.enabled = True exit_label.text = '>>> ВЫХОД <<<' # ——— ВЫХОД ——— if exit_entity.enabled: if distance(player, exit_entity) < 3: game_over(won=True) return # ——— МОНСТР ——— monster_pos = monster_body.position player_pos = player.position dist_to_player = distance(monster_body, player) # Направление к игроку dir_to_player = (player_pos - monster_pos) dir_to_player.y = 0 if dir_to_player.length() > 0.01: dir_to_player = dir_to_player.normalized() # Агрессия: если видит (близко) или агро-режим seeing_player = dist_to_player < 18 chasing = seeing_player or game_state['monster_aggro'] speed = game_state['monster_speed'] if chasing: if dist_to_player > 1.5: monster_body.position += dir_to_player * speed * dt # Поворот к игроку angle = math.degrees(math.atan2(dir_to_player.x, dir_to_player.z)) monster_body.rotation_y = angle else: # Патруль monster_state['patrol_timer'] -= dt patrol_dir = (monster_state['patrol_target'] - monster_pos) patrol_dir.y = 0 if patrol_dir.length() < 2 or monster_state['patrol_timer'] <= 0: monster_state['patrol_target'] = Vec3( random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5), 1.2, random.uniform(-FOREST_SIZE + 5, FOREST_SIZE - 5), ) monster_state['patrol_timer'] = random.uniform(4, 10) elif patrol_dir.length() > 0.01: monster_body.position += patrol_dir.normalized() * (speed * 0.5) * dt monster_body.y = 1.2 # Рычание monster_state['roar_timer'] -= dt if monster_state['roar_timer'] <= 0: if chasing: show_monster_sound(random.choice(ROAR_MESSAGES[-2:])) monster_state['roar_timer'] = random.uniform(3, 6)
Ниже подробное решение и пошаговое объяснение данного кода игры. Цель здесь — понять, как всё работает, какие сущности создаются, как взаимодействуют объекты и как движется монстр. Это помогает закрепить представления о кооперации объектов, скриптах обновления кадра и простой логике ИИ. Краткое резюме задачи - Игра в лесной хоррор: игрок собирает заметки, после сбора всех заметок открывается выход. - Есть монстр, который патрулирует и гонится за игроком в зависимости от условий. - Мир генерируется случайно: деревья, заметки, выход появляется после последней заметки. - Визуальные эффекты и UI поддерживают сюжет: фон, туман, экран страха, подсказки, счётчик заметок. 1) Что здесь за глобальные элементы кода - Константы настройки окружения - NOTES_TOTAL = 10: всего заметок в мире. - FOREST_SIZE = 60 и TREE_COUNT = 180: размеры леса и количество деревьев. - Состояние игры (game_state) - notes_collected, alive, won, flashlight_on, monster_speed, monster_aggro: основная логика геймплея (сколько заметок собрано, жив ли игрок, достиг ли он победы, скорость монстра, угроза монстра). - Набор сцен и объектов - Небо/туман, земля (ground), деревья (spawn_trees) - Заметки (spawn_notes) - Выход (exit_entity, exit_label) - Игрок (FirstPersonController) - Фонарик (PointLight) и освещение сцены (AmbientLight) - Монстр (monster_body, monster_head, глаза монстра) - Монстр в состоянии патруля/атаки (monster_state) - UI элементы: счётчик заметок, подсказка, подсветка фонаря, страха/overlay, крестик, экран окончания - Звуковой текст «рычания» монстра (monster_sound_text) и таймеры - Основная логика обновления (update) - Обработка сбора заметок - Разблокировка выхода после сборa всех заметок - Логика монстра: поведение (патруль vs агрессия), поворот к игроку, скорость перемещения - Взаимодействие с UI и звуком 2) Как создаются мир и объекты (генерация) - Земля - Entity с моделью 'plane', масштабом, текстурой 'grass', коллайдером 'box'. - Цвет и текстура задают внешний вид поверхности. - Деревья - spawn_trees(): для каждого дерева создаются два объекта: - Ствол: цилиндр, цвет коричневый, масштаб (0.5, h, 0.5), высота h рандомная [8,18], position случайный в пределах FOREST_SIZE, чтобы не пересекаться с игроком. - Крона: сфера над стволом, зеленоватый оттенок, размер головы рандомизирован. - Добавляются в массив trees (хотя по коду они не используются далее, но это визуальная часть сцены). - Заметки - spawn_notes(): размещаются на случайных координатах в пределах леса, но вне области около центра (чтобы не создать моментальный сбор). - Модель 'quad', цвет светло-коричневый/бежевый, размер и положение так, чтобы заметки были видны и устойчивы к движению игрока (billboard=True, чтобы они всегда смотрели на камеру). - Каждая заметка получает note_id и хранится в notes_entities. - Выход - Изначально отключён (enabled=False). После сборa всех заметок он становится активным. - exit_label: визуальная подсказка на экране, помогающая понять, что выход доступен. - Монстр - Базовая форма: monster_body (куб) и monster_head (сфера) — частично визуальные элементы, которые следят за игроком. - Глаза монстра - маленькие сферы, прикреплённые к голове. - monster_state хранит целевые точки патруля, таймеры на патруль, агрессию и рычание. - Монстр двигается по миру и имеет возможность быстро приближаться к игроку, если он в зоне агрессии. - UI и эффекты - UI тексты: счетчик заметок, подсказка, статус фонаря. - fear_overlay - красный экран для эффекта страха/уровня опасности. - end_screen и end_text показывают победу/побочный экран. - crosshair и overlay для визуальной прицельной метки. - Звук и текстовые уведомления монстра - monster_sound_text показывается на экране, когда монстр рычит или что-то говорит. - ROAR_MESSAGES — набор возможных рычаний. 3) Логика игрового цикла (обновление кадра) - Общий рабочий цикл update(): - Проверка жив ли игрок. Если игрок не жив (game_state['alive'] = False): - Нажатием R можно перезапустить игру (через вызов перезапуска скрипта). - Функция возвращает управление, чтобы не продолжать обновление логики. - Таймер подсказки (hint_timer): после истечения подсказка пропадает (меняется прозрачность текста). - Фонарик: в коде есть задел на обработку F, но конкретная логика переключения не представлена (пустой pass в условии held_keys['f']). Вероятно, реальная обработка в input() не показана здесь. - Подбор заметок: - Для каждой заметки в notes_entities, если заметка включена (enabled): - Расстояние до игрока: distance(player, note). - Если расстояние меньше 2.2 единицы: заметка исчезает (enabled = False), счетчик заметок увеличивается. - Обновляется UI: notes_ui.text. - Показывается сообщение рычания (монстру): обновляется текст и цвет. - Монстру увеличивается скорость (monster_speed) и возможность агро-режима (monster_aggro) становится активной после трёх заметок. - Если собраны ВСЕ заметки: экран аналогичный выходу активируется, сообщает игроку, что можно выйти, и включаются выход и подпись. - Выход: - Если exit_entity включён и игрок подходит к выходу на距离 < 3: вызывается game_over(won=True). - Монстр: - dist_to_player: расстояние от монстра до игрока. - dir_to_player: направление к игроку, обнуление по оси Y (dir_to_player.y = 0) для движения по плоскости. - seen/chasing: монстр начинает преследовать, если игрок в радиусе видимости (часть упрощённой логики) или если активирован режим агрессии. - Если сейчас преследуют (chasing): - Если монстр ещё слишком далеко от игрока, он движется в направлении игрока: monster_body.position += dir_to_player * speed * dt. - Монстр разворачивается по направлению к игроку через rotation_y, используя угол направления. - Если не преследуют (patrolling): - Патруль: monstru_state['patrol_timer'] уменьшается; patrol_dir = целевая точка минус текущая позиция монстра. - Если монстр близко к патрульной точке или таймер истёк: выбирается новая патрульная цель и задаётся новый таймер. - Если патрульная дистанция велика, монстр движется к цели с половиной скорости (speed * 0.5). - Горизонтальное положение монстра: monster_body.y = 1.2 (фиксирование высоты, чтобы не "плавать" по нулям). - Рычание: roar_timer уменьшается, если истёк, и монстр рычит, когда он в атаке (показ уведомления на экране). Затем устанавливается новый рандомный интервал рычания. - Примечания по поведению - Условие «видит игрок» здесь упрощено: dist_to_player < 18 — монстр становится агрессивным и начинает преследование. - Агро-режим «monster_aggro» активируется после сбора трёх заметок, что делает монстра быстрее и более опасным. - Патрульный режим задаётся целочисленно-случайными целями внутри границ FOREST_SIZE. 4) Как работают ключевые элементы кода (пункты с ответами на ключевые вопросы) - Как монстр находит игрока и движется к нему? - dir_to_player = (player_pos - monster_pos); обнуляется ось Y (dir_to_player.y = 0) для движения по плоскости. - Если dist_to_player в пределах радиуса видимости, или включен агро-режим, монстр считается «chasing». - В режиме преследования монстр двигается по направлению к игроку: monster_body.position += dir_to_player.normalized() * speed * dt; и поворачивается в направлении игрока (rotation_y соответствует углу направления). - Что происходит, когда все заметки найдены? - exit_entity.enabled и exit_label.enabled становятся True. - exit_label получает текст '>>> ВЫХОД <<<', и игрок должен прийти к выходу, чтобы победить. - При соприкосновении с выходом вызывается game_over(won=True). - Как патруль монстра выбирает точку? - Когда монстр не в режиме преследования, он идёт к patrol_target. - Если расстояние до patrol_target меньше 2 единиц или patrol_timer <= 0, выбирается новая цель случайно в диапазоне [-FOREST_SIZE+5, FOREST_SIZE-5], и устанавливается новый patrol_timer (4–10 секунд). - Движение к патрульной точке идёт со скоростью speed * 0.5 (медленнее, чем в преследовании). - Как реализована "страх" и визуальные эффекты? - fear_overlay: полупрозрачный красный прямоугольник на камере UI, который может менять прозрачность (не виден в этом фрагменте напрямую, но задуман как эффект). - monster_sound_text и функция show_monster_sound(msg): показывает сообщение о рычании на экране, цветом красным с высокой непрозрачностью. Функция update_monster_sound(dt) уменьшает прозрачность по времени. - Как работают UI элементы? - notes_ui показывает текущий счёт заметок, обновляется после каждого взятия заметки. - hint_ui — подсказка, которая постепенно исчезает по таймеру hint_timer. - flashlight_ui — текстовый indikатор состояния фонаря (индикатор включён/выключен) и обновление в реальном коде (в текущем фрагменте — заглушка). - end_screen и end_text — экран победы/поражения с текстом и цветом согласно результату. - Как работает перезапуск? - При нажатии R, вызывается os.execv с текущим интерпретатором Python и аргументами скрипта, что делает перезапуск программы. 5) Потенциальные улучшения и замечания - Реализм видимости монстра: - Сейчас видимость определяется на расстоянии, без учёта препятствий. Можно добавить простой лучевой трассинг (raycast) от монстра к игроку, чтобы проверить, есть ли прямой путь без дерева/объектов. - Игровая динамика столкновений: - В текущем коде нет явной обработки столкновения монстра с игроком (фрагмент о смерти игрока не показан). Можно добавить логику: при пересечении монстра с игроком — игрок умирает или теряет здоровье. - Управление фонарём: - В коде есть заготовка на обработку нажатия F, но реальная логика выключения/включения фонаря не реализована здесь. Можно добавить toggling и изменение color/интенсивности фонаря. - Эффекты страха: - fear_overlay существует, но активация не полностью реализована в видимом фрагменте. Можно добавить постепенное усиление эффекта при приближении монстра или при достаточной агрессии. - Оптимизация: - Генерация множества деревьев и заметок может быть более эффективной через уровни LOD или кэширование. Сейчас нет вопросов по производительности, но в больших сценах стоит подумать о оптимизации. 6) Практические задачи для закрепления материала - Задача 1: Объясните, зачем dir_to_player.y обнуляют (dir_to_player.y = 0). Ответ: чтобы движение монстра происходило только по горизонтальной плоскости, избежав вертикального подъёма/падения и сохранения устойчивого направления к игроку по земле. - Задача 2: Что произойдёт, если NOTES_TOTAL увеличить до 20? Ответ: будет больше заметок, требуют большего времени на сбор; скорость монстра может вырасти по достижению порога агро (если рост монстра зависит от количества заметок). - Задача 3: Как монстр выбирает новую точку патруля? Ответ: когда таймер патруля истекает или монстр слишком близко подходит к текущей целевой точке, выбирается новая цель в пределах леса и устанавливается новый patrol_timer (4–10 секунд). - Задача 4: Как активируется выход и что он делает в игре? Ответ: выход активируется после сбора всех заметок (enabled = True для exit_entity и exit_label). Игрок должен подойти к выходу на расстояние менее 3 единиц, чтобы выиграть (game_over(won=True)). 7) Пример вопросов к тесту по коду - Какие значения влияют на скорость монстра и как они изменяются во времени? - Как изменение переменной NOTES_TOTAL влияет на игровой цикл? - Что делает функция update_monster_sound(dt) и когда она вызывается? - Какие элементы UI обновляются после сбора заметки? - Как можно расширить игру, добавив например оружие игрока или здоровье монстра, и как это повлияет на huidige цикл? Если нужно, могу привести упрощённую «пошаговую» версию кода с пометками, чтобы легче было переписать или изменить отдельные части (например, добавить видимую прямую видимости монстра к игроку, добавить логику убийства при контакте, включать/выключать фонарь по клавише F и т. п.). Также могу предложить набор тестов/практических заданий на тему, например, «модифицируй патруль монстра так, чтобы он избегал заранее заданных зон» или «сделай так, чтобы заметки располагались в чаще леса и труднее попались на виду».