Перейти к содержимому


Информация о статье

  • Добавлено:
  • Обновлено:
  • Просмотры: 3770
  • |

 


* * * * *
1 Рейтинг

Операторы и функции

Написано в Мар 16 2013 21:01
*Это руководство не претендует на полноту или абсолютную достоверность.

Первая часть

Скрипты и Skyrim Constraction Set. Часть 2: Операторы и функции.

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

5. Мы писали, мы писали, наши пальчики устали…
Как вы знаете, файлы скриптов языка Papyrus имеют расширение .psc. Их можно отредактировать в любом текстовом редакторе. Чтобы сделать процесс создания скриптов более простым и оградить вас от появления глупых ошибок, я расскажу о некоторых вещах, призванных привнести удобства в наш нелегкий труд. Редактирование или создание больших по объему скриптов требует определенной доли усидчивости и внимания. Разбираться в скрипте в том же блокноте подчас неудобно. Когда требуется выловить мелкую ошибку, стена текста, состоящая из функций и условий, большинство из которых писал вовсе не ты, работе не способствует. Чтобы облегчить работу начинающего скриптера Bethesda сделала специальный пакет для текстового редактора Sublime Text. Вы можете скачать этот простой и полезный редактор с официального сайта www.sublimetext.com, а затем установить дополнением к нему ряд сниппетов, которые используют функции автозаполнения часто используемых форм. Это позволяет не зацикливаться на синтаксических ошибках, нередко ставящих в тупик своей непредсказуемой глупостью. Окно Sublime Text выглядит следующим образом.

Изображение


Ключевые слова подсвечиваются нужным цветом, что значительно упрощает восприятие текста. Поэтому его использование много удобней, нежели использование стандартного редактора.
Подробнее об этом редакторе вы можете узнать на официальной Wiki по Creation Kit.
Наш пример состоит из семи строк. В этом скрипте всего две переменные, одно событие, и одна вызываемая функция. Представьте, что переменных стало в десять раз больше. Вы сможете в точности запомнить, какая переменная к чему относится, для выполнения какой функции была предназначена? Что, если у вас несколько десятков функций? В состоянии ли вы запомнить в точности триста или четыреста строк скрипта? Я думаю, через неделю, вы уже и не вспомните, для чего вам нужна была функция, созданная под названием AFDS_01_G(). Вы также должны понимать, что, возможно, ваш скрипт будет полезен людям. Но если вы хоть чуть-чуть разбираетесь в его структуре, то другому человеку последовательность ваших действий в скрипте может быть абсолютно непонятна. Чтобы избежать недоразумений, в тексте скрипта любой желающий может разместить комментарии. Комментарии никак не влияют на работу скрипта и не отображаются вне его. Для размещения комментариев используйте «;» или «{» «}», т. е. точку с запятой или фигурные скобки. Например:
В окне PapyrusScriptManager редактора Creation kit вы можете просмотреть скрипты, написанные непосредственно разработчиками. Практически все они прокомментированы тем или иным образом. Если вы знаете английский язык, то благодаря предусмотрительности скриптеров компании Bethesda вы можете разобрать скрипт «по косточкам» и использовать его для написания своего собственного.
Event OnLoad()
;это мои комментарии после точки с запятой
Endevent
{и это тоже комментарии, но уже внутри фигурных скобок}
6.Операторы и условия
6.1 Для присваивания переменным определенных значений и совершения над ними всем известных арифметических операций, в скриптовом языке Papyrus используются операторы: «=» , «+», «-», «*», «/», «%».
Например:

MyCount01 = Myfloat01*23.5+15/Myfloat02

здесь MyCount01 – переменная типа float, которой мы присваиваем значение (Myfloat01*23.5+15/Myfloat02). Myfloat01и Myfloat02– переменные, над которыми мы производим математические операции. Следует понимать, что в большинстве случаев мы не можем присвоить переменной одного типа значение переменной другого типа.
Например:

Int MyCount01
float MyFloat01
MyFloat01= 45/84*2.34
MyCount01 = MyFloat01

Данный скрипт, несмотря на то, что он кажется правильным, выдаст ошибку несоответствия: type mismatch while assigning to a int (cast missing or types unrelated). Мы попытались переменной (целочисленной) типа integer присвоить значение (1,2534) типа float.
Если мы перепишем скрипт так:

float MyCount01
float MyFloat01
MyFloat01= 45/84*2.34
MyCount01 = MyFloat01

Ошибки не будет.
Чтобы избежать подобных ошибок, можно также использовать оператор приведения к типу: «as».

Int MyCount01
float MyFloat01
MyFloat01= 45/84*2.34
MyCount01 = (MyFloat01 int)

Здесь мы сообщаем, что хотели бы видеть переменную MyFloat01 как переменную типа integer.
Операцию сложения можно также производить над строковыми переменными:

String MyString
MyString = “Привет,” +”Мир”+”!”
В переменную MyString запишется: “Привет, Мир!”

6.2 Особое место занимают переменные булевого типа (Boolean), благодаря которым мы можем производить логические операции. Они могут принимать только два значения: true (правда, 1) и false (ложь, 0).
Например:
Bool myboolean
Bool truth
Truth=false
Myboolean=true


Мы можем присваивать этим переменным значения 1 или 0.

Truth=0
Myboolean=1

Результат будет тот же самый.
Эти переменные необходимы для проверки условий совершения действий. Для них предусмотрены операции:
  • ! - отрицание
  • == - эквивалентность
  • != - неравно
  • > - больше
  • < - меньше
  • >= - больше или равно
  • <= - меньше или равно
  • || - логическое ИЛИ
  • && - логическое И
Например:
b = !a b правда, если а – ложь b = a == c b правда, если а эквивалентно с b = a != c b правда, если а не равно с b = a > c b правда, если а больше с b = a&&c b правда, если а И с правда b=a||c b правда, если а правда ИЛИ с правда

Для работы с подобными переменными используется оператор условия if-then-else (если-то-иначе).
Рассмотрим принцип действия этого оператора на нашем первом скрипте.

Scriptname MyFirstScript extends ObjectReference
MiscObject property MyItem auto
Event OnActivate(ObjectReference akActionRef)
akActionRef.additem(MyItem, 1)
endevent


Допустим, мы не знаем точно, кто будет использовать активатор, но хотим, чтобы в результате предмет оказался в инвентаре только у игрока. Нам нужно проверить, кто воспользовался активатором.
Если им воспользовался игрок – действие в блоке OnActivate можно совершить. Если же им воспользовался кто-то другой – никаких указаний скрипту мы посылать не будем. Таким образом, между ключевыми словами Event и endevent мы должны поместить блок:

If ; начало блока условия
Endif ;окончание блока условия

благодаря которому осуществляется проверка условия. Грубо говоря, этот блок можно назвать функцией, которая возвращает значения правда или ложь. У нас есть ссылка (akActionRef ) на объект, и мы должны проверить, соответствует ли эта ссылка игроку (player).
Для получения ссылки на игрока воспользуемся функцией getplayer(), которая содержится в скриптах объекта Game. Мы можем ее вызвать для объекта ObjectReference: game.getplayer().
Как это будет выглядить в скрипте:
Scriptname MyFirstScript extends ObjectReference
MiscObject property MyItem auto
Event OnActivate(ObjectReference akActionRef)

{Начало блока условия}
If akActionRef == game.getplayer()
{Если переменная akActionRef равна переменной game.getplayer(), то…}
akActionRef .additem(MyItem, 1)
{…совершаем действие – добавляем предмет в инвентарь}
Endif
{Конец блока условия}
Endevent

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

Например:

If проверка условия №1
Выполняем действие 1
If проверка условия №2
Выполняем действие 2
If проверка условия №3
Выполняем действие 3
Endif
Endif
Endif

Если мы все же хотим выполнить иное действие в случае, если объект был активирован не игроком, можно добавить elseif (иначе, если…)
If akActionRef == game.getplayer() ;Если активировал игрок, то…
akActionRef .additem(MyItem, 1) ;добавляем предмет
elseif
akActionRef ; иначе
(
akActionRef as actor).kill() ;убиваем объект
Endif
Здесь использована функция kill(), мгновенно убивающая актера, вызвавшего её. Но в данном случае akActionRef имеет тип ObjectReference и мы должны привести его к типу Actor, используя “as”.
Еще одним видом проверки является цикл While-do. Он вставляется также в виде блока. Пока выполняется условие, следующее за ключевым словом While, мы циклически совершаем действие, указанное внутри блока. Обычно условием окончания является достижение переменной-счетчиком какого-либо значения.
Отличие от условия if-then-else:

While пока счетчик
Совершаем действие 1
Совершаем действие над счетчиком
Endwhile Возвращаемся к началу цикла

Например:

While i<=10 ;пока i меньше или равняется 10, выполняем действие
akActionRef .additem(MyItem, 1)
i+=1 ;прибавляем к счетчику 1
Endwhile
;возвращаемся к началу

По окончании действия цикла у актера в инвентаре будет 11 новых предметов.

7. Функции некоторых объектов.
Разработчиками игры созданы очень полезные объекты типа Math, Debug, Game и Utility, которые не являются «расширением» объекта Form. Чтобы использовать функции, доступные в них, можно вызывать их, как мы сделали это для game.getplayer(), либо подключив (или «импортировав») эти объекты как «модули», объявив в теле скрипта с помощью слова Import.
Например:
Import Utility
Import Game

В этом случае нам не придется писать каждый раз при обращении к функции game.GetPlayer(), слово game. Просто пишем - GetPlayer().
Далее привожу некоторые функции этих объектов.
7.1 Функции объекта Math – математические функции:

float Function abs(float afValue)
Вычисляет абсолютное значение переменной.

float Function acos(float afValue)
Вычисляет арккосинус.

float Function asin(float afValue)
Вычисляет арксинус.

float Function atan(float afValue)
Вычисляет арктангенс.

int Function Ceiling(float afValue)
Округляет в большую сторону.

float Function cos(float afValue)
Вычисляет косинус (в градусах)

float Function DegreesToRadians(float afDegrees)
Конвертирует аргумент в радианы.

int Function Floor(float afValue)
Округляет в меньшую сторону.

float Function pow(float x, float y)
Возводит x в степень у.

float Function RadiansToDegrees(float afRadians)
Переводит аргумент из радиан в десятичную систему.

float Function sin(float afValue)
Вычисляет синус.

float Function sqrt(float afValue)
Вычисляет квадратный корень.

float Function tan(float afValue)
Вычисляет тангенс (в градусах)

Как вы видите, перед функциями стоит параметр int или float. Это означает, что функции возвращают определенную переменную, после того как выполнили над исходными переменными необходимые операции. Для их использования у нас должна быть дополнительная переменная.
Например:
MyFloat = Pow(MyNum01, MyNum02)
В результате MyFloat получит число из переменной MyNum01, возведенное в степень MyNum02.

7.2 Основные функции объекта Game - контролирующие игровой процесс:

Function DisablePlayerControls(bool abMovement, bool abFighting, bool abCamSwitch, bool abLooking, bool abSneaking, bool abMenu, bool abActivate, bool abJournalTabs, int aiDisablePOVType)
Отключает управление игры. В качестве дополнительных параметров указываются невозможность двигаться, сражаться, использовать меню и т.п.


Function EnablePlayerControls(bool abMovement, bool abFighting, bool abCamSwitch, bool abLooking, bool abSneaking, bool abMenu, bool abActivate, bool abJournalTabs, int aiDisablePOVType)
Обратная предыдущей.

