Мы уже создали основной игровой процесс, теперь важно обеспечить для игрока обратную связь по результатам его действий, например:
- Маркер цели, который показывает игроку, куда идти.
- Прибавление времени к таймеру, когда игрок зарабатывает очки.
- Запуск игры только тогда, когда игрок садится в транспорт.
- Удаление лишних пицц у игрока при подборе нового предмета, чтобы игрок не сбрасывал пиццы на карте.
В этом разделе урока по созданию игры «Гонка на время: Доставка пиццы» вы узнаете, как довести игру до идеала.
Отображение для игрока следующей выбранной зоны для подбора
Выполните следующие действия, чтобы добавить маркер цели, который будет показывать игроку, куда идти. Внесите следующие изменения в файл game_coordinator_device.verse:
- Добавьте редактируемый объект
PickupMarker
типаobjective_marker
со спецификаторомpublic
к классуgame_coordinator_device
.@editable PickupMarker<public> : objective_marker = objective_marker{}
- В начале функции
PickupDeliveryLoop()
активируйте устройство «Указатель на карте», связанное с объектомPickupMarker
, и добавьтеdefer
для деактивации и отключения устройства при выходе из функции.PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 FirstPickupZoneCompletedEvent := event(){} <# Откладываем отключение объекта MapIndicator, чтобы завершение цикла PickupDeliveryLoop всегда приводило к отключению маркера. Отложенный код также выполняется при условии, если цикл PickupDeliveryLoop отменяется. #> defer: if (ValidPlayer := MaybePlayer?): PickupMarker.MapIndicator.DeactivateObjectivePulse(ValidPlayer) PickupMarker.MapIndicator.Disable() PickupMarker.MapIndicator.Enable()
- При выборе следующей зоны подбора переместите маркер подбора в эту зону и вызовите метод
PickupMarker.MapIndicator.ActivateObjectivePulse()
, чтобы указать игроку, куда следует двигаться.race: loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() Sleep(0.0) PickupMarker.MoveMarker(PickupZone.GetTransform(), ?OverTime := 0.0) if (ValidPlayer := MaybePlayer?): PickupMarker.MapIndicator.ActivateObjectivePulse(ValidPlayer) <# Это единственная отложенная операция, необходимая для любой активируемой зоны подбора PickupZone. Она либо деактивирует первую зону PickupZone в конце каждого внешнего цикла, либо деактивирует любую последующую зону PickupZone. Это происходит потому, что выражение оценивается в самом конце, когда переменная PickupZone уже привязана к новой зоне. #> defer: PickupZone.DeactivateZone() PickupZone.ZoneCompletedEvent.Await() Logger.Print("Подобрано", ?Level := log_level.Normal)
Добавление дополнительного времени к таймеру, когда игрок зарабатывает очки
Вы можете стимулировать игрока доставлять предметы чаще, награждая его дополнительным временем за каждый доставленный предмет.
Выполните следующие шаги, чтобы начать добавлять время к таймеру:
- В файле score_manager.verse измените возвращаемый тип в функции
AddPendingScoreToTotalScore()
наint
и добавьтеreturn PendingScore
, чтобы можно было использовать очки, добавленные к общему счёту, для обновления таймера.AddPendingScoreToTotalScore<public>() : int = set TotalGameScore += PendingScore defer: set PendingScore = 0 UpdateUI() return PendingScore
- В файле game_coordinator_device.verse добавьте редактируемую переменную
DeliveryBonusSeconds
типаfloat
со спецификаторомpublic
в классgame_coordinator_device
, которая будет содержать количество секунд, которое нужно добавить к таймеру за каждое очко, полученное за доставку.# Сколько секунд добавлять к таймеру за доставку подобранного предмета. @editable DeliveryBonusSeconds<public> : float = 20.0
- Измените выражение с
block
для доставки так, чтобы оно рассчитывало бонусное время и обновляло оставшееся время на таймере.block: FirstPickupZoneCompletedEvent.Await() if (DeliveryZone := DeliveryZoneSelector.SelectNext[]): DeliveryZone.ActivateZone() # Мы откладываем деактивацию зоны, поэтому прерывание PickupDeliveryLoop также приводит к деактивации любой активной зоны доставки. defer: Logger.Print("Деактивация зоны доставки.", ?Level := log_level.Normal) DeliveryZone.DeactivateZone() DeliveryZone.ZoneCompletedEvent.Await() Logger.Print("Доставлено", ?Level := log_level.Normal) PointsCommitted := ScoreManager.AddPendingScoreToTotalScore() BonusTime : float = DeliveryBonusSeconds * PointsCommitted CountdownTimer.AddRemainingTime(BonusTime) else: Logger.Print("Следующая зона DeliveryZone для выбора не найдена.", ?Level := log_level.Error) return # Ошибка при выходе из PickupDeliveryLoop
- При тестировании отправление в зону доставки после подбора пиццы добавляет дополнительное время к таймеру.

