Puzzle Script — это минималистичный игровой движок для создания головоломок для HTML5, имеет открытые исходники. Примеры готовых игр можно посмотреть здесь.

Часть 1. Создаём первую игру на Puzzle Script.


Puzzle Script — это бесплатная онлайн-программа, которая используется для создания игр-головоломок. Наиболее известен она благодаря созданию головоломок с толканием блоков наподобие моей The Nodus. В этой части мы создадим игру, изучив базовые функции Puzzle Script, а в следующей приступим к программированию.


Перейдите на веб-сайт движка. Нажмите Make a Game, чтобы открыть редактор Puzzle Script.

Загрузка примеров


Для начала давайте рассмотрим несколько примеров. В верхней части экрана откройте список «Load Example» и выберите первый пример под названием «Basic». Теперь нажмите на «Run».



Появится экран игры. Щёлкните внутри его окна и нажмите Enter на клавиатуре.


Попробуйте сыграть в игру. Ваша цель — дотолкать оранжевые ящики до чёрных квадратов-целей. Когда на каждой цели будет стоять по ящику, уровень будет пройден. Можно нажимать на клавиатуре Z для отмены хода или R для перезапуска уровня.

Делаем первую игру


Теперь мы создадим несколько уровней. Один я создам вместе с вами, а другие советую придумать самостоятельно. Нажмите на «Level Editor» в верхнем меню.


Если вы не видите экрана редактора уровней, то нажмите «Run» и запустите игру. Попав на уровень, снова нажмите на кнопку «Level Editor». Это позволит редактировать уровень, на котором вы только что находились.

Создаём новый уровень


Вверху находятся игровые объекты. При левом щелчке отрисовывается выбранный объект. Правый щелчок отрисовывает «фоновый» объект. Левый щелчок на краю карты увеличивает её размер, правый щелчок его уменьшает.


Чтобы пройти уровень, нужно поставить по ящику на каждую из целей, поэтому на каждом уровне должны быть минимум:

  • 1 ящик
  • 1 цель
  • 1 игрок

Пока правым щелчком превратим всё в траву. Затем щёлкнем левой клавишей на краю уровня, чтобы сделать его больше, и наконец нарисуем уровень, который похож на показанный ниже.


Добавляем его в список уровней


Закончив создание уровня, мы добавим его в список уровней. В редакторе уровней нажмите на белую букву S рядом со списком игровых объектов, чтобы сохранить созданный уровень.


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


Эти забавные символы обозначают только что созданный нами уровень. Каждый символ представляет отдельный объект. В левой части экрана опуститесь вниз и найдите LEGEND. В легенде представлено объяснение каждого из символов:

. = Background
# = Wall
P = Player
* = Crate
@ = Crate and Target
O = Target

Всё, что находится в левой части экрана — это код игры, разделённый на разные части, такие как OBJECTS или LEGEND. Опуститесь вниз к LEVELS. Здесь мы видим уровни, которые используются в примере.


Чтобы добавить новый уровень, создадим новую пустую строку в нижней части раздела уровней. Затем скопируем символы, сгенерированные для нашего уровня и вставим их туда. Всё, уровень добавлен.


Протестируем его. После создания нового уровня нужно снова нажать кнопку «Run» в верхней части экрана, чтобы перезагрузить игру с новым уровнем. Иногда это не срабатывает, и тогда нужно нажать кнопку «Rebuild», а затем снова нажать «Run».

Сохранение и загрузка игры


Попробуйте создать ещё несколько новых уровней. Когда будете готовы сохранить игру, поднимитесь вверх к началу кода и введите собственное название, имя автора и домашнюю страницу, а затем нажмите на кнопку «Save».



Ограниченное количество сохранений хранится в меню «Load» на используемом вами компьютере. Однако в верхней части экрана существует кнопка «Share». При нажатии на неё генерируется сообщение с двумя веб-ссылками.


Одна из ссылок — исходный код вашего проекта. Вторая — это ссылка на играбельную версию игры, которой можно поделиться с друзьями. Рекомендую периодически создавать новую ссылку на исходный код и сохранять её в какой-нибудь текстовый документ, чтобы у вас оставалась постоянно хранимая версия проекта.

Экспорт игр


Также можно экспортировать игру как файл html5, который вы затем сможете загрузить на игровые порталы, например на itch.io, Kongregate или Newgrounds. Просто нажмите «Export» и загрузите скачанный файл html на игровой портал.

Наш пример проекта можно посмотреть здесь.

Часть 2. Начинаем программировать в Puzzle Script


В этой части мы узнаем, как начать программировать в Puzzle Script.


Код


Откройте пример проекта. Код программы находится в левой части экрана, он разделён на части: Objects, Legend, Sounds и т.д. В разделе Rules задаются правила взаимодействия объектов. Зайдите в него. Здесь должна быть только одна строка кода:

[ > Player | Crate ] -> [ > Player | > Crate ]

Эта строка означает, что если игрок находится рядом с ящиком и перемещается в его сторону, то игра перемещает игрока и толкает ящик. Чтобы объяснить, как это работает, нужно понять, что код Puzzle Script следует такой структуре:

[ Условие ] -> [ Событие ]

Это означает следующее:

[ Если это условие выполняется ] -> тогда [ Делать это ]

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

Примеры условий


Вот пример условия:

[ object1 | object2 ]

Данное событие проверяет, находится ли object1 рядом с object2. Можно проверить, находятся ли два объекта рядом друг с другом, поместив между ними прямую | линию, вводимую нажатием shift + \. Условия всегда заключены в квадратные [ ] скобки.

[ crate | crate ]

Приведённый выше код проверяет, находятся ли рядом друг с другом два ящика.

[ crate | crate | crate ]

Это условие проверяет, находятся ли рядом три ящика.

[ crate target ]

