Определение бездействия игрока
В этом разделе вы научитесь определять, переместился ли игрок на некоторое расстояние с момента последнего обновления симуляции. Если игрок переместился, его текущее положение сохраняется и опять проверяется. Если он не переместился, цикл прерывается и этот метод завершается. Этот метод использует GetFortCharacter[]
, GetTransform()
и Translation
, чтобы получить местоположение игрока. Подробности можно узнать на соответствующих страницах справочника API.
На данной странице приведены фрагменты кода на Verse, показывающие, как реализовать механику для этого режима. Выполните следующие действия и скопируйте весь код из этой инструкции на шаге 6.
Выполните следующие действия, чтобы определить, бездействует игрок или нет.
-
Создайте метод расширения для класса агента под названием
AwaitStopMoving()
. Тем самым вы добавляете пользовательский метод в уже определённый класс.(PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void= Logger.Print("Проверка, переместился ли агент на расстояние, которое меньше минимума.")
-
Получите исходное положение игрока.
(PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void= Logger.Print("Проверка, переместился ли агент на расстояние, которое меньше минимума.") # Получаем исходное положение агента от его персонажа в сцене. if (Tracked := PropAgent.GetFortCharacter[]): var StartPosition:vector3 = Tracked.GetTransform().Translation
-
Получите следующее положение игрока при очередном обновлении симуляции.
(PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void= Logger.Print("Проверка, переместился ли агент на расстояние, которое меньше минимума.") # Получаем исходное положение агента от его персонажа в сцене. if (Tracked := PropAgent.GetFortCharacter[]): var StartPosition:vector3 = Tracked.GetTransform().Translation Sleep(0.0) # Получаем местоположение агента в следующем игровом такте. NewPosition := Tracked.GetTransform().Translation
- Убедитесь, что расстояние между начальным и последним положением в находится в допустимых пределах, переданных в функцию через параметр
MinimumDistance
.(PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void= Logger.Print("Проверка, переместился ли агент на расстояние, которое меньше минимума.") # Получаем исходное положение агента от его персонажа в сцене. if (Tracked := PropAgent.GetFortCharacter[]): var StartPosition:vector3 = Tracked.GetTransform().Translation Sleep(0.0) # Получаем местоположение агента в следующем игровом такте. NewPosition := Tracked.GetTransform().Translation # Если расстояние до нового местоположения от начального местоположения меньше величины MinimumDistance, значит, агент не двигался, и мы прерываем цикл. if (Distance(StartPosition, NewPosition) < MinimumDistance): Logger.Print("Агент передвинулся на расстояние меньше минимального.") # В противном случае мы сбрасываем StartPosition, чтобы обеспечить перемещение игрока с нового местоположения. else: set StartPosition = NewPosition
-
Теперь нам нужно зациклить проверку между начальным и последним местоположением; прерываться этот цикл будет в момент, когда расстояние между этими местоположениями превысит
MinimumDistance
.# Цикл повторяется до тех пор, пока агент не переместится на расстояние, которое меньше MinimumDistance. (PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void= Logger.Print("Проверка, переместился ли агент на расстояние, которое меньше минимума.") # Получаем исходное положение агента от его персонажа в сцене. if (Tracked := PropAgent.GetFortCharacter[]): var StartPosition:vector3 = Tracked.GetTransform().Translation loop: Sleep(0.0) # Получаем местоположение агента в следующем игровом такте. NewPosition := Tracked.GetTransform().Translation # Если расстояние до нового местоположения от начального местоположения меньше величины MinimumDistance, значит, агент не двигался, и мы прерываем цикл. if (Distance(StartPosition, NewPosition) < MinimumDistance): Logger.Print("Агент передвинулся на расстояние меньше минимального.") break # В противном случае мы сбрасываем StartPosition, чтобы обеспечить перемещение игрока с нового местоположения. else: set StartPosition = NewPosition
Обратный отсчёт до активации сигнала сердцебиения
Выполните следующие шаги, чтобы можно было выдержать определённое время, равное величине HeartBeat.MoveTime - HeartBeat.WarningTime
, перед тем, как отобразится предупреждение и таймер, ведущий обратный отсчёт, которые будут видны, пока не завершится обратный отсчёт, после чего предупреждение и текст обратного отсчёта исчезнут.
- Создайте функцию CountdownTimer().
# Выполняет задержку до запуска HeartBeatWarningTime. Затем ведёт обратный отсчёт с помощью HeartBeatWarningTime и задаёт текст обратного отсчёта. При отложенном выполнении текст удаляется. CountdownTimer(PropAgent:agent)<suspends>:void = Logger.Print("Запуск обратного отсчёта перед сердцебиением.")
-
Сначала необходимо попытаться получить
heartbeat_warning_ui
для соответствующего игрока из схемы, настроенной в heartbeat.verse. Если это удаётся сделать, нужно запустить задержку между моментом остановки игрока и отображения таймера обратного отсчёта.# Выполняет задержку до запуска HeartBeatWarningTime. Затем ведёт обратный отсчёт с помощью HeartBeatWarningTime и задаёт текст обратного отсчёта. При отложенном выполнении текст удаляется. CountdownTimer(PropAgent:agent)<suspends>:void= Logger.Print("Запуск обратного отсчёта до начала эффекта сердцебиения.") if (UIData := HeartBeat.WarningUI[PropAgent]): Sleep(HeartBeat.MoveTime - HeartBeat.WarningTime) # Бездействие в течение этого времени до появления предупреждения. Logger.Print("Запуск предупреждения о сердцебиении.") else: Logger.Print("Данные UIData не обнаружены.")
-
Теперь создайте переменную, которая будет появляться на экране и с каждой секундой будет уменьшаться на единицу. Назовите её
WarningTimeRemaining
. Задайте для неё значениеWarningTime
из heartbeat.verse. ПосколькуWarningTimeRemaining
— это значение типаint
, аWarningTime
— значение типаfloat
, нужно использовать функциюCeil[]
, чтобы привести его к типуint
.# Выполняет задержку до запуска HeartBeatWarningTime. Затем ведёт обратный отсчёт с помощью HeartBeatWarningTime и задаёт текст обратного отсчёта. При отложенном выполнении текст удаляется. CountdownTimer(PropAgent:agent)<suspends>:void= Logger.Print("Запуск обратного отсчёта до начала эффекта сердцебиения.") if (UIData := HeartBeat.WarningUI[PropAgent]): Sleep(HeartBeat.MoveTime - HeartBeat.WarningTime) # Бездействие в течение этого времени до появления предупреждения. Logger.Print("Запуск предупреждения о сердцебиении.") var WarningTimeRemaining:int = 0 if (set WarningTimeRemaining = Ceil[HeartBeat.WarningTime]) {} else: Logger.Print("Данные UIData не обнаружены.")
-
Перед запуском цикла обратного отсчёта используйте выражение
defer
для сброса обратного отсчёта в интерфейсе игрока в момент, когда функцияCountdownTimer()
завершает выполнение. Она завершит выполнение только тогда, когда таймер доведёт отсчёт до конца и игрок опять начнёт двигаться. Подробности читайте здесь: Отложенное выполнение.# Выполняет задержку до запуска HeartBeatWarningTime. Затем ведёт обратный отсчёт с помощью HeartBeatWarningTime и задаёт текст обратного отсчёта. При отложенном выполнении текст удаляется. CountdownTimer(PropAgent:agent)<suspends>:void= Logger.Print("Запуск обратного отсчёта до начала эффекта сердцебиения.") if (UIData := HeartBeat.WarningUI[PropAgent]): Sleep(HeartBeat.MoveTime - HeartBeat.WarningTime) # Бездействие в течение этого времени до появления предупреждения. Logger.Print("Запуск предупреждения о сердцебиении.") var WarningTimeRemaining:int = 0 if (set WarningTimeRemaining = Ceil[HeartBeat.WarningTime]) {} # Отложенное выполнение возникает при завершении выполнения функции или её отмене, если, например, при конкурентном выполнении первой была выполнена другая функция. # В этом случае текст предупреждения удаляется по завершении обратного отсчёта или когда агент прячущейся команды начинает двигаться до завершения обратного отсчёта. defer: UIData.Text.SetText(HeartBeatWarningClear) # Задаём для текста предупреждения остающееся время, ожидаем секунду и затем уменьшаем оставшееся время. Если обратный отсчёт завершится, прерываем цикл. UIData.Text.SetText(HeartBeatWarningMessage(WarningTimeRemaining)) else: Logger.Print("Данные UIData не обнаружены.")
-
Наконец, создайте цикл, который будет уменьшать значение таймера. Используйте функцию
SetText()
для отображенияHeartBeatWarningMessage
вместе сWarningTimeRemaining
. Затем подождите секунду, используяSleep()
, перед тем, как начать уменьшение остающегося времени. ЕслиWarningTimeRemaining
равно 0 или меньше, обратный отсчёт завершается и можно прервать цикл.# Выполняет задержку до запуска HeartBeatWarningTime. Затем ведёт обратный отсчёт с помощью HeartBeatWarningTime и задаёт текст обратного отсчёта. При отложенном выполнении текст удаляется. CountdownTimer(PropAgent:agent)<suspends>:void= Logger.Print("Запуск обратного отсчёта до начала эффекта сердцебиения.") if (UIData := HeartBeat.WarningUI[PropAgent]): Sleep(HeartBeat.MoveTime - HeartBeat.WarningTime) # Бездействие в течение этого времени до появления предупреждения. Logger.Print("Запуск предупреждения о сердцебиении.") var WarningTimeRemaining:int = 0 if (set WarningTimeRemaining = Ceil[HeartBeat.WarningTime]) {} # Отложенное выполнение возникает при завершении выполнения функции или её отмене, если, например, при конкурентном выполнении первой была выполнена другая функция. # В этом случае текст предупреждения удаляется по завершении обратного отсчёта или когда агент прячущейся команды начинает двигаться до завершения обратного отсчёта. defer: UIData.Text.SetText(HeartBeatWarningClear) # Задаём для текста предупреждения остающееся время, ожидаем секунду и затем уменьшаем оставшееся время. Если обратный отсчёт завершится, прерываем цикл. loop: Logger.Print("Сердцебиение через {WarningTimeRemaining} сек.") UIData.Text.SetText(HeartBeatWarningMessage(WarningTimeRemaining)) Sleep(1.0) set WarningTimeRemaining -= 1 if (WarningTimeRemaining <= 0): break else: Logger.Print("Данные UIData не обнаружены.")
Воспроизведение эффектов на бездействующих игроках
Когда функция AwaitStopMoving()
завершится, наступит момент запуска таймера обратного отсчёта игрока, а потом его эффектов сердцебиения. Но как только он возобновит движение, потребуется отменить либо включённый в данный момент таймер, либо эффект сердцебиения. Для этого потребуется выражение race
, в которое будут входить следующие два выражения:
-
PropAgent.AwaitStartMoving(MinimumMoveDistance)
. -
block
в месте обратного отсчёта до начала воспроизведения эффектов сердцебиения.
AwaitStartMoving()
требуется для того, чтобы при конкурентном выполнении функция завершилась быстрее и остановился таймер обратного отсчёта или эффектов сердцебиения.
Блочное выражение используется для последовательного запуска двух функций в его пределах — CountdownTimer()
и StartHeartbeart()
, — которые не должны выполняться конкурентно. Таймер обратного отсчёта позволяет прячущемуся игроку быть готовым к тому, что эффекты сердцебиения начнутся после завершения отсчёта времени таймером, так что нет смысла запускать одновременно таймер и эффект сердцебиения.
Чтобы воспроизвести эффекты при длительном нахождении игрока без движения, выполните следующее.
- Создайте метод расширения с именем
AwaitStartMoving()
, который реализуется так же, за исключением следующего:
-
он проверяет, двигался ли игрок на расстояние, равное
MinimumDistance
, или дальше с момента последнего обновления симуляции. Вместо величины нижеMinimumDistance
, как вAwaitStopMoving()
; -
не сбрасывает
StartPosition
после каждого цикла. Если бы значениеStartPosition
сбрасывалось в конце каждого цикла, игроку пришлось бы двигаться на всё расстояниеMinimumDistance
или даже дальше за время, отводимое на обновление симуляции, что может оказаться нереальным.# Цикл повторяется до тех пор, пока агент не переместится на расстояние, превышающее MinimumDistance. (PropAgent:agent).AwaitStartMoving(MinimumDistance:float)<suspends>:void= Logger.Print("Проверка перемещения агента на расстояние больше минимального.") # Получаем исходное положение агента от его персонажа в сцене. if (Tracked := PropAgent.GetFortCharacter[]): StartPosition:vector3 = Tracked.GetTransform().Translation loop: Sleep(0.0) # Получаем местоположение агента в следующем игровом такте. NewPosition := Tracked.GetTransform().Translation # Если расстояние до нового местоположения от начального местоположения превышает значение MinimumDistance или равно ему, значит, агент совершил перемещение и мы прерываем цикл. if (Distance(StartPosition, NewPosition) >= MinimumDistance): Logger.Print("Агент передвинулся на расстояние, которое больше минимального или равно ему.") break
-
Создайте функцию
RunPropGameLoop()
, которая будет отвечать за воспроизведение эффекта сердцебиения, если игрок будет долго бездействовать.# Если агент прячущейся команды перестанет двигаться, запускаем конкурентное выполнение, чтобы проконтролировать перемещение агента за пределы расстояния MinimumMoveDistance, завершение отсчёта таймера эффекта сердцебиения или устранение агента. RunPropGameLoop(PropAgent:agent)<suspends>:void = Logger.Print("Запуск игрового цикла агента прячущейся команды.")
-
Дождитесь, пока агент прячущейся команды не переместится на расстояние меньше минимального, и переходите к дальнейшим действиям.
# Если агент прячущейся команды перестанет двигаться, запускаем конкурентное выполнение, чтобы проконтролировать перемещение агента за пределы расстояния MinimumMoveDistance, завершение отсчёта таймера эффекта сердцебиения или устранение агента. RunPropGameLoop(PropAgent:agent)<suspends>:void = Logger.Print("Запуск игрового цикла агента прячущейся команды.") # Выполняем цикл бесконечно с учётом поведения спрятавшегося игрока до тех пор, пока агент прячущейся команды не будет устранён или игрок не покинет сеанс. loop: # Дожидаемся, пока агент прячущейся команды не переместится на расстояние меньше минимального, и переходим к дальнейшим действиям. PropAgent.AwaitStopMoving(MinimumMoveDistance) Sleep(0.0)
-
Добавьте выражение
race
для запуска конкурентного выполнения между завершениемAwaitStartMoving()
(начало движения игрока) и выражениемblock
с запускомCountdownTimer()
и затемStartHeartbeat()
.# Если агент прячущейся команды перестанет двигаться, запускаем конкурентное выполнение, чтобы проконтролировать перемещение агента за пределы расстояния MinimumMoveDistance, завершение отсчёта таймера эффекта сердцебиения или устранение агента. RunPropGameLoop(PropAgent:agent)<suspends>:void = Logger.Print("Запуск игрового цикла агента прячущейся команды.") # Выполняем цикл бесконечно с учётом поведения спрятавшегося игрока до тех пор, пока агент прячущейся команды не будет устранён или игрок не покинет сеанс. loop: # Дожидаемся, пока агент прячущейся команды не переместится на расстояние меньше минимального, и переходим к дальнейшим действиям. PropAgent.AwaitStopMoving(MinimumMoveDistance) # Пока агент прячущейся команды не переместится на расстояние больше минимального, отсчитываем время до сердцебиения, а затем непрерывно воспроизводим эффект сердцебиения. race: PropAgent.AwaitStartMoving(MinimumMoveDistance) block: CountdownTimer(PropAgent) PropAgent.StartHeartbeat() Sleep(0.0) # Как только конкурентное выполнение завершится (агент прячущейся команды начнёт двигаться), вновь запустить цикл.