Nollopa Serious Macro Scripting. Начальный уровень.


Serious Macro Scripting. Начальный уровень

Введение

В Serious Engine 2 отказались от системы триггеров из Serious Engine 1. На замену пришел скриптовый язык Macro. А уже в следующих версиях движка разработчики перешли на Lua.

По итогу информации про скриптинг в SE2 в интернете исчезающе мало.
Основное место, где можно почитать как он устроен - это встроенный в Serious Editor 2 Help. Однако, на мой взгляд, информация там подана сложновато.

Целью данного гайда является простое изложение с намеренными упрощениями, а также с большим количеством ходовых примеров скриптов, которые пригодятся маперам.
Это первая часть из двух, где описаны базовые особенности работы со скриптами и объектами.
Подразумевается, что читатель знает, как делать базовые вещи в редакторе - как создать уровень, как создать модель и т.д. и т.п. В целом, из-за схожести всех Serious Engine начиная со второй версии подойдет вот этот замечательный гайд.

Базовые понятия

к оглавлению

Вся игровая логика в скриптах состоит из двух элементов: СОБЫТИЯ(Event) и ДЕЙСТВИЯ(Action).
Соответственно в скриптах мы заставляем сущность(Entity) что-то СДЕЛАТЬ и/или ждем пока произойдет какое либо СОБЫТИЕ.
Если перед ДЕЙСТВИЕМ нет ожидания какого либо СОБЫТИЯ то оно выполняется сразу, как запущена игровая сессия.

В скриптах у нас для этого есть несколько служебных слов:

- Action - указывает что сейчас будет описано ДЕЙСТВИЕ
- ActionAsync - тоже команда для выполнения действия, но она выполняется одновременно с другими командами, что были написаны перед ней.
- Wait() - ждет СОБЫТИЯ, указанное в скобках.
Практически у каждой сущности(Entity) в игре есть свои наборы событий и действий.

Начало работы

к оглавлению

Для начала необходимо добавить сущность Macro на уровень.
Выделив Macro нажимаем E - откроется редактор скриптов.
По умолчанию там уже будет следующий код, который ничего не делает:

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
}

ничего

Macro Main ? - начало главной функции. Запускается автоматически.

Напишем “Hello world!” в консоли:

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Print ("Hello, world!")
}

При старте уровня в консоль будет выведена фраза “Hello, world!”

Print - команда для вывода информации в консоль

В редакторе есть разница между сущностью и спавнером этой сущности. У этих двух объектов разные действия и события.
В данном гайде будут рассматриваться скрипты по отношению к спавнерам.

В примерах, в качестве врагов мы будем создавать всегда сущность Legged Character c такими настройками:
- Puppet params: \Databases\Puppets\Enemies\SciFi_Grunt.ep
- Character behavior: \Databases\Behaviors\Objects\Ameba.cb (для того чтобы враг не мешал нам тем, что будет бегать и стрелять в ответ)
- Spawner: выбираем создать новый.

В качестве “награды” мы будем создавать монетку - сущность Treasure S с такими настройками:
- Spawner: выбираем создать новый.

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Action Enemy.SpawnSimple()
}

- спавн врага

Action - указание на то что дальше с будет выполнено какое либо действие

Enemy - переменная

- .SpawnSimple() - действие для переменной

Можно заметить, что сейчас Enemy красного цвета - это значит, что переменной не назначен объект.
Чтобы назначить, необходимо его выделить, в данном примере это созданный ранее Legged Character, зажать Ctrl+Alt+Shift и перетащить его на переменную Enemy в окно редактора скриптов.
Цвет переменной при этом сменится с красного на зеленый.
Запустив уровень мы увидим, что враг сразу же появился.

Ключевые моменты:

Если нужно что-то сделать с объектом, то это Action ИмяОбъекта.Действие(аргументы). Важно, что даже если в аргументах ничего не должно быть, то скобки всё равно нужны.