Начало игры, когда игрок садится в транспорт
Выполните следующие действия, чтобы запускать игру, когда игрок садится в транспорт, и обрабатывать ситуации, когда он выходит из транспорта:
- Создайте редактируемую ссылку на устройство «Генератор транспорта» со спецификатором
public
.@editable VehicleSpawner<public> : vehicle_spawner_atk_device = vehicle_spawner_atk_device{}
- Создайте функцию с названием
StartGameOnPlayerEntersVehicle()
со спецификаторамиprivate
иsuspends
. Эта функция будет ожидать момента, когда игрок сядет в транспорт, перед тем как вызвать методStartGame()
и обновить переменнуюMaybePlayer
ссылкой на игрока после его посадки в транспорт. Обновите методOnBegin()
для вызова этой функции (вместоStartGame()
).OnBegin<override>()<suspends> : void = FindPlayer() SetupZones() # Требуется вывод уведомления только в первый раз, когда игрок садится в транспорт, чтобы начать игру. # Функция StartGameOnPlayerEntersVehicle будет ожидать это событие, после чего запустит цикл игрового процесса. StartGameOnPlayerEntersVehicle() StartGameOnPlayerEntersVehicle<private>()<suspends> : void = VehiclePlayer := VehicleSpawner.AgentEntersVehicleEvent.Await() Logger.Print("Игрок сел в транспорт") set MaybePlayer = option{player[VehiclePlayer]} StartGame()
- Задайте обработчик событий для выхода игрока из машины. Создайте функцию
HandlePlayerExitsVehicle()
со спецификаторомprivate
.OnBegin<override>()<suspends> : void = FindPlayer() SetupZones() # После того, как игрок сел в транспорт, он может выйти из него в любой момент; # необходимо отслеживать этот момент каждый раз, чтобы возвращать игрока обратно в транспорт. VehicleSpawner.AgentExitsVehicleEvent.Subscribe(HandlePlayerExitsVehicle) # Требуется вывод уведомления только в первый раз, когда игрок садится в транспорт, чтобы начать игру. # Функция StartGameOnPlayerEntersVehicle будет ожидать это событие, после чего запустит цикл игрового процесса. StartGameOnPlayerEntersVehicle() HandlePlayerExitsVehicle<private>(VehiclePlayer : agent) : void = Logger.Print("Игрок вышел из транспорта. Возвращаю игрока в транспорт") VehicleSpawner.AssignDriver(VehiclePlayer)
Удаление лишних пицц
Выполните следующие действия, чтобы убрать лишние пиццы у игрока, когда он подбирает новый предмет, чтобы игрок не сбрасывал пиццы на карте:
- Добавьте редактируемую ссылку на устройство для удаления предметов, которое отвечает за удаление пиццы из инвентаря игрока.
@editable PizzaRemover<public> : item_remover_device = item_remover_device{}
- В функции
PickupDeliveryLoop()
удалите пиццу у игрока сразу после того, как он подберёт предмет.loop: if (PickupZone : base_zone = PickupZones[PickupLevel].SelectNext[]): PickupZone.ActivateZone() Sleep(0.0) PickupMarker.MoveMarker(PickupZone.GetTransform(), ?OverTime := 0.0) if (ValidPlayer := MaybePlayer?): PickupMarker.MapIndicator.ActivateObjectivePulse(ValidPlayer) <# Это единственная отложенная операция, необходимая для любой активируемой зоны подбора PickupZone. Она либо деактивирует первую зону PickupZone в конце каждого внешнего цикла, либо деактивирует любую последующую зону PickupZone. Это происходит потому, что выражение оценивается в самом конце, когда переменная PickupZone уже привязана к новой зоне. #> defer: PickupZone.DeactivateZone() PickupZone.ZoneCompletedEvent.Await() Logger.Print("Подобрано", ?Level := log_level.Normal) <# Забираем пиццу из инвентаря игрока, чтобы избежать её скапливания и сбрасывания при переполнении. #> if (RemovingPlayer := MaybePlayer?): PizzaRemover.Remove(RemovingPlayer) # После первого подбора можно активировать зону доставки. # Обновляем уровень подбора и ScoreManager. if (PickupLevel < MaxPickupLevel): set PickupLevel += 1 else: Logger.Print("Следующая зона PickupZone для выбора не найдена.", ?Level := log_level.Error) return # Ошибка при выходе из PickupDeliveryLoop