Function EnableFastTravel(bool abEnable)
Отключает или включает быстрые перемещения в игре.

Function FastTravel(ObjectReference akDestination)
Принудительный фаст-тревел. Аргумент – место назначения.

Actor Function FindClosestActor(float afX, float afY, float afZ, float afRadius)
Находит ближайшего актера, относительно точки, в определенном радиусе.

Actor Function FindClosestActorFromRef(ObjectReference arCenter, float afRadius)
Находит ближайшего актера относительно другого актера.

Form Function GetForm(int aiFormID)
Возвращает форму по ее ID.

float Function GetRealHoursPassed()
Возвращает кол-во часов реального времени, прошедших с момента начала игры.

Function QuitToMainMenu()
Принудительно выходит в главное меню.

Function PlayBink(string asFileName, bool abInterruptible, bool abMuteAudio, bool abMuteMusic, bool abLetterbox)
Проигрывает bink видео.

Function IncrementSkill(string asSkillName)
Увеличивает скилл (только для игрока).

7.3 Функции Utility:

float Function GetCurrentGameTime()
Возвращает прошедшее игровое время в днях.

Float Function RandomFloat(float afMin, float afMax)
Генерирует случайное значение float от минимального числа до максимального

int Function RandomInt(int aiMin, int aiMax)
Генерирует случайное значение int.

Function WaitGameTime(float afHours)
Останавливает выполнение скрипта на определенное игровое время в часах.

7.4 Функции Debug:

Function CenterOnCell(string asCellName)
Телепортирует игрока в определенную локацию.

Function MessageBox(string asMessageBoxText)
Показывает сообщение типа (текст + ок)

Function QuitGame()
Выходит из игры.

Function SendAnimationEvent(ObjectReference arRef, string asEventName)
Посылает анимационное событие определенному объекту.

8. Создание собственной функции. Расширение скрипта.

8.1 Как уже говорилось, возможность создания и использования собственных функций один из наиболее мощных инструментов скриптового языка Papyrus. Давайте воспользуемся этим и немного улучшим наш скрипт.
Наш активатор раз за разом выдает игроку предметы (у меня это аметисты, у вас он может давать нечто иное, в зависимости от настроенной переменной MiscItem). Мы заставим его раздавать не только аметисты, но и другие драгоценные камни. Очевидный способ увеличить его ассортимент – добавить несколько новых переменных MiscObject, каждой переменной назначить свой параметр и выдать все игроку. Но что, если новых переменных будет несколько сотен штук? Объявлять 200 одинаковых переменных с уникальными идентификационными именами – абсурд. Хорошо было бы иметь одну большую переменную и засунуть туда все переменные поменьше…
Для операций с огромным количеством однотипных переменных в языке Papyrus предусмотрены массивы (arrays). Это «склады» или по-другому матрицы, содержащие однотипные элементы, каждому из которых присвоен собственный уникальный идентификационный номер. Мы можем обрабатывать эти переменные, пользуясь их номерами.
Условно массив выглядит как:
Имя-массива = [0],[1],[2],[3],[4],[5],[…],[N]
Номера элементов целочисленные (integer). Максимальное количество элементов 128. Нумерация начинается с нуля.
Многомерные массивы, к сожалению, в языке Papyrus не предусмотрены (будем дожидаться Skyrim Script Extender (SKSE) – расширителя скриптового языка).
Для объявления массива достаточно указать его имя, тип и количество элементов, содержащихся в нем.
Например:

Int[] MyIntegerArray

Здесь int[] объявление массива типа integer, MyIntegerArray- его имя.
В данном случае массив содержит 0 элементов – он пуст.

Float[] MyFloatArray = new float[25]

