Serious Macro Scripting. Углубленный уровень
- Введение
- Доступ к спискам доступных действий и событий у сущностей в редакторе скриптов.
- World Info в скриптах
- Сущность игрока
- Нетрикса, говорящие NPC и Сэм
- Добавление ресурсов в скрипт
- Ловим сущность после спавна
- Send event
- Chapter control
- IF
- FOR
- Завершение уровня
Введение
В прошлом гайде были расписаны самые базовые примеры игровой логики. В этом я расскажу о менее очевидных моментах, которые позволят сделать логику на карте близкой к уровню основной игры. Конечно же главным источником примеров должны служить скрипты из оригинальной игры. Однако, с этим гайдом будет гораздо проще разобраться, что там происходит.
Без долгих и нудных объяснений в этот раз не получится - надеюсь, что это поможет лучше и быстрее понять сложные моменты.
Доступ к спискам доступных действий и событий у сущностей в редакторе скриптов
В редакторе скриптов есть система подсказок при написании кода.
Для её активации, нужно нажать Ctrl+Space(или правую кнопку мыши). Подсказки контекстно зависимые - разместив курсор в теле функции (между скобками {...}) получим варианты как начать строку. Выбрав первый вариант Action Object.Function() у нас появится вот такая строчка:
Action ?.?()
Поставив курсор у первого знака вопроса и опять нажав Ctrl+Space(или правую кнопку мыши) появится список из доступных переменных в скрипте, по дефолту там будет Null и This. Заменим знак вопроса на нужную переменную, например, Entity. Теперь назначим ей сущность. Напомню, что для этого нужно выделить сущность в мире, зажать Ctrl+Alt+Shift и перетащить её на переменную Entity.
Теперь, перейдя ко второму знаку вопроса и нажав на Ctrl+Space мы получим список действий, доступные этой сущности.
С Wait() и On() do {} эта система работает не так удобно, редактор теряется из-за всех этих скобок и не может предложить нужные подстановки. Однако, как только у вас есть хоть что-то, что написано после точки у переменной, то разместив курсор на этом тексте через Ctrl+Space мы получим список событий, доступные этой сущности.
World Info в скриптах
Для смены музыки на локациях, запуска таймера и получении информации об игроках нужно в скриптах обращаться к сущности World Info.
Чтобы переменной в скрипте назначить эту сущность необходимо выбрать ее в списке сущностей нажав на клавишу N и выделив там её. После, как и с другими объектами - зажать Ctrl+Alt+Shift и перетащить её на переменную.
Лайфхак - на W можно сохранять в быстрый доступ выделенные объекты на уровне - так что, выделите World Info(убедитесь, что только его), нажмите на W и сохраните выделение.
Сущность игрока
Для вывода на экран сообщений от Нетриксы, фраз игрока или других NPC, а так же для разных воздействий напрямую с игроком нужно обращаться к сущности игрока.
Получить её можно через World Info:
Player=WorldInfo.GetClosestPlayer()
Интересный момент - скрипт не знает, к какому классу относится эта переменная, так как она назначается во время работы игры, а сейчас, в редакторе она пустая. Из-за этого не будут появляться подсказки по событиям и действиям.
Один из способов, как объяснить скрипту, что мы тут имеем в виду - это заранее обозначить класс переменной:
global var CPlayerPuppetEntity Player
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
- объявляем глобальную переменную Player - через 0.1 секунду со старта |
|
|
Macro Main ? |
- объявляем глобальную переменную Player - после активации детектора |
|
Ключевые моменты:
Важно, чтобы игрок успел однозначно загрузиться на карту.
Если нужно сразу со старта уровня что то делать с игроком, то необходимо ставить маленькую задержку Wait (0.1 sec).
В случае, если игрок уже загружен и в игровом процессе понадобится к нему обратиться, то задержка не нужна.
Нетрикса, говорящие NPC и Сэм
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
- .GetAllPlayersInRange()- если аргументы оставить пустыми, - .ShowNetricsaIcon(), .HideNetricsaIcon() - команды, |
|
Для диалогов используются следующие команды:
.SayNetricsa(), .SayPlayer(), SayPuppet() - для Нетриксы, Сэма и NPC соответственно.
Аргументы для .SayNetricsa(), .SayPlayer() одинаковые, сначала нужно указать звуковой файл, потом в кавычках ("") написать титры, далее указываем длительность отображения титров на экране.
Эти команды не знают о длительности файла, так что нам необходимо кроме того, что указывать в аргументах нужную длительность фразы, так и добавлять Wait() с длительностью фразы, чтобы следующая фраза не прервала предыдущую.
Для SayPuppet() добавляется вначале еще один аргумент - указание на того NPC, который говорит, чтобы звук доносился именно от него.
Тут мы сталкиваемся с новым видом переменной, которая использует ресурс, а не какую либо сущность из уровня.
Добавление ресурсов в скрипт
Чтобы добавить ресурс в переменную нужно сделать следующее:
- выделить объект Macro, в котором наш скрипт
- в его свойствах раскрыть вкладку Variables
- добавить новую переменную, дать ей имя
- в Var value в Implementation выбрать Resource
- и наконец, в Resource выбрать нужный ресурс.
Ловим сущность после спавна
к оглавлению
В прошлом гайде упоминалось, что мы в основном работаем со спавнерами, а не с самими сущностями, но это накладывает одно ограничение - спавнер ничего не знает и ничего не может сделать или сообщить о состоянии заспавненой сущности, кроме того, что она появилась, была использована или убита.
Для того, чтобы например поменять маршрут патрулирования со временем, или заставить проиграть какую либо анимацию мы должны иметь доступ к сущности.
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
Заспавнили персонажа Как только он появился определили переменную для удобства запустили проигрывание |
|
В таком варианте скрипта мы можем работать только с единственным заспавненым персонажем. Плюс сам скрипт начинает выглядеть достаточно запутано, а если нам понадобится обратиться не к 1 спавнеру, а к нескольким, то нужен другой подход.
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
- Run/RunAsync - команда запуска функции, применительно к конкретной сущности, - Macro EnemyBehavior CLeggedCharacterEntity - После тела основной функции Main Обратите внимание, что вместо знака вопроса мы указали класс сущности, к которой эта функция относится. |
|
|
Macro Main ? Macro Enemy_01_Behaviour СLeggedCharacterEntity Macro Enemy_02_Behaviour СLeggedCharacterEntity |
||
В таком варианте скрипта мы можем работать со всеми заспавнеными персонажами.
Ключевые моменты:
Переменная This - это по сути ссылка сущность, которой располагает скрипт. По умолчанию он будет ссылаться сам на себя. Однако если мы запустим внутри скрипта новую функцию применительно к какой либо переменной, то внутри этой функии This начнет соответствовать этой переменной.
Send event
Send event Объект.Событие - это команда, которая отправляет именное событие какой либо переменной. Соответственно через ожидание событий мы можем это услышать.
Это работает как внутри скрипта так и на другие скрипты. Пример будет в следующем разделе.
Chapter control
Конечно логику уровня можно сделать полностью в одном скрипте от начала до конца. А можно на каждый спавнер создавать по отдельному скрипту. Можно попробовать сгруппировать все одинаковые события в свои скрипты и тд и тп, и всё это будет работать. Но как сделать “правильнее”?
Что подразумевается под “правильно”? Удобство внесения изменении, а так же логичная структура, чтобы и через пол года открыть проект и не потеряться. Мы можем воспользоваться уже готовой структурой уровней, как это придумали разработчики оригинального Serious Sam 2.
В чем заключается их подход?
Уровень делится на логические главы, с набором связанных событий.
За начало главы отвечает Chapter Info.
Для каждой главы создается свой отдельный скрипт, условием начала которого становится начало главы, и работает он до тех пор, пока глава не закончится.
Что нам дает такой подход?
Во первых, каждый Chapter Info это точка автосохранения для игрока.
Во вторых, в редакторе мы можем начинать уровень с какой угодно выбранной главы, а это значит, что вместе с ней будет запускаться нужный нам набор событий, при этом остальные скрипты не будут стартовать. Это позволяет быстро и изолировано проводить тесты, например как реализована арена или как работает головоломка, не тратя время на прохождение уровня до или на отключение ненужных спавнеров, которые должны стартовать одновременно с другой главой.
Давайте рассмотрим подобный скрипт для одной главы:
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
Можно заметить, что тут две функции Также можно обратить внимание, |
|
|
Macro Main ? |
Это скрипт менеджера глав - ChapterControl. |
|
Может возникнуть вопрос - зачем нам отправлять событие в менеджер глав, а не просто внутри скрипта одной главы запускать следующую?
При кажущейся переусложненности такого подхода это нам дает независимость скриптов друг от друга. В рамках одного скрипта у нас есть информация только про интересующую нас главу.
Соответственно мы можем тасовать очередность глав как мы захотим, так как это определяется только менеджером глав (ну и конечно, как выстроена цепочка самих глав, но это менять очень просто).
Если будет интересно, можете открывать оригинальные уровни, и посмотреть, как на них то реализовано.
Сразу видно что их делали разные люди, и хотя явно было некое общее представление как это стоит оформлять - код у всех отличается. Так что возможно, что вы найдете для себя более удобный способ, как реализовывать подобную схему логики.
IF
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
При активации детектора происходит проверка |
|
FOR
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
5 раз будет проиграна анимация |
|
Завершение уровня
|
Скрипт |
Что тут происходит? |
|
|---|---|---|
|
Macro Main ? |
При активации детектора происходит завершение уровня. |
|

Комментарии