Данное условие проверяет, находится ли ящик поверх цели, потому что прямой | линии между двумя объектами нет. Объекты могут находиться друг на друге, если они расположены в разных слоях коллизий, которые мы рассмотрим в следующих частях туториала.

Сохраняем равенство


Правила должны быть уравновешены. И проверка условия, и следующее за ним событие должны описываться одинаковым образом. Я покажу, что это значит.

[ player | crate ] -> [ player | ]

Эта строка кода уничтожает ящик, если рядом с ним находится игрок. Нельзя записать:

[ player | crate ] -> [ player ]

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

[ Если игрок | рядом с ящиком ] -> тогда [ игрок не делает ничего | ящик удаляется ]

[ player | crate ] -> [ player | ]

То есть даже пустые пространства в коде имеют значение. Однако следующая запись допустима:

[ player target ] -> [ player ]

Так как в условии мы говорим только об одном пространстве сетки, то событие описывает то же пространство сетки.

Как двигать ящики


Вернёмся к исходной строке кода.

[ > Player | Crate ] -> [ > Player | > Crate ]

Другими словами:

[ Если игрок двигается к ящику | и находится рядом с ящиком ] -> тогда [ переместить игрока | толкнуть ящик ]

Стрелка > подчёркивает движение.

Иногда нам требуется писать комментарии, чтобы помнить, что делает код. Puzzle Script игнорирует комментарии — они предназначены только для пользователя. Чтобы записать комментарий, нужно поместить текст в скобки. Напишем над нашим правилом комментарий, описывающий то, что оно делает:

(Игрок толкает ящик)
[ > Player | Crate ] -> [ > Player | > Crate ]

Теперь под кодом толкания ящика напишем следующее:

(Игрок тащит ящик)
[ < Player | Crate ] -> [ < Player | < Crate ]

Перевёрнутая стрелка означает, что если игрок движется от ящика, то он тянет ящик. Нажмите «Run», чтобы протестировать это действие. У вас должна появиться возможность толкать и тащить ящики. В программировании даже при опечатке в одну букву компьютер может не понять код, поэтому избавляйтесь от всех ошибок. При внесении изменений в код снова нажимайте «Run», чтобы загрузить изменения. Если игра ведёт себя не так, как нужно, попробуйте нажать на «Rebuild», чтобы очистить память программы, а затем нажмите «Run».

Иногда бывает нужно деактивировать строку кода. Просто превратите её в комментарий, чтобы сохранить на будущее. Давайте закомментируем код толкания ящиков, чтобы игрок мог только таскать ящики:

(Игрок толкает ящик)
([ > Player | Crate ] -> [ > Player | > Crate ])
(Игрок тащит ящик)
[ < Player | Crate ] -> [ < Player | < Crate ]

Если это сработало, то закомментируйте код таскания ящиков и попробуйте сделать следующее:

[ < Player | Crate ] -> [ < Player | > Crate ]

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

[ > Player | Crate ] -> [ Player | > Crate ]

Ящик двигается, но игрок остаётся на месте. Поэкспериментируйте с ^ и v (буква v), чтобы посмотреть, как будут двигаться объекты.

Ошибки


Давайте намеренно напишем неверное правило и посмотрим, что произойдёт. Введите такую строку:

[ < Player | Crate ] -> [ < Player ]

Попробуйте запустить программу. Вы должны увидеть такое сообщение:

line 81: In a rule, each pattern to match on the left must have a corresponding pattern on the right of equal length (number of cells).

Обычно Puzzle Script очень хорошо описывает ошибку. Однако иногда Puzzle Script ошибается сам. В таких случаях вам нужно самим пройтись по коду и разобраться, где ошибка.

Ещё немного экспериментов


Попробуйте ещё поэкспериментировать и самостоятельно писать правила. Вот несколько примеров.

[ > Player | … | Crate ] -> [ > Player | … | > Crate ]

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

[ > Player | Crate ] -> [ Crate | Player ]

Этот код меняет игрока и ящик местами.

[ > Player | Crate ] -> [ Player | Target ]

В этом коде если игрок рядом с ящиком и движется к нему, то игрок перестанет двигаться, но ящик превратится в цель. Самое лучшее в Puzzle Script — простота создания нового и возможность экспериментов.

Часть 3. Создание объектов


Все графические фрагменты в играх на Puzzle Script обозначают объекты. Для создания игр на Puzzle Script необходимо создавать собственные объекты. В этой части я расскажу, как создавать их и добавлять в свой код.


Общие сведения


Откройте пример проекта. Процесс создания объекта состоит из следующих шагов:

  • Создание его в списке объектов
  • Добавление объекта в легенду
  • Добавление в слой коллизий

Сделав все эти шаги, можно начать использовать объект.

Создание объекта


Несколько объектов уже существует. В каждой игре должен быть фоновый объект. Все объекты создаются из сетки размером 5 x 5 пикселей и имеют хотя бы один цвет. Ниже показан фоновый объект.

Background
LIGHTGREEN GREEN
11111
01111
11101
11111
10111

Числа обозначают пиксели изображения. Каждое число соответствует своему цвету. Первый цвет имеет число 0, второй — 1, и так до 9. Может быть до десяти цветов. В нашем случае каждая 1 окрашивает пиксель в светло-зелёный (Light Green), а 0 — в зелёный (Green). Результат выглядит так:


Объекты всегда создаются следующим образом:

  • Название
  • Цвета
  • Изображение

Название всегда находится в верхней строке. Цвета всегда во второй строке, а изображение занимает следующие 5 строк, по 5 символов на строку, что формирует сетку 5 x 5. Или можно сделать следующее:

Background
LIGHTGREEN

Этот код создаст объект с названием «Background», который будет сеткой 5 x 5 пикселей светло-зелёного цвета. Если не описать сетку изображения, то мы получим блок сплошного цвета, что иногда может быть полезно.