Последовательная логика

к оглавлению

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Wait (3 sec)
  Action Enemy.SpawnSimple()
}

- ждем 3 секунды

- спавн врага

Wait (3 sec) - ждём 3 секунды

Macro Main ?
{
  Wait (event(Detector.Activated))
  Action Enemy.SpawnSimple()
}

- активировали детектор

- спавн врага

Wait (event(Detector.Activated)) - ждём, пока игрок активирует Detector area.

Macro Main ?
{
  Wait (event(Detector.Activated))
  Wait (3 sec)
  Action Enemy.SpawnSimple()
}

- активировали детектор

- ждем 3 секунды

-  спавн врага

 

Добавим на уровень сущность Detector area. Размеры можно изменить в свойствах объекта.
Не забудьте также присвоить в скрипте переменной Detector этот новый объект.

Ключевые моменты:

Ели мы ждем некоторого события, то это Wait(Событие).
Ждать мы можем событие от объекта - Wait(event(ИмяОбъекта.Событие)). У событий нет аргументов, так что скобки ставить не нужно.
Либо можем ждать по таймеру - Wait(3 sec).

Приведенные примеры делают вещи последовательно, сверху вниз.
Если мы захотим в таком же виде сделать три детектора, которые должны вызывать разные события:

Скрипт

Результат

Macro Main ?
{
  Wait (event(Detector.Activated))
  Action Enemy.SpawnSimple() 
  Wait (event(Detector2.Activated))
  Action Enemy2.SpawnSimple()
  Wait (event(Detector3.Activated))
  Action Enemy3.SpawnSimple()
}

После срабатывания 1 детектора появится 1 враг, ПОСЛЕ этого можно будет активировать 2 детектор и т.д.

При этом, если активировать 2 и 3 детекторы ДО активации первого, то враги не появятся.

то мы столкнемся с тем, что детектор 2 не заспавнит своего врага, пока не сработает первый детектор.
Следовательно, нам нужно обрабатывать события параллельно.

Параллельная логика

к оглавлению

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Wait (False)
} On (event(Detector.Activated)) do {
  Action Enemy.SpawnSimple() 
} On (event(Detector2.Activated)) do {
  Action Enemy2.SpawnSimple() 
} On (event(Detector3.Activated)) do {
  Action Enemy3.SpawnSimple()
}