Здесь мы объявили массив MyFloatArray[] типа Float, содержащий 25 элементов. По-умолчанию все элементы этого массива заполняются нулями. Ключевое слово “new” служит для создания массива.
Мы можем, например, объявить массив:

MiscObject[] MyObjects = new MiscObject[10]

содержащий в себе 10 элементов типа MIscObject, заполненный По-умолчанию значениями None (null).
Мы можем обращаться к каждому элементу массива, просто вызывая его по идентификационному номеру.
Например:

ObjectReference[] Weapon01 = new objectReference[5]
Weapon01[0] = objectRef01
Weapon01[1] = objectRef02
Weapon01[4] = Weapon01[0]


Здесь мы объявили массив Weapon01[], определили, что в нем 5 элементов и затем присвоили первому элементу значение objectRef01, второму элементу значение objectRef02, а пятому элементу присвоили значения первого.
Для обработки массивов часто используются циклы. Например:

Int i
MiscObject[] MyObjects =
new MiscObject[10]
While i<10
MyObjects[i] = RandomObject
i+=1
Endwhile


Здесь мы объявляем счетчик i и массив MyObjects[], а затем в цикле каждому элементу массива присваиваем значение RandomObject. Заметьте, что мы обращаемся к элементам массива через счетчик: MyObjects[i], который увеличивает свое значение с каждым циклом.
Мы можем объявить массив как свойство:

ObjectReference[] property MyArray auto
8.2 Даже используя некоторые ухищрения, вроде циклов, заполнять массив объектов не всегда удобно. В редакторе Creation Kit для этих целей предусмотрены объекты FormList. Это списки, которые могут содержать другие Form внутри себя. Их можно создать непосредственно в окне редактора, задав определенный ID и заполнив объектами. Все FORmlist вы найдете в Object window -> Miscellaneous -> FormList.
Работа с ними аналогична работе с массивами. Создадим новый FormList.
Назовем его aMyFormList. Заполните этот список своими MiscObject, для чего просто перетащите интересующие в окно созданного FormList.

Изображение

Изображение

К этому моменту наш скрипт должен выглядеть примерно так:

Scriptname MyFirstScript extends ObjectReference
MiscObject property MyItem auto
Event OnActivate(
ObjectReference akActionRef)
If akActionRef == game.getplayer()
akActionRef.additem(MyItem, 1)
elseif akActionRef
(akActionRef as actor).kill()
Endif
endevent


Объявим новую переменную типа FormList:
FormList property MyFormList auto
Значение этой переменной мы должны выбрать в свойствах (property) активатора. Выбираем наш только что созданный FormList.
Для обработки списка применим цикл:

Int i
While i<4

akActionRef.additem(MyFormList.GetAt(i), 1)
i+=1
endwhile

В моем списке четыре объекта. По-умолчанию счетчик равен нулю, но с каждым циклом значение его увеличивается на 1, пока не достигнет четырех. Мы вызываем функцию addItem(), указывая в качестве параметра элемент созданного нами списка, который извлекаем функцией GatAt(), задавая в качестве параметра номер элемента. Таким образом, цикл четырежды добавляет в инвентарь драгоценный камень и каждый раз разный. Но что делать, если мы не знаем длину списка?
Для работы с FormList кроме функции GetAT(), существуют функции:
Function AddForm(Form apForm)
Добавляет Form в FormList.
int Function GetSize()
Возвращает длину списка.
bool Function HasForm(Form akForm)
Возвращает true, если искомая Form находится в списке.
Function RemoveAddedForm(Form apForm)
Удаляет добавленную Form из списка.
Function Revert()
Удаляет все формы, добавленные через скрипт.
Теперь, Если мы перепишем условие как:
While i< (MyFormList .GetSize())

Нам не придется знать изначально, сколько элементов содержится в списке.
А теперь давайте добавим не все элементы списка, а какой-нибудь один, но случайный. В этом нам поможет функция объекта Utility RandomInt(), о которой я уже писал.

akActionRef.additem(MyFormList.GetAt(utility.RandomInt(0, MyFormList.getsize())), 1)
Мы вызываем функцию additem() указывая в качестве параметра объект, полученный функцией GetAt(), аргумент которой получен функцией RandomInt(), которая в свою очередь, возвращает случайное значение в диапазоне от нуля до значения, полученного функцией getsize(). Неплохо, да? Но как-то слишком запутано. Не лучше ли нам разделить этот кусок кода на несколько частей?
Вот тут мы и добрались до создания функций. Мы получили кусок кода, который труден для восприятия, но при этом может использоваться не только в данном событии, но и в других событиях, использующих наш FormLIst. Нам нужно выделить в отдельный блок все эти скобки и раскрыть. Чтобы создать новую функцию вы должны использовать операторные скобки:
Возвращаемое - значение Function Имя-функции (аргумент01, аргумент02)
Endfunction
Внутри них вы можете объявлять переменные, который будут недоступны другим функциям того же скрипта. Нам нужно, чтобы функция добавляла выбранный из списка случайным образом объект в инвентарь выбранному актеру. Значит, нам нужна переменная для хранения ссылки на актера(ObjectReference),переменная для хранения кол-ва передаваемых объектов, и мы должны передать функции какой-нибудь FormList:
Function MyFirstFunction(formList akSourceFormList, int count, ObjectReference akActorRef)
Endfunction

Здесь akSourceFormList – передаваемый нами formList, а akActorRef – ссылка на актера. Давайте получим случайный номер элемента нашего списка
Function MyFirstFunction(formList akSourceFormList, int count, ObjectReference akActorRef)
int Number = Utility.RandomInt(0, akSourceFormList.getSize())
Endfunction

Как вы видите, мы присваиваем переменной Number случайную integer величину. Теперь нам нужно получить ссылку на объект из formlist:
Function MyFirstFunction(formList akSourceFormList, int count, ObjectReference akActorRef)
int Number = Utility.RandomInt(0, akSourceFormList.getSize())
Form ValueREf = akSourceFormList.GetAt(Number)

Endfunction
ValueRef мы присваиваем значение формы, полученной функцией GetAt(Number). В последнем шаге мы добавим выбранному актеру полученный объект.
Function MyFirstFunction(formlist akSourceFormList, int count, ObjectReference akActorRef)
int Number = Utility.RandomInt(0, akSourceFormList.getSize())

Form ValueREf = akSourceFormList.GetAt(Number)
akActorRef.additem(ValueREf, count)

Endfunction
Теперь мы можем вызвать эту функцию для события onActivate:
MyFirstFunction(MyformList, 1, akActionRef)
Как видите, вместо akActionRef.additem(MyFormList.GetAt(utility.RandomInt(0, MyFormList.getsize())), 1) , мы получили аккуратный инструмент для своей работы, который можем использовать и в дальнейшем. Более того, теперь нам не нужно знать, как именно он работает.

В окончательно варианте, скрипт должен выглядеть так:
Scriptname MyFirstScript extends ObjectReference
MiscObject property MyItem auto
Event OnActivate(ObjectReference akActionRef)
If akActionRef == game.getplayer()
MyFirstFunction(MyFormList, 1, ObjectReference akActoionRef)
elseif akActionRef
(akActionRef as actor).kill()
Endif
endevent

Function MyFirstFunction(formlist akSourceFormList, int count, ObjectReference akActorRef)
int Number = Utility.RandomInt(0, akSourceFormList.getSize())
Form ValueREf = akSourceFormList.GetAt(Number)
akActorRef.additem(ValueREf, count)
endfunction


На этом вторая часть руководства по скриптам закончена. Чтобы узнать о скриптах больше, воспользуйтесь ресурсом www.creationkit.com, где можно найти описание всех используемых в редакторе функций и объектов.


Автор статьи: Bartolomeo