- Вывод текста
- Программирование
- Оформление
- Расширенные возможности
- Нюансы написания игр под разные плееры
-
- x AeroQSP
- Утилиты и средства разработки
Сам принцип написания игр на QSP предполагает, что игра будет состоять из отдельных блоков кода — локаций. Эти локации могут быть связаны между собой переходами с помощью операторов GOTO
и XGOTO
. Таким образом в QSP реализуется перемещение по игре, например, перемещение с места на место, или по главам интерактивной истории.
Однако иногда нам необходимо использовать локации не как локации мест, не как главы или страницы, а для того, чтобы иметь возможность выполнять повторяющиеся фрагменты кода. Например, мы пишем на нескольких разных локациях действие подъёма яблок. И нам нужно, чтобы это действие увеличивало число яблок у нас в рюкзаке, уменьшало на соответствующей локации и добавляло предмет "Яблоки", если мы не поднимали ещё ни одного яблока.
В принципе мы это можем сделать, просто копируя на каждую локацию вот такое действие:
act "Взять яблоко": if яблоки['рюкзак']=0: addobj "Яблоки" end яблоки['рюкзак'] += 1 яблоки[$curloc] -= 1 end
Но посмотрите, сколько строчек кода это действие занимает. Семь. Если мы скопируем такое действие два или три раза, это ещё ничего, но что, если нам придётся копировать действие сто раз? Это семь сотен строчек кода. Это попросту увеличивает объём нашей игры в десятки раз. А ведь у нас может быть не одно такое действие.
Чтобы нам не приходилось писать одинаковый код множество и множество раз, в QSP предусмотрены специальный оператор GOSUB
и специальная функция FUNC
, которые позволяют нам превращать любые локации в собственные процедуры и функции. В данном случае мы можем вынести код действия в отдельную локацию, назвать эту локацию "поднять_яблоко"
, а затем вызывать эту локацию из действия с помощью GOSUB
.
Локация "поднять_яблоко"
:
!# поднять_яблоко if яблоки['рюкзак']=0: addobj "Яблоки" end яблоки['рюкзак']+=1 яблоки[$curloc]-=1
Действие, которое мы пишем на любой другой локации:
act "Взять яблоко": gosub "поднять_яблоко" end
Как видите, всё наше действие сократилось до трёх строчек кода, а сам код подъёма яблока написан нами всего один раз!
(Подробнее о том, как работает оператор GOSUB
вы можете прочитать в разделе ниже).
Ну а если, предположим, нам нужно написать сразу три действия по подъёму яблок? И все три эти действия отличаются только числом яблок, которые мы поднимаем:
act "Взять одно яблоко": if яблоки['рюкзак']=0: addobj "Яблоки" end яблоки['рюкзак']+=1 яблоки[$curloc]-=1 end act "Взять два яблока": if яблоки['рюкзак']=0: addobj "Яблоки" end яблоки['рюкзак']+=2 яблоки[$curloc]-=2 end act "Взять пять яблок": if яблоки['рюкзак']=0: addobj "Яблоки" end яблоки['рюкзак']+=5 яблоки[$curloc]-=5 end
Вроде бы код здесь тоже повторяется, но не совсем. Что же делать?
И для таких ситуаций в QSP уже предусмотрен специальный механизм. Дело в том, что оператор GOSUB
позволяет передавать на локацию, которую мы с его помощью вызываем, различные значения. В данном случае нам нужно передавать на локацию "поднять_яблоко"
, которую мы писали в предыдущем примере, число яблок. В действиях это будет выглядеть вот так:
act "Взять одно яблоко": gosub "поднять_яблоко",1 end act "Взять два яблока": gosub "поднять_яблоко",2 end act "Взять пять яблок": gosub "поднять_яблоко",5 end
Через запятую после названия локации мы можем перечислять различные значения (строковые, числовые, кортежи), и эти значения будут переданы на указанную локацию. Однако где на локации "поднять_яблоко"
нам искать эти переданные значения?
Всё просто. Значения, которые мы передаём на локацию таким образом, автоматически помещаются в массив ARGS
, в ячейки, начиная с нулевой. И получается, что нам надо искать наше значение в ARGS[0]
:
!# поднять_яблоко if яблоки['рюкзак'] = 0: addobj "Яблоки" end яблоки['рюкзак'] += args[0] яблоки[$curloc] -= args[0]
Можно передавать до девятнадцати любых значений на локацию и искать их на этой локации в массиве ARGS
соответственно в ячейках с нулевой по восемнадцатую. Вот ещё пример передачи аргументов:
Локация "Приготовить"
:
!# приготовить *pl "Я должен приготовить <<$args[0]>>, но не просто <<$args[0]>>, а <<$args[0]>> <<$args[1]>>, чтобы съесть <<$args[2]>>."
А вот пример вызова этой локации из действий:
act "Завтрак": gosub "Приготовить","яичницу","с луком","на завтрак" end act "Обед": gosub "Приготовить","гуляш","с овощами","в обед" end act "Ужин": gosub "Приготовить","салат","с креветками","на ужин" end
Иногда нам нужно не только передать данные на локацию, но и получить с неё какие-то данные назад. Для этого случая нужно воспользоваться специальной функцией FUNC
.
Как и GOSUB
, эта функция вызывает локацию, чтобы выполнить на ней какой-то код, однако, в отличие от GOSUB
, FUNC
может вернуть значение из локации.
Чтобы локация, вызванная с помощью FUNC
, вернула какое-то значение, мы должны на этой локации присвоить это значение переменной RESULT
. Например, мы хотим написать локацию, на которую будем передавать число, и чтобы эта локация возвращала нам квадрат переданного числа (то есть число умноженное на само себя). Назовём эту локацию "sqr"
:
!# sqr result = args[0]*args[0]
Как видите, на локации "sqr"
мы умножаем число из ARGS[0]
само на себя, а затем результат присваиваем переменной RESULT
. Именно значение из переменной RESULT
локация "sqr"
вернёт с помощью функции FUNC
на любой другой локации:
*pl func('sqr',2) & ! на экране будет 4 *pl func('sqr',3) & ! на экране будет 9 *pl func('sqr',4) & ! на экране будет 16
Во всех приведённых примерах функцией является FUNC
, а оператором — GOSUB
, однако для удобства допустимо называть функциями именно локации, которые мы вызываем с помощью GOSUB
или FUNC
. Например: «Я написал функцию "поднять_яблоко"».
GOSUB
— выполнение кода указанной локации без непосредственного перехода на неё.
Общая запись:
GOSUB [$локация],[аргумент 0],[аргумент 1], ... ,[аргумент 18]
, где [$локация]
— это название локации, код которой мы хотим выполнить без непосредственного перехода на неё. Значения [аргумент 0]
, [аргумент 1]
и т.д. могут использоваться на этой локации, их значения автоматически помещаются в ячейки массива ARGS[0]
, ARGS[1]
, и т.д. соответственно. После обработки локации предыдущие значения ARGS
восстанавливаются. Использование аргументов не обязательно.
Оператор имеет краткую форму GS
:
GS [$локация],[аргумент 0],[аргумент 1], ... ,[аргумент 18]
Допустимо помещать название локации и аргументы в скобки:
GOSUB([$локация],[аргумент 0],[аргумент 1], ... ,[аргумент 18])
При вызове указанной локации с помощью GOSUB
происходит следующее:
GOSUB
.
На каждой локации автоматически создаётся собственный уникальный массив ARGS
, поэтому значения в этом массиве для каждой локации будут свои собственные. После выполнения кода локации, вызванной по GOSUB
, массив ARGS
этой локации уничтожается.
Обратите внимание! Значения из массива ARGS
с локации, вызванной через GOSUB, не транслируются в блоки ACT
. В блоки ACT
транслируются значения ARGS
из текущей локации, т.е. локации, на которую был осуществлён переход с помощью операторов GOTO
/XGOTO
.
#начало $args[0] = 'локация начало' gosub 'переход', 'локация переход' - #переход *pl $args[0] &! На экран выведется 'локация переход' act 'Перейти': goto $args[0] &! если нажмётё действие, увидите текст 'локация начало' end -
Другие примеры:
! обработка локации "ход" ! На локацию не переадются аргументы ! массив ARGS пуст GS 'ход' !обработка локации с названием из переменной $location !Передаётся один параметр - args[0] равен 1. GS $location,1 !обработка локации "ход" с передачей 3-х параметров. ! $args[0] = $var (значению), args[1] = 2, ! $args[2] = "данные". Обратите внимание на символы '$'. GS 'ход',$var,2,'данные'
FUNC
— выполнение кода указанной локации без непосредственного перехода на неё с возвращением значения.
Общая запись:
FUNC([$локация],[аргумент 0],[аргумент 1], ... ,[аргумент 18]) $FUNC([$локация],[аргумент 0],[аргумент 1], ... ,[аргумент 18]) %FUNC([$локация],[аргумент 0],[аргумент 1], ... ,[аргумент 18])
, где [$локация]
— это название локации, код которой мы хотим выполнить без непосредственного перехода на неё. Аргументы [аргумент 0]
, [аргумент 1]
и т.д. могут использоваться на этой локации, их значения автоматически помещаются в ячейки массива ARGS[0]
, ARGS[1]
, и т.д. соответственно. Название локации и аргументы должны обязательно заключаться в скобки.
Чтобы FUNC
вернула строковый результат, на указанной локации нужно присвоить этот результат переменной $RESULT
.
Для возвращения числового результата, он должен быть присвоен переменной RESULT
.
Если нужно вернуть несколько значений, используется кортеж %RESULT
.
Следует помнить, что %RESULT
, $RESULT
и RESULT
— это одна и та же переменная, но разных типов, поэтому если вы определили на локации и %RESULT
, $RESULT
и RESULT
, функция вернёт то значение, которое было записано в эту переменную последним.
Если переменной RESULT
не было присвоено никакое значение, функция ничего не вернёт. И тут есть два варианта поведения плеера.
FUNC
стоит в каком либо выражении, вместо функции будет подставлено значение по умолчанию (пустая строка или ноль в зависимости от типа самого выражения).FUNC
стоит сразу после неявного оператора, такой оператор будет проигнорирован, и на экран ничего выводиться не будет. Пример:
!# локация_Н N = 23 * 13 ! ни одно значение не присваивается переменной result
'строка 1' func('локация_Н') &! ничего не выведется на экран 'строка 2' 56 + func('локация_Н') &! функция стоит в выражении. Её значение = 0. На экране будет 56
Не обязательно, но рекомендуется, записывать ключевое слово FUNC
с символом $
, если функция должна вернуть текстовое значение, и с символом %
, если функция должна вернуть кортеж. Это улучшит читаемость кода:
$func('срез_строки','Мы вышли из дома, когда во всех окнах погасли огни.',3,7) func('возвести_в_степень',3,3) $name, age, %coords = %func('get_pers', 'mjolnir')
При вызове указанной локации с помощью FUNC
происходит следующее:
На каждой локации автоматически создаются свои собственные уникальные массивы ARGS
и RESULT
, поэтому значения в этих массивах для каждой локации будут свои собственные. После выполнения кода локации, вызванной по FUNC
, массивы ARGS
и RESULT
этой локации уничтожаются.
Обратите внимание! Значения из массивов ARGS
и RESULT
с локации, вызванной через FUNC
, не транслируются в блоки ACT
. В блоки ACT
транслируются значения ARGS
и RESULT
из текущей локации, т.е. локации, на которую был осуществлён переход с помощью операторов GOTO
/XGOTO
.
Примеры:
!Обработка локации "поднять_яблоко" как функции. !Массив ARGS пуст. !Результат передается через RESULT !и записывается в переменную яблоки яблоки = FUNC('поднять_яблоко') !обработка локации с названием в $name как функции. ARGS[0] равен 1. PL FUNC($name, 1) * 78 !обработка локации с названием в $name как функции. !$ARGS[0] содержит строку "строка", ARGS[1] равен 2. MSG "text" + FUNC($name, "строка", 2)
Чтобы прервать выполнение текущего кода и выйти из вызванной с помощью GOSUB
или FUNC
локации, используйте оператор EXIT
.
EXIT
- завершение выполнения текущего кода (преждевременный выход из подпрограммы/обработчика какого-либо события).
Пример:
if args[0] = 0: exit
Вы можете не прописывать всякий раз функцию FUNC
явно. Можно использовать более простую запись. Вот как это выглядит в общем виде:
@[$локация]([аргумент 0],[аргумент 1], ... ,[аргумент 18])
Здесь [$локация]
— это название локации, код которой мы хотим выполнить без непосредственного перехода на неё. Аргументы [аргумент 0]
, [аргумент 1]
и т.д. могут использоваться на этой локации, их значения автоматически помещаются в ячейки массива ARGS[0]
, ARGS[1]
, и т.д. соответственно.
То есть: пишем символ @
, затем без пробелов название локации, и сразу за названием локации в скобках через запятую перечисляем аргументы.
При такой записи необходимо соблюдать несколько правил:
Примеры:
*pl @sqr(2) & ! на экране будет 4 *pl @sqr(3) & ! на экране будет 9 *pl @sqr(4) & ! на экране будет 16 ! функция не принимает аргументов *pl @PI() &! можно указать пустые скобки *pl @PI &! можно не указывать пустые скобки степень = @возвести_в_степень(3,3) ! пример вложения функций и участие их в выражениях point = @queue.addPoint($next[i],new_cost+@hevr.est($next[i],$args[0]))
Если подобный неявный вызов сочетается с использованием неявного оператора, а на локации при этом НЕ присваивается значение переменной RESULT
, можно использовать неявный вызов функции вместо оператора GOSUB
:
Локация "поднять_яблоко"
:
!# поднять_яблоко if яблоки['рюкзак']=0: addobj "Яблоки" end яблоки['рюкзак']+=1 яблоки[$curloc]-=1
Действие, которое мы пишем на любой другой локации:
act "Взять яблоко": @поднять_яблоко & ! в данном случае поведение функции будет идентично поведению GOSUB end
На любой локации вы можете назначать локальные переменные, которые будут существовать только в момент выполнения кода этих локаций, а после — автоматически уничтожаться. Значения в этих локальных переменных не пересекаются со значениями одноимённых переменных в других локациях.
См. раздел "Локальные переменные" в разделе Переменные.
Таким образом вы можете разбивать свой код на множество более мелких блоков, дабы избегать повторения одинаковых фрагментов. Это позволяет не только лучше структурировать код, но и улучшает его читаемость и позволяет разрабатывать его модульно, т.е. отдельными независимыми фрагментами. Это, в свою очередь, ускоряет отладку и написание больших и сложных механик для ваших игр.
Так же см. большую статью по операторам, функциям и аргументам.