3 детектора - активация каждого спавнит отдельного врага.

 - Wait(false) - означает, что скрипт никогда не закончит свою работу и будет ждать дальнейших событий, которые ждет обработчик событий On() do {.

-  On() do { - обработчик событий, ждет событие и выполняет код внутри себя.

Macro Main ?
{
  Wait (False)
} On (event(Detector.Activated)) do {
  Wait (3 sec)
  Action Enemy.SpawnSimple() 
} On (event(Detector2.Activated)) do {
  Wait (7 sec)
  Action Enemy2.SpawnSimple() 
}

Добавим задержку перед спавном противников, для каждого детектора свою

 

Ключевые моменты:

Очень упрощая, можно сказать, что On() do { это тот же Wait() но для параллельных событий.

Wait(event(ИмяОбъекта.Событие)) → On(event(ИмяОбъекта.Событие)) do {

Если вместо Wait(false) будет ожидание какого либо конкретного события, то после того как оно произойдет скрипт закончит свою работу и обработчики событий уже не будут реагировать.

Дополнительные условия

к оглавлению

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Wait (event all(Detector1.Activated, Detector2.Activated))
  Action Enemy.SpawnSimple()
}

- 2 детектора

- враг появится только если будут оба активированы

Мы добавили после event условие all и перечислили все события, что нам нужны через запятую. И теперь Wait() будет выполнен после совершения всех перечисленных событий.

Macro Main ?
{
  Wait (event any(Detector1.Activated, Detector2.Activated))
  Action Enemy.SpawnSimple()
}

- 2 детектора

- враг появится после активации любого

Мы добавили после event условие any и перечислили все события, что нам нужны через запятую. И теперь Wait() сработает от любого из перечисленных событий.

Macro Main ?
{
  Wait (False)
} On (event any(Detector1.Activated, Detector2.Activated)) do {
  Action Enemy1.SpawnSimple() 
} On (event all(Detector3.Activated, Detector4.Activated)) do {
  Action Enemy2.SpawnSimple() 
}

- 4 детектора

- один враг появится после активации любого из первых двух детекторов

- другой враг появится только после активации 3 и 4 детекторов

Эти условия так же работают и для обработчика событий  On() do {

События от персонажей и объектов

к оглавлению

Персонажи и объекты, которые появились на карте через спаунер могут отправлять определенные события.
Общие для обеих групп это .OneSpawned, .GroupSpawned, .AllSpawned - отправляют событие соответственно либо после единичного спауна, либо после спавна группы, либо после того, как заспавнится всё.

Для персонажей - .OneKilled, .GropKilled, .AllKilled
Для объектов - .OnePicked, GropPicked, .AllPicked

Скрипт

Результат

Macro Main ?
{
  Action Item.SpawnSimple()
  Wait (event(Item.OnePicked))
  Action Enemy.SpawnSimple()
}

- объект заспавнился

- объект подобрали

- враг заспавнился

Macro Main ?
{
  Action Enemy.SpawnSimple()
  Wait (event(Enemy.OneKilled))
  Action Item.SpawnSimple()
}

- враг заспавнился

- врага убили

- объект заспавнился

Повторяющиеся события

к оглавлению

Для следующих примеров нужно поменять в настройках спавнеров Total Count с 1 на 6.

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Action Enemy.SpawnSimple()
  Wait (3 times (event(Enemy.OneKilled)))
  Action Item.SpawnSimple()
}

- враг заспавнился

- врага убили 3 раза

- объект заспавнился

 - Wait(n times(Событие)) - означает, что Wait() сработает только после N количества событий внутри times()

Macro Main ?
{
  Action Enemy.SpawnSimple()
  Wait(false)
} on (every(event(Enemy.OneKilled))) do {
  Action Item.SpawnOne()
}

- враг заспавнился

- на каждое убийство врага появляется 1 объект

Мы добавили перед event(...) слово every, и теперь обработчик событий будет каждый раз, когда происходит событие в event()

- .SpawnOne() - спавнит только 1 штуку, и будет и дальше спавнить по 1 штуке, пока не будет достигнуто число в Total number в спавнере.

Macro Main ?
{
  Action Enemy.SpawnSimple()
  Wait(false)
} on (every 3 times(event(Enemy.OneKilled))) do {
  Action Item.SpawnOne()
}

- враг заспавнился

- на каждое 3 убийство врага появляется 1 объект

 

Ключевые моменты:

Важно понимать, что every не имеет смысла в комбинации с Wait(), так как после одного срабатывания сразу пойдет выполняться следующая строчка.

Многоразовые кнопки и детекторы

к оглавлению

Чтобы создать кнопку/рычаг нужно разместить на уровне сущность Static model, выбрать в Model любую модель и поставить галочку напротив Control by macro.
После срабатывания .EnableUsage() подойдя к объекту на экране появится надпись “Press F to use”.

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Wait (False)
} On (every(event(Detector.Activated))) do {
  Action Enemy.SpawnOne()
  Action Detector.Recharge()
}

- при каждой активации детектора будет спавнится 1 враг

.Recharge() - действие для перезарядки детектора

Macro Main ?
{
  Action Switch.EnableUsage()
  Wait (False)
} On (every(event(Switch.Used))) do {
  Action Enemy.SpawnOne()
  Action Switch.EnableUsage()
}

- кнопка становится активной

- при каждом использовании кнопки будет спавнится 1 враг

- .EnableUsage() - действие для Static model, которое позволяет использовать объект как кнопку.

.Used - событие, которое происходит после использования объекта

Групповые переменные

к оглавлению

Разберем ситуацию, где нужно заспавнить сразу 3,4,5 разных противников с разных сторон. Можно конечно для каждого из них заводить свою переменную и отдельно к каждой прописывать Action ….SpawnSimple().

Скрипт

Результат

Macro Main ?
{
  Action Enemy1.SpawnSimple()
  Action Enemy2.SpawnSimple()
  Action Enemy3.SpawnSimple()
}

Спавн трёх разных врагов одновременно.

Но есть вариант удобнее. Если мы выделим сразу несколько сущностей и не снимая выделения назначим их на переменную, то у нас получится групповая переменная, которая будет работать со всеми теми объектами, которые мы в неё назначили.

Скрипт

Результат

Macro Main ?
{
  Action Enemies.SpawnSimple()
}

Спавн трёх разных врагов одновременно.

Однако важно учесть то, что события от групповых переменных надо писать так же, как будто их несколько. Т.е. если нужно, что на убийство ВСЕХ противников из этой группы что то произошло, то нужно писать Wait (event ALL(Enemies.AllKilled)).

Скрипт

Результат

Что тут происходит?

Macro Main ?
{
  Action Enemies.SpawnSimple()
  Wait (event all(Enemies.AllKilled))
  Action Item.SpawnSimple()
}

- Спавн трёх разных врагов одновременно

- при убийстве всех из ОДНОГО спавнера появится объект

В данном случае скрипт работает как будто стоит дополнительное условие any

Macro Main ?
{
  Action Enemies.SpawnSimple()
  Wait (event all(Enemies.AllKilled))
  Action Item.SpawnSimple()
}

- Спавн трёх разных врагов одновременно

- при убийстве всех заспавнишихся появляется объект

 

Стоит отметить, что ни в редакторе скриптов, ни в игровом редакторе никак нельзя узнать, какие именно сущности объединены в группу. Чтобы избегать путаницы в дальнейшем стоит для подобных групп давать осмысленные имена что для самой переменной, что для сущностей в игровом редакторе, например EnemiesWave1.

Двери

к оглавлению

Добавим на уровень сущность Door с такими настройками:
Door model: \Models\Levels\05_Ellenier\Architecture\Gates_01\GateDoor.mdl
Поставить галочки напротив Detector in front и Detector in back.

Скрипт

Результат

Macro Main ?
{
  Action Door.Lock()
}

Со старта дверь закрыта “на ключ”

Macro Main ?
{
  Action Door.Lock()
  Action Item.SpawnSimple()
  Wait (event(Item.OnePicked))
  Action Door.Unlock()
}

- Со старта дверь закрыта “на ключ”

- Спавн ключа

- Подбираем ключ

- теперь при приближении дверь будет открываться

Macro Main ?
{
  Action Door.Lock()
  Action Enemy.SpawnSimple()
  Wait (event(Enemy.AllKilled))
  Action Door.Open()
}

- Со старта дверь закрыта “на ключ”

- Спавн врага

- убиваем врага

- дверь открывается и остается в таком состоянии

Если снять галочки напротив Detector in front и Detector in back, то дверь не будет сама открываться при нашем приближении, даже когда она Unlocked, так что можно реализовать вариант открытия двери по использованию

Скрипт

Результат

Macro Main ?
{
  Action Door.Lock()
  Action Door.EnableUsage()
  Wait (event(Door.Used))
  Action Door.Open()
}

- Со старта дверь закрыта

- делаем её активной

- ждем использования

- дверь открывается

Macro Main ?
{
  Action Door.Lock()
  Action Door.EnableUsage()
  Wait (False)
} On (every(event(Door.Used))) do {
  Action Door.Open()
  Wait (5 sec)
  Action Door.Lock()
  Action Door.EnableUsage()
}

- Со старта дверь закрыта

- делаем её активной

- ждем использования

- дверь открывается

- через 5 секунды закрывается и становится готовой к использованию снова

Усредненный скрипт

к оглавлению

В целом, используя все вышеперечисленные примеры можно уже организовать 80% игровых ситуаций, наподобие тех, что были в 1 и 2 пришествиях.

Скрипт

Результат

Macro Main ?
{
  Action DoorFromArena.Lock()
  Action DoorToArena.Open()
  Action StartHealsGroup.SpawnSimple()
  Action SingleShotgun.SpawnSimple()
  Action ShellsGroup1.SpawnSimple()
  Wait (false)
} On (event any(SingleShotgun.OnePicked, ArenaDetector.Activated)) do {
  Action DoorToArena.Lock()
  Action EnemiesWave1.SpawnSimple()
} On (every (3 times(event(EnemiesWave1.OneKilled)))) do {
  Action HeavyEnemy.SpawnOne()
} On (event all(EnemiesWave1.AllKilled, HeavyEnemy.AllKilled)) do {
  Action DoorKey.SpawnSimple()
  Action Heals.SpawnSimple()
} On (event (DoorKey.OnePicked)) do {
  Action DoorFromArena.Unlock()
}

- Со старта дверь на арену открыта, а с арены закрыта

- спавн здоровья, патронов, дробовика

- при подборе дробовика или активации детектора по центру арены спавн первой волны врагов, дверь на арену закрывается

- на каждые три убийства врагов из первой волны спавнить по 1 тяжелому врагу

- при убийстве всех врагов спавнится здоровье и ключ от двери с арены

- после подбора ключа дверь будет открываться при приближении к ней

 



Комментарии   

[Материал]  |  № 1  |  12.01.2026  |  +1  
Хорошая работа!
Ответ Цитата

Оставьте комментарий

Мини-профиль

Гость

Вы в группе: Гости
Ваш IP: 216.73.216.127

Сейчас обсуждают

duck_Da_quack1701
duck_Da_quack6917
duck_Da_quack6
Bebralot2282474
duck_Da_quack70
Mindbreak18
Sylvain122
Mindbreak11
JustRick18
geodeutschTV5

МЕМЫ

НОВЫЙ
KlirckerReturning 4 1

Файлы

SeriousSasha
112 20 1

Ar2R-devil-PiNKy
11047 2697 86

NeptooN_aka_FLASh2004
6271 1581 48

DeMoN
6427 1363 39

T02my
4167 1016 8

Категории раздела

Croteam [4]
Материалы по студии разработчика игр CroTeam.
Интервью с разработчиками [9]
Интервью с разработчиками, а также их издателями.
Serious Sam 1 [9]
Статьи о Serious Sam 1
Serious Sam 2 [6]
Статьи о Serious Sam 2
Serious Sam HD [4]
Статьи о HD-серии, ремейков классических версий Сэма.
Serious Sam 3: BFE [10]
Статьи о Serious Sam 3: BFE
Уроки по Serious Editor 1 [61]
Уроки по Serious Editor 2 [4]
Самое серьёзное редактирование, на русском!
Уроки по Serious Editor 3 [40]
Уроки по работе с редактором.
Serious Engine [6]
Описание возможностей всех движков компании Croteam
Разное [26]
Всё что не подошло не под одну из вышеупомянутых категорий.
Serious Sam: Revolution [1]
Материалы по SS: Revolution, серьёзном обновлении классических Крутых Сэмов.
Serious Sam 1.05/1.07 SDK [10]
SDK - набор исходников для Serious Sam: The Second Encounter 1.05/1.07.

Видео

Тизер VR-порта Serious Sam Classics TFE/TSE
71 5.0

Статистика



На сайте: 4
Гостей: 2
Пользователей: 2
kostyan56229, polymorn

Кто сегодня был