Даём названия объектам


Объекты можно называть как угодно, но название не может начинаться с символа и оно должно быть одним словом без пробелов. Давайте объектам понятные названия, но не переборщите. PlayerStill — хорошее название, PlayerThatIsStandingStill — слишком длинное и многословное.

Цвета


Необходимо объявить цвета, которые вы хотите использовать для объекта, и разделить их пробелом. Puzzle Script имеет заранее заданные цвета:

  • black
  • white
  • grey
  • darkgrey
  • lightgrey
  • gray
  • darkgray
  • lightgray
  • red
  • darkred
  • lightred
  • brown
  • darkbrown
  • lightbrown
  • orange
  • yellow
  • green
  • darkgreen
  • lightgreen
  • blue
  • lightblue
  • darkblue
  • purple
  • pink
  • transparent

Можно также задавать цвета в шестнадцатеричном виде, что даёт нам гораздо больший диапазон цветов. Для выбора шестнадцатеричных цветов можно использовать веб-сайты, например такой. Выберите нужный цвет, а затем перепишите код цвета, указанный над изображением. Шестнадцатеричные коды цветов записываются в Puzzle Script следующим образом:

Название
#51A2BD #ff0000 #ffffff

Коду цвета всегда предшествует символ #.

Добавляем объект в легенду


Создав объект, нужно добавить его в легенду. Легенда выглядит так:

. = Background
# = Wall
P = Player
* = Crate
@ = Crate and Target
O = Target

Каждый символ обозначает объект на уровне. То есть когда мы видим такую сетку символов:

#p.*.##
#.**.##
#..#..#
##....#
##...o#
#######

то на самом деле она описывает наш уровень:


Каждому создаваемому объекту нужно присвоить букву, символ или число, обозначающие этот объект на уровне. Вот так:

P = player

Группируем объекты в легенде


Также мы можем создавать в легенде группы объектов. Например, если у нас есть несколько разноцветных ящиков, то можно сделать так:

O = OrangeCrate
B = BlueCrate
G = GreenCrate

Что позволит нам использовать ящики в редакторе уровне. Но для создания кода можно в Legend сгруппировать их вместе, вот так:

Crates = OrangeCrate or GreenCrate or BlueCrate

И всё вместе это будет выглядеть так:

=======
LEGEND
=======
O = OrangeCrate
B = BlueCrate
G = GreenCrate

Crates = OrangeCrate or GreenCrate or BlueCrate

Зачем это делать? Потому что тогда вместо создания таких правил:

[ > Player | OrangeCrate ] -> [ > Player | > OrangeCrate ]
[ > Player | BlueCrate] -> [ > Player | > BlueCrate ]
[ > Player | GreenCrate] -> [ > Player | > GreenCrate]

можно просто записать:

[ > Player | Crates ] -> [ > Player | > Crates ]

И этот код будет работать для всей группы объектов.

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

Слои коллизий


По умолчанию раздел слоёв коллизий выглядит следующим образом:

Background
Target
Player, Wall, Crate

Каждая строка выделяет объекты в свой собственный слой. Порядок расположения слоёв объектов определяет, какие объекты будут поверх других. Объекты в верхней строке будут находиться на нижнем слое, следующая строка будет в слое над ним, и так далее. Фон всегда должен находиться в верхней строке, чтобы располагаться на нижнем слое. Объекты в одном слое не могут находиться друг поверх другого. То есть такого быть не может:

[ player wall ] -> [ player wall ]

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

[ > Player | Target ] -> [ > Player | > Target ]

Эксперимент


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

Часть 4. Условия победы


Все любят побеждать. Мы, как игроки, хотим побеждать. В этой части мы узнаем, как запрограммировать условия победы в игре.

Условия победы


Загрузите пример проекта и перейдите к разделу кода Win Conditions. Вы должны увидеть следующее:

All Target on Crate

Игра выиграна, если на каждой цели есть ящик. Если у вас есть 3 ящика и 2 цели, то выиграете, поставив на цели всего 2 ящика. Если поменять местами:

All crate on target

то каждый ящик должен будет находиться на цели.

Условий может быть одно или несколько. В случае множественных условий они должны выполняться все. Например, у нас может быть следующее:

All Target on Crate
All Target2 on Crate2

Если на уровне есть target и target2, то на них, соответственно, crate и crate2. Если на уровне нет ни одного из объектов, необходимых для выполнения определённого условия победы, например, нет target2, то это условие выполняется автоматически.

Различные типы условий победы


Существует несколько разных типов условий победы.

No Object

В этом случае победа настаёт тогда, когда на уровне нет ни одного такого объекта.

Some Object

Вы выигрываете, когда на уровне есть хотя бы один объект указанного типа.

Some Object1 on Object2

Нужно, чтобы хотя бы один из объектов Object1 находился на Object2.

No Object1 On Object2

Это условие противоположно All Target on Crate. В данном случае нам нужно, чтобы все указанные объекты находились отдельно друг от друга, а не друг на друге. Также можно комбинировать друг с другом различные условия победы.

Эксперимент


Поэкспериментируем с разными условиями победы. Попробуйте создать игру, в которой выигрыш наступает, если все ящики находятся не на целях. Или сделайте игру, в которой нужно уничтожить все ящики определённого типа, но на вашем пути находятся другие толкаемые ящики.

Мой завершённый пример проекта можно посмотреть здесь.

Часть 5. Команда late


В PuzzleScript есть очень полезная команда под названием «late». Порядок происхождения событий в игре важен, и иногда для получения нужных результатов нужен код, который выполняется позже. В этой части я расскажу об использовании команды late.

Зачем она нам понадобится


Откройте пример проекта, после чего вставьте в игру следующий код и запустите её:

[ player | target ] -> [ player | ]

Можно ожидать, что как только игрок встанет рядом с целью, цель будет уничтожена, однако этого не происходит. Вместо этого цель исчезает в ходе после того, как рядом с ней встал игрок. Поэтому попробуем другой код:

late [ player | target ] -> [ player | ]

Как только вы встанете рядом с целью, она исчезнет. Так получилось потому, что всё, обозначенное как late, происходит после выполнения всего остального кода. Иногда такое бывает нужно.

Порядок событий


Вот как выполняется код в Puzzle Script при каждом перемещении.

  1. Puzzle Script обнаруживает, что игрок хочет переместиться
  2. По возможности правила считываются и выполняются сверху вниз
  3. Игрок перемещается, если это возможно
  4. Применяются Late-правила

Компьютер начинает с верхней строки кода и считывает вниз, строка за строкой. проверяя каждое условие. Поэтому когда вы пытаетесь переместиться, Puzzle Script считывает все правила и проверяет, истинны ли условия, и если это так, то он что-то делает. Например, первая строка может быть такой:

[ player | spikeTrap ] -> [ | spikeTrap ]

Если игрок не стоит рядом с ловушкой из кольев, то код продолжает выполнение. Это значит, что важен порядок написания строк кода. Можно применять команду late в некоторых случаях, которые вы узнаете со временем на практике.

Способы использования Late на практике


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

[ player spikeTrap ] -> [ spikeTrap ]

В приведённом выше случае игрок не будет убит ловушкой с кольями до следующего хода после перемещения в ловушку. Чтобы игрок умер мгновенно, просто добавим команду late,

late [ player spikeTrap ] -> [ spikeTrap ]

Чтобы перезапустить весь уровень при смерти персонажа, можно сделать следующее:

late [ player spikeTrap ] -> restart

И уровень перезапустится, когда игрок попадёт в ловушку с кольями.

Готовый пример проекта можно посмотреть здесь.

Часть 6. Работа со звуковыми эффектами


Мы сделали отличную игру на Puzzle Script, но теперь нужно добавить в неё звуки. Как это сделать? Сейчас я вам расскажу!

Генерация звуков


Откройте пример проекта. Добавим в него звуков. Под экраном игры можно увидеть чёрные квадраты с белыми символами. Они используются для генерации звука. Каждый символ генерирует уникальный тип звука, а крестик удаляет созданные звуки. Попробуйте понажимать на квадраты и послушайте звуки.


Жёлтые числа — это уникальные коды, которые нужно копировать и вставлять в те места кода, где необходимы звуки.

Как использовать звуки


Найдя подходящий звук, вы должны вставить его в раздел Sounds кода игры.


Звуки можно использовать несколькими способами. Наиболее простой — создание нового звукового эффекта (sfx). Они должны быть пронумерованы. Мы создаём новый sfx, назначая ему число от 0 до 10 при вставке числового ID звука. В списке Sounds создадим новый sfx под названием sfx0 и присвоим ему сгенерированный звук:

sfx0 36301705

Для использования звука нужно вставить его в правила после события. Давайте прикрепим только что созданный sfx0 к событию уничтожения ящика (событие уже присутствует в примере проекта):

(The player destroys a crate)
[ > Player | CrateVanish ] -> [ Player | ] sfx0

Также звуки можно объявлять для воспроизведения с определёнными событиями, например:

Crate MOVE 36772507

В этом случае звук воспроизведётся при перемещении Crate. Сгенерируем новый звуковой эффект для перетаскивания объектов CratePull и заставим его воспроизводиться при перемещении CratePull:

CratePull MOVE 12735307

Звуки событий нужно объявлять только в разделе Sounds: их не требуется упоминать в правилах.

Список способов воспроизведения звуков


Ниже представлен список различных звуков событий, которые можно использовать, взятый из документации Puzzle Script.

Object Action 541566 — воспроизводится, когда объект во время хода подвергается действию (action).

Object Create 641667 — воспроизводится при создании определённого объекта.

EndGame 5416789 — воспроизводится при завершении игры.

EndLevel 6417822 — воспроизводится после завершения уровня.

Object CantMove 781673 — воспроизводится, когда объект безуспешно пытался сдвинуться в любом направлении.

Player CantMove Down Left 464674 — воспроизводится, когда объект безуспешно пытался сдвинуться вниз или влево.

CloseMessage 344456 — воспроизводится, когда игрок закрывает окно с сообщением.

Object Destroy 187975 — воспроизводится при уничтожении объекта.

Object Move 264567 — воспроизводится, когда объект успешно перемещается в любом направлении.

Object Move Down Left 765432 — воспроизводится, когда объект успешно сдвинулся вниз или влево.

Object Move Horizontal 345367 — воспроизводится, когда объект успешно переместился по горизонтали. Можно также использовать Vertical.

Restart 7865435 — воспроизводится, когда игрок нажимает кнопку перезапуска R.

SFX0 765743 — может быть чем угодно от SFX0 до SFX10. Это особые звуковые события, которые можно выполнять из правил.

ShowMessage 478483 — воспроизводится при отображении сообщения.

StartGame 234626 — воспроизводится в начале новой игры.

Startlevel 765436 — воспроизводится при начале каждого уровня.

TitleScreen 876543 — воспроизводится после загрузки экрана заставки.

Undo 436234 — воспроизводится, когда игрок нажимает клавишу отмены (Z).

Для move и cantmove можно указывать направления, чтобы при перемещении в разных направлениях воспроизводились разные звуки.

Готовый пример проекта находится здесь.

Часть 7. Воспроизведение музыки


Мы научились создавать игры на Puzzle Script, но разве не здорово будет добавить в них музыку? Это возможно, и сейчас я расскажу, как это делается.

Примечание: похоже, на данный момент эта функция в Puzzle Script сломана, так что переходите к следующей части.

Что нужно делать


Откройте заготовку проекта. Музыка работает в Puzzle Script следующим образом: в игру можно вставить ссылку на одно видео с Youtube PuzzleScript автоматически будет воспроизводить все звуки этого видео. Откройте Youtube и выберите любое видео, или используйте следующее:

youtube.com/watch?v=CKAc3nYEatw

Чтобы воспроизвести музыку из видео, нам нужно получить уникальный ID видео. Выделенная зелёным часть в строке выше и есть уникальный ID.


Под меткой домашней страницы автора в начале проекта добавьте метку youtube, а после неё уникальный ID видео, например, так:

youtube CKAc3nYEatw

Чтобы убедиться в правильности работы, нажмите на «Share» и щёлкните по ссылке игры (не по ссылке исходного кода). Во время тестирования внутри редактора музыку Puzzle Script воспроизводить невозможно. Если вы хотите, чтобы в игре была музыка, то нужно загрузить её как видео на Youtube и вставить в свой проект уникальный ID. В каждом проекте может быть только одно видео Youtube.

Пример проекта


Пример проекта Puzzle Script можно найти здесь.

Часть 8. Как использовать команду Action


Мы уже знаем, как перемещаться, толкать и тащить блоки, но что если нам нужно сделать что-то при нажатии определённой клавиши, например пробела?

Возможности ввода в PuzzleScript довольно ограничены, в основном это клавиши-стрелки, Z для отмены, R для перезапуска, и мы не можем их изменить. Но движок даёт нам дополнительную клавишу для действий — нажатие на пробел или клавишу X.

Работа с командой Action следует описанному выше формату. Мы использовали базовый формат кода PuzzleScript:

[ Условие ] -> [ Событие ]

Если условие истинно, то мы выполняем событие. Команда action используется таким же образом, но у неё есть собственные правила. Она выглядит вот так:

[ Action дополнительные условия ] -> [ Событие ]

Вот пример использования команды Action:

[ Action Player ] -> [ Crate ]

Во-первых, Action всегда является первым словом условия.

Во-вторых, если мы хотим воздействовать на определённый объект в игре, то нужно упомянуть этот объект и в условии, и в событии (если упомянуть другой объект, то программа удалит исходный и заменит его на новый объект, а если объект не указать, то он просто удалится).

Наконец, слово Action нужно использовать только в условии, однако иногда стоит использовать его и в условии, и в событии. Об этом я расскажу ниже.

Проверим показанный выше код в проекте игры. Можно начать с этого примера. Вы увидите, что при нажатии на пробел или X игрок становится ящиком.

Условие сообщает [ Если мы нажимаем клавишу Action и на уровне есть объект Player ] -> тогда [ заменить объект игрока на ящик ]

Теперь попробуем в примере проекта следующий код:

[ Action Player | Crate ] -> [ Player | > Crate ]



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

Заменим код обновлённой версией:

[ Action Player | Crate ] -> [ Action Player | > Crate ]


Теперь мы можем толкать все ящики одновременно. Если вам нужно применить действие к нескольким объектам, то поместите Action и в условие, и в событие.

Часть 9. Проверка множественных условий


Давайте научимся проверять несколько условий, например, наличие бомбы И разрушаемого блока.


Откройте пример проекта. Теперь добавьте в него следующий код:

late [Player Switch][DoorClosed] -> [Player Switch][DoorOpen]

Код соответствует такому формату:

[ Условие 1 ] [ Условие 2 ] -> [ Действие 1 ] [ Действие 2 ]

Если условие 1 истинно и условие 2 истинно, то выполняются действие 1 и действие 2. В нашем случае в условии 1 проверяется, находится ли Player на Switch. Если да, то проверяется условие 2, то есть наличие на уровне закрытой двери? Если условие истинно, то объект DoorClosed превращается в объект DoorOpen, открывая дверь.


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

late [Player | Switch][DoorOpen] -> [Player | Switch][DoorClosed]

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

late [Crate Switch][DoorClosed] -> [Crate Switch][DoorOpen]

Теперь дверь будет оставаться открытой, пока на переключателе стоит ящик.

Готовый пример проекта можно посмотреть здесь.

Часть 10. Создание контрольных точек


Возможно, у вас есть хорошая идея для игры на Puzzle Script, но для неё нужны контрольные точки (чекпоинты), чтобы игрок в случае смерти восстанавливался на них. Как это сделать? Довольно просто, и сейчас я объясню, как.


Создание контрольных точек


Откройте пример проекта. Теперь нам нужно запрограммировать контрольную точку. Для этого достаточно всего одной строки кода:

late [ Player FlagRed ] -> CHECKPOINT

FlagRed — это контрольная точка. Когда игрок находится поверх флага, этот код создаёт контрольную точку (CHECKPOINT). Если не использовать late, то функция контрольной точки не сработает.

Протестируйте игру. Пройдите над контрольной точкой, а затем немного дальше и нажмите R. Вы должны будете начать с контрольной точки.

Несколько контрольных точек


При наличии нескольких контрольных точек игра использует последнюю, активированную игроком.

Чтобы избежать повторной активации уже использованной контрольной точки, нужно сменить её на другой объект. Для этого создадим в разделе OBJECTS кода под красным флагом его белую копию.

FlagWhite
White Orange
.1…
.00..
.000.
.1…
.1…

Теперь перепишем эту строку в легенде:

Flag = FlagRed or FlagWhite

Мы можем создать группу объектов. В таком случае Flag будет или FlagRed, или FlagWhite. Пока хотя бы одному из сгруппированных объектов присвоен символ (мы присвоили FlagRed символ F), нам не нужно будет присваивать символы другим объектам группы, при этом получить доступ к ним вы сможете только в коде, но не в редакторе уровней. Затем можно присвоить группе слои коллизий, что мы сделали. Обращение к группе объектов, например, Flag, обращается ко всей группе. Поэтому:

[ > Player | Flag ] -> [ > Player | ]

Этот код повлияет и на красный, и на белый флаг.

Изменение объекта флага


Вот как поменять FlagRed на FlagWhite:

late [ Player FlagRed ] -> [ Player FlagWhite ]

Если в конце хода игрок находится на красном флаге, то мы превращаем его в белый. Однако нужно сделать так, чтобы графика флага менялась после создания контрольной точки, потому что код читается сверху вниз. Протестируйте программу.

Изменение второго флага


В проекте есть два флага. Давайте сделаем так, чтобы при активации второго флага старый белый флаг превращался в чёрный, чтобы его нельзя было снова использовать. Запишем следующее:

late [ Player FlagRed ][ FlagWhite] -> [ Player FlagRed ][FlagBlack]

Код сообщает: если игрок находится на красном флаге и где-то в игре есть белые флаги, то нужно сделать белые флаги чёрными. Так как код считывается сверху вниз, нам нужно выполнить это в следующем порядке:

late [ Player FlagRed ] -> CHECKPOINT
late [ Player FlagRed ][ FlagWhite] -> [ Player FlagRed ][FlagBlack]
late [ Player FlagRed ] -> [ Player FlagWhite ]

Если вы не понимаете, почему код должен быть в таком порядке, то попробуйте поменять порядок строк и протестируйте программу. Чтобы решать собственные задачи, вам нужно научиться делать шаг назад и обдумывать то, что делает код. Но я дам вам подсказку — когда вы играете, где находятся красные флаги во время последней строки кода в следующем неправильном примере?

[ > Player | Crate ] -> [ > Player | > Crate ]
[ > Crate | Flag ] -> [ Crate | Flag ]
late [ Player FlagRed ] -> CHECKPOINT
late [ Player FlagRed ] -> [ Player FlagWhite ]
late [ Player FlagRed ][ FlagWhite] -> [ Player FlagRed ][FlagBlack]

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

Часть 11. Анимации


В PuzzleScript есть два способа создания анимаций. В одном из них используется реальное время, но в этой части я не буду о нём говорить. Другой используется для быстрых одноразовых анимаций, таких как взрыв или спускающийся по вертикальной лестнице персонаж.

Откройте пример проекта. Мы создадим бомбу и серию кадров взрыва, а затем анимируем их.

Анимация объектов


Для 2D-анимаций необходимо несколько рисунков объекта, переходящего из одного состояния в другое, например, спрайт бегущего Марио. Он содержит 4 кадра анимации.


Для симуляции перехода одного кадра анимации в другой мы создадим в PuzzleScript несколько объектов, которые будут кадрами анимации, а затем используем код для переключения между ними.

Бомба


Создадим такой объект-бомбу.

Bomb
black yellow grey
..1..
..1..
.000.
00020
.000.

Не забудьте добавить его в слои и в легенду.

Чтобы анимировать взрыв, нам нужно создать каждый кадр анимации как отдельный объект, а затем переключаться между ними в коде. Давайте создадим объекты взрыва.

Explosion1
black yellow grey red
..1..
..1..
.000.
00320
.000.

Explosion2
black yellow grey red
..1..
..1..
.333.
03330
.333.

Explosion3
black yellow grey red
..1..
.333.
33333
33333
.333.

Explosion4
black yellow grey red
.333.
33333
33333
33333
.333.

Explosion5
black yellow grey red
.333.
33333
33.33
33333
.333.

Explosion6
black yellow grey red
.333.
3...3
3...3
3...3
.333.

Explosion7
black yellow grey red
.....
.....
.....
.....
.....

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

[Explosion7] -> []
[Explosion6] -> [Explosion7]
[Explosion5] -> [Explosion6]
[Explosion4] -> [Explosion5]
[Explosion3] -> [Explosion4]
[Explosion2] -> [Explosion3]
[Explosion1] -> [Explosion2]
[Bomb] -> [Explosion1]

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

Критически важен порядок переключения между анимациями. Последний кадр должен находиться сверху, а первый — внизу. Не забывайте, что код считывается сверху вниз. Если кадры анимации будут находиться в другом порядке, то мы никогда не увидим изменений. Мы увидим только последний кадр, а в нашем случае бомба просто исчезнет. Она будет изменяться на первый кадр, потом на второй и так далее, в одном кадре, ещё до того, как вы увидите графику. Поместив последний кадр в начало, мы в каждом ходу будем видеть следующее изменение.

Использование команды Again


Чтобы анимировать всё вместе, нам нужна команда again. Again означает, что после считывания всего кода PuzzleScript сделает паузу, а затем считает код снова, выполняя все команды again. Её можно использовать для гравитации, скольжения по льду, а в нашем случае и для анимаций. Всё, что нужно — это переписать код следующим образом:

[Explosion7] -> []
[Explosion6] -> [Explosion7] again
[Explosion5] -> [Explosion6] again
[Explosion4] -> [Explosion5] again
[Explosion3] -> [Explosion4] again
[Explosion2] -> [Explosion3] again
[Explosion1] -> [Explosion2] again
[Bomb] -> [Explosion1] again

Протестируйте игру. Вы увидите, что вся анимация бомбы воспроизводится сразу. Если на ваш взгляд она слишком медленная или быстрая, то скорость можно изменить. В самом начале текста программы, под homepage напишите следующий код:

again_interval 0.1

Это часть того, что в PuzzleScript называется prelude. Это место, в котором до остальной части кода можно задать дополнительные правила, определяющие поведение игры. Теперь анимация должна воспроизводиться быстрее. Попробуйте изменить число после again_interval и проверьте, что поменялось.

Готовый пример проекта можно посмотреть здесь.

Часть 12. Гравитация


Обычно PuzzleScript используется для создания игр с видом сверху (top down), но на самом деле можно симулировать некоторые элементы сайдскроллеров, хоть и с ограниченными правилами PuzzleScript, то есть они всё равно будут пошаговыми. В этой части я расскажу, как реализовать гравитацию.

Пример проекта


Откройте пример проекта. Я создал простой уровень, в нём есть игрок, ящик на уступе и дверь внизу. Мы хотим сделать так, чтобы когда игрок толкает ящик с уступа, тот падал на землю. Также мы хотим, чтобы сойдя с края, игрок тоже падал на землю.

Падение, этап 1


Вот первая часть кода, который мы будем использовать:

down [ Player | no Object ] -> [ | Player ]
down [ Crate | no Object no Player ] -> [ | Crate ]

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

Во-первых, давайте поговорим о ключевом слове down. Добавляя down, мы ограничиваем правило тем, что оно применяется только в направлении «вниз». Именно поэтому ящик сдвигается вниз. Если заменить down на right, то вы увидите, что ящик застрянет в правой стене, как в игре с антигравитацией. Попробуйте так сделать.

Далее сделаем что-нибудь необычное. Вместо того, чтобы проверять, находится ли игрок рядом с определённым объектом (например, ящиком), мы проверяем, находится ли он рядом с обобщённым object. Если посмотреть на легенду в коде примера, то вы увидите, что мы определили object как группу объектов, то есть при каждом использовании слова object мы подразумеваем группу объектов. То есть мы проверяем, есть ли какие-то из этих объектов под игроком. Если нет, то мы приказываем игроку занять это пустое пространство и покинуть предыдущее пространство, из-за слова down следуя в направлении вниз.

Но также можно заметить, что после сталкивания с уступа ящик не падает до следующего хода, и что он мгновенно достигает земли. Чтобы устранить это отложенное падение, можно использовать ключевое слово late:

late down [ Player | no Object ] -> [ | Player ]
late down [ Crate | no Object no Player ] -> [ | Crate ]

Но как сделать так, чтобы он падал постепенно, кадр за кадром?

Падение, этап 2


Теперь мы используем ключевое слово random. Перепишем код следующим образом:

random down [ Player | no Object ] -> [ | Player ]
random down [ Crate | no Object no Player ] -> [ | Crate ]

Запустите код. Он работает очень похоже на предыдущий код, но одним важным различием. Ящик зависает в воздухе, но при каждом перемещении игрока он падает вниз на одну ячейку. Это происходит благодаря слову random. Строго говоря, random предназначено для создания игр со случайными элементами, но нам оно пригодилось здесь. Оно заставляет выполняться соответствующую строку кода по одному разу за ход. PuzzleScript выполняет за один ход каждое правило сколько может раз, и только потом игрок видит графические изменения. Именно поэтому кажется, что ящик падает на землю мгновенно. Но при использовании слова random мы разрешаем ему падать только на одну ячейку за раз.

Падение, этап 3


Теперь мы добавим ключевое слово again:

random down [ Player | no Object ] -> [ | Player ] again
random down [ Crate | no Object no Player ] -> [ | Crate ] again

Запустите игру. Всё почти идеально. Ящик на какое-то время зависает в воздухе, но если вы ещё раз переместитесь, то он постепенно падает на землю. Мы уже знакомы с ключевым словом again, по сути оно означает, что в конце хода PuzzleScript снова считывает код и пытается выполнить все команды again как отдельный ход, после чего делает паузу, затем повторяет их снова столько раз, сколько сможет. Важно здесь то, что он делает паузы между ходами again, что позволяет нам увидеть падение ящика.

Последний этап


И последний штрих. Чтобы ящик падал до самой земли сразу после выталкивания с края, нам нужно добавить ещё одну строку кода над той, которую мы только что написали:

[moving Player] -> [moving Player] again
random down [ Player | no Object ] -> [ | Player ] again
random down [ Crate | no Object no Player ] -> [ | Crate ] again

Ключевое слово moving, использованное в квадратных скобках [] перед player означает, что мы проверяем, движется ли игрок. Это значит, что если игрок движется, то мы приказываем ему продолжать двигаться и запускать команду again. Тогда сразу же сработает всё, что использует ключевое слово again; в нашем случае это анимации падения ящика.

Готовый пример проекта можно посмотреть здесь.

Часть 13. Проверка направления движения


Указание направления


Иногда бывает, что нужно проверить что-то только в одном направлении. Например, вам нужно сделать блоки, которые можно толкать только по горизонтали, или создать утку, которая может ходить только влево.

Как бы то ни было, мы можем указывать, чтобы события происходили только в определённых направлениях.

Откройте PuzzleScript, создайте игру на основе примера Basic и замените его Rules следующим кодом:

[ Left Player ] -> [ Crate ]

Запустите игру и посмотрите, что произойдёт.

Если попытаться пойти влево, то игрок превратится в ящик. Это хороший пример того, как работает указание направлений.

Мы уже привыкли к формату, в котором я объяснял код PuzzleScript:

[ Условие ] -> [ Событие ]

Если событие истинно, то происходит событие.

Но теперь, когда нам нужно проверять направления, он будет следовать новым правилам:

[ Movement Direction Object Affected ] -> [ New State of Object ]

То есть наш предыдущий код:

[ Left Player ] -> [ Crate ]

проверяет, движется ли игрок влево (Left). Если да, то мы заменяем игрока на объект ящика.

Типы направлений


Можно выбирать из следующих типов направлений:

  • Up
  • Down
  • Left
  • Right
  • Horizontal (проверяет, есть ли движение по горизонтали)
  • Vertical (проверяет, есть ли движение по вертикали)


Часть 14. Создание настраиваемых перемещений


По каким-то причинам в игре может понадобиться настраиваемое перемещение. Утки могут ходить только влево, ящики можно толкать только по горизонтали, и так далее. Сделать это очень просто. Давайте ещё раз взглянем на базовый формат кода PuzzleScript:

[ Условие ] -> [ Событие ]

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

[ Condition ] -> [ Movement Direction Object To Move]

Вот как будет выглядеть пример:

[ Player | Crate ] -> [ Player | Left Crate ]

Загрузите PuzzleScript, откройте пример Basic, вставьте этот код и посмотрите, что произойдёт. Подходите к ящику, вокруг которого нет стен.

Вы увидите, что когда рядом с ящиком находится игрок, то ящик толкается влево. Однако так как ключевого слова Late нет, это происходит в ходе после того, как вы сделали первый шаг к нему.

При указании в Event кроме объекта ещё и направления движения, например Left, PuzzleScript будет пытаться переместить объект в указанном направлении. Именно поэтому ящик, а не игрок движется влево — Left находится рядом с Crate.

Помните этот код из предыдущей части?

[ Направление движения Объект] -> [ Новое состояние объекта]

Если указать направление движения в условии рядом с объектом, то оно проверяет, двигается ли этот объектов данном направлении. Это важное отличие. Можно переписать это так:

[ Проверяем это ] -> [ Делаем это ]

Допустимые настраиваемые перемещения


Настраиваемые движения можно создавать с любым из этих слов:

  • Up
  • Down
  • Left
  • Right

Нельзя использовать Horizontal или Vertical, потому что PuzzleScript не поймёт, в каком направлении вы хотите переместить объект, или чтобы сделать это, потребуется много обходных действий. Нужно указывать конкретное направление.

Часть 15. Проверка ячеек рядом с объектами


Иногда бывает необходимо проверить, что находится рядом с объектом. Сделать это довольно просто. Давайте ещё раз посмотрим на формат кода PuzzleScript:

[ Условие ] -> [ Событие ]

то есть

[ Проверяем это ] -> если это истинно [ То делаем это ]

Чтобы проверить по сторонам от объекта, нам нужно добавить один элемент. Формат выглядит так:

Проверяемая сторона [ проверяемый object1 | object2 рядом ] -> [ object1 | object2]

Перед условием мы проверяем, в какой стороне от объекта мы хотим выполнить проверку.

Внутри условия мы предполагаем, что нужно не меньше двух ячеек. Ячейка — это, по сути, любые объекты, находящиеся в одном пространстве и не рядом друг с другом.

[ Это одна ячейка ]

[ Это первая ячейка | это вторая ячейка | это третья ячейка ]

Первая ячейка — это объект, стороны которого мы проверяем. Вторая ячейка — это объект, наличие которого мы проверяем. Попробуйте следующие примеры:

Left [ Player | Crate ] -> [ Player | ]

Показанный выше код удаляет ящики, если они находятся в квадрате слева от игрока.

Left [ Crate | Crate ] -> [ Player | Crate ]

Этот код проверяет, находится ли ящик слева от другого ящика. Если да, то ящик, сторона которого проверяется, станет новым объектом игрока.

Left [ Crate | Crate ] -> [ Crate | Player ]

В приведённом выше коде есть такая же проверка, но объектом игрока становится ящик слева.

Left [ Crate | Crate | Crate ] -> [ Crate | Player | Crate ]

В этом коде тоже используется аналогичный паттерн. Ящик непосредственно слева от проверяемого становится игроком, если 3 ящика находятся рядом друг с другом по горизонтали.

Left [ Crate | Crate | Crate ] -> [ Crate | Crate | Player ]

Если 3 ящика находятся рядом друг с другом по горизонтали, то самый левый становится игроком.

Если вы протестируете каждый из этих примеров, то начнёте понимать закономерность. Первая ячейка — это объект, который мы проверяем, чтобы увидеть, что находится рядом. Следующие ячейки, от ближайшей до дальней, являются объектами, наличие которых мы проверяем.

Ключевые слова


  • Up — проверяет над объектом
  • Down — проверяет под объектом
  • Left — проверяет слева от объекта
  • Right — проверяет справа от объекта
  • Horizontal — проверяет слева и справа от объекта
  • Vertical — проверяет над и под объектом

image

image

image

image

Комментарии (5)


  1. staticmain
    08.11.2018 12:16

    Открыл «галерею». Открыл пример где коридор и циферка, поле не более 20х20.
    image
    «Минималистичный» движок, сразу на старте с голым полем отжирающий 100 метров и 10% i5 камня. А минималистичный он в каком месте? В плане возможностей?


    1. phoenixweiss
      09.11.2018 01:40

      В плане возможностей и низкого порога вхождения.
      Кому нужно рациональное использование ресурсов — пишут движки сами на чистом Си или плюсах, те кому нужна поддержка всего на свете, производительность и крутая графика выбирают UE4, те кто хочет в первую очередь инфраструктуру с тонной всего, но писать на сях не горазд, выбирают Unity.
      А те кто хочет сделать какую-то забавную штуку с целью поразвлечься но не упороться в оптимизацию выбирают такие вот движки, коих довольно много.

      P.S. если что я топлю за Godot, вот уж где удовольствие от процесса разработки


  1. leshabirukov
    09.11.2018 14:44

    Было бы интересно услышать оценку этого инструмента от GlukKazan.


    1. GlukKazan
      09.11.2018 16:01
      +1

      Да, я положил себе в закладки, чтобы почитать внимательнее. Редактор выглядит интересно (у меня такого нет). Язык правил, как по мне, так слишком высокоуровневый. Сокобан сделать можно (под него и задумано), а вот на счёт нормальных шашек или шахмат — не уверен. Даже если не брать в расчёт ботов. Возможно что и можно сделать что-то похожее, надо почитать внимательнее, но всю прозрачность код по дороге растеряет.

      Кстати, это нормально, что puzzlescriptgallery.tumblr.com не открывается?


      1. PatientZero Автор
        10.11.2018 21:32

        Кстати, это нормально, что puzzlescriptgallery.tumblr.com не открывается?

        У меня открывается, странно.