Papyrus Введение

Навигация

Материал из Creation Kit Русский
Перейти к: навигация, поиск
Symbol error.png Статья не закончена!

Что такое Papyrus?

Papyrus это скриптовой язык, используемый при создании игры Skyrim. Его работа заключается в получении событий из игры, и ответом на них, вызовая соответствующие функции. Это как "клей", который соединяет вместе квесты, устанавливает переменные в зависимости от действий игрока, а так же ждет и отвечает на конкретные события в игре. Помимо этого, Papyrus управляет большей частью функциональности анимированных объектов, которые требуют взаимодействия со стороны игрока или NPC, и большую часть поведения магических эффектов.

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

Что такое скрипт?

Скрипт на языке papyrus, является простым текстовым документом с исходным кодом (.psc файл), поэтому вы можете использовать любой текстовый редактор чтобы написать и скомпилировать скрипт в формате, понимаемом самой игрой (.pex файл). Чтобы внести изменения в уже скомпилированный файл, нужно обновить или заменить старый .psc файл, и, перекомпилировать его, заменив тем самым устаревший .pex файл.

Что внутри скриптов?

Язык papyrus можно разделить на несколько понятий: скриптовые объекты, функции (Functions), события (Events), переменные и свойства. Страница структура файлов скриптов содержит информацию и примеры по перечисленным понятиям.

Каждый скрипт определяется как тип объекта, например "Quest" (Квест), "Reference" (Ссылка на экземпляр объекта), "Actor" (Актер), или "Book" (Книга). Чаще всего, названия этих скриптовых объектов точно соответствуют тем, которые можно найти в мастер-файле игры в редакторе.

У этих объектов есть функции, которые можно использовать для получения данных, содержащихся в этих объектах из мастер-файла, или, если эти данные сохранились непосредственно во время игры. Например, вы можете использовать функцию GetActorValue("Health") для получения количества единиц здоровья актера в данный момент, и ModActorValue("Health", 50) чтобы добавить 50 единиц к здоровью актера. Так же вы можете использовать функцию Kill() чтобы убить актера. Каждая из этих функций является частью объекта Actor. Если вы попробуете применить функцию Kill() к объекту Book, то компилятор будет жаловаться на код и не скомпилирует скрипт, выдав ошибку, что функция Kill() не является функцией скрипта Book, потому что в скрипте Book эта функция не определена (потому что игра не имеет понятия как убить книгу :D).

У скриптов так же есть события, которые работают аналогично вызову функций, но которые игра сама вызывает на конкретном объекте. Например, есть событие OnDeath(), которое игра передает скрипту типа Actor, прицепленному к объекту, когда актер умер. Это позволяет вам реагировать на игровые события, например, указав завершение квеста после того, как игрок убивает конкретного врага. Поэтому, функции, описанные в предыдущем пункте, как правило, вызывается внутри события.

В скрипте так же могут быть переменные. Переменная - это значение, которое можно изменять и на которое может ссылаться игра и скрипты. Например, вы хотите хранить чисто, полученное с помощью функции GetActorValue(). С помощью переменной, вы можете хранить эту информацию, и использовать её позже.

Скрипт так же может иметь свойства, которые функционируют почти так же, как переменные, но в отличии от них, другие скрипты могут получать и задавать их значения. Свойства так же могут быть изменены в самом редакторе. (Примечание: На самом деле, свойства немного более сложный тип, чем простые "переменные, которые можно получить/задать извне", но для практических целей, свойства очень удобны, и с ними обязательно следует разобраться).

Переменные и Свойства могут быть определены как простые структуры, такие как Boolean, Integer, Float, и т.д. Но их реальная мощь заключается в том, что они могут быть определены как, и содержать любой тип объекта. Например, скрипт Quest, может иметь свойство, которое содержит указатель на Актера. Если у вас есть указатель на объект в свойстве скрипта, то этот скрипт может запускать функции как на самом этом свойстве, так и на самом объекте, прикрепленном к этому свойству. Например, в скрипте квеста есть свойство с указателем на актера, поэтому можно вызывать функцию Kill() к этому свойству, и в итоге, это убьет актера, на которого оно указывает. Свойства могут быть установлены с помощью скрипта во время его выполнения, или могут быть установлены в редакторе, указав в них нужные объекты.

Ещё важно запомнить то, что скрипты на papyrus запускаются только в ответ игре или другим скриптам. Поэтому необходимо, чтобы весь код располагался внутри блока Event, или внутри скриптовых фрагментов, как например участок кода в [Вкладка_Quest_Stages|quest stage], или в topic info, но об этом позже.

InDepth.jpg То, что мы называем скрипты - объектами может запутать некоторых людей. Скрипты добавляются к объектам в игре, они описывают их поведение и функциональность. Они являются как-бы "душой" неподвижной трехмерной модели, сделанной из треугольников и покрытой несколькими текстурами, все это позволяет модели более реально выглядеть. Papyrus скрипты - это то, что делает эту модель реальной. То, что делает её настоящим, полноценным объектом.

Грубо говоря, скрипт это не объект, а скорее его важная часть. Обычно, объект должен быть заключен в некоторую оболочку, содержащуюся в отдельном блоке, который может унаследовать некоторый функционал из класса родителя, который является, если хотите, более абстрактной или общей его версией. Просто эта концепция изначально используется в коде, без скриптового языка, где все, от "вида" до поведения определяется одним, связанным отделенным блоком кода, который связан с другими блоками кода. "Настоящие объекты" в игре это те, которые отвечают за показ модели на экране (машинный код добавляется поверх движка Creation Engine), они содержат базовые свойства. Скрипты добавляются к этим объектам, чтобы предоставить игровым дизайнерам простой, универсальный инструмент, определяющий их поведение, не участвуя в бою с механизмами работы движка на низком уровне (машинном), что препятствуют творчеству и производительности. Они наследуют базовый объект, добавляя к нему поведение или изменения его основные свойства (напр. запуск анимации в ответ на событие), они не являются объектами сами по себе. Они изменяют объект или добавляют что-то к нему, иногда они содержат бОльшую часть определения объекта. Но опять же, они не являются объектами. Вот почему было подчеркнуто, что они точно схожи в большинстве случаев, с предметами, которые можно найти в мастер-файле игры в редакторе.

Так как они добавляют функциональность базовым объектам, то они добавляются к ним, и используют объектно-ориентированные парадигмы, такие как наследование (англ. inheritance), абстракция (англ. abstraction) и инкапсуляция (англ. encapsulation) (путем использования свойств, которые, между прочим, служат как механизм получения/установки), и их можно рассматривать как объекты, пока вы держите вышесказанное в голове. Они как ниточки у марионетки, вместе они составляют единое целое. Объект.

Как писать скрипты на Papyrus?

Вы будете использовать текстовый редактор для написания скрипта (избранные здесь включают Notepad++ и Sublime Text, в скором времени выйдет редактор от skv1991). После того как вы написали скрипт, вам нужно скомпилировать его прежде чем он будет работать в игре.

Перед тем, как сесть писать скрипт, нужно подумать, а какой же тип скрипта должен быть. Или, другими словами, какой будет тип у объекта, на котором планируется запускать этот скрипт. Как только вы это решите, вы создаете новый скрипт, объявив его в верхней части .psc файла, и что он наследует базовый скрипт Объекта, на основе которого будет работать. Например, если вы собираетесь сделать триггер-бокс, который задает переменную, вы создадите скрипт, который наследует ObjectReference.

Наследование скриптов

Для каждого объекта в игре, который может быть заскриптован, или на который можно указывать через свойство, есть уже соответствующий готовый "базовый" скрипт на Papyrus: смотрите Скриптовые объекты для ознакомления с тем, какие есть. По сути, игра может дать любой объект, соответствующий типу этого скрипта во время выполнения.

Например, есть ObjectReference Script. Таким образом, любой объект типа ObjectReference в игре, имеет привязанный скрипт типа ObjectReference. Это значит что вы можете определить свойство ObjectReference в скрипте, и указать в нем на любой объект типа ObjectReference, и вызывать любую функцию, касающуюся типа ObjectReference на этом объекте, как например Disable(), без необходимости вручную прикреплять скрипт ObjectReference в редакторе к каждому объекту ObjectReference игры, на котором вы хотите вызвать функцию Disable().

Однако, скорее всего вы пишете скрипт потому, что хотите сделать что-то особенное. Ответить на конкретное событие, как например ожидание, когда игрок активирует объект, или сделать что-то в ответ на смерть актера, и т.д. Никакой из данного, уже существующего функционала, не находится в скриптах объектов. Впрочем, чтобы компилятор знал с каким типом объекта вы имеете дело, и получал доступ ко всем событиям и функциям, которые уже определены для этого типа объекта, вы наследуете существующий “базовый” объект-скрипт, и добавляете ему ваш специальный код. (Похожая система есть с классами в языке PHP, и практически любом языке ООП)

Пример скрипта (наследует ObjectReference)

Например, вы хотите установить этап квеста когда игрок вошел в определенный триггер-бокс. Вы создали скрипт с названием “MyTriggerBoxScript” который наследует “ObjectReference” и прицепили его к ссылке на триггер-бокс в окне Render Window. Затем вы создали свойство с именем MyQuest, и указали в нем на ваш квест в редакторе. Затем, написали свою версию события OnTriggerEnter, которая бы вызывала установку этапа у квеста из свойства myQuest.

 Scriptname MyTriggerBoxScript extends ObjectReference
 
 Quest Property MyQuest Auto
 Int Property StageToSet Auto
 
 Event OnTriggerEnter(ObjectReference akActionRef)
 	If akActionRef == Game.GetPlayer()
 		MyQuest.SetStage(StageToSet)
 	EndIf
 
 EndEvent

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

Первая строчка любого скрипта начинается с ("заголовочной строки") со слова “Scriptname”, благодаря которому мы говорим компилятору, что скрипт будет иметь следующее название (в данном случае название “MyTriggerBoxScript). Это имя ДОЛЖНО совпадать с именем текстового файла, в котором вы пишете скрипт (в данном случае “MyTriggerBoxScript.psc”). Слово “extends” обозначает что этот скрипт наследует возможности другого скрипта (в данном случае скрипт “ObjectReference”). Это значит что ваш скрипт имеет доступ ко всем функциям и событиям, определенным в “ObjectReference.psc” и вы можете изменять их, или добавить новые в ваш скрипт.

В следующей строке объявляется "auto" свойство. “Свойство”(англ. property) говорит о том, что мы хотим объявить новое свойство, которое будем использовать в нашем скрипте. Quest это тип свойства (другими словами, только объекты типа Quest могут быть записаны в это свойство). MyQuest это имя свойства. Итак, вся вместе строка сообщает компилятору, что “Я хочу знать о свойстве с названием MyQuest, и в нем может находиться только объект типа Quest, и никакой другой.” (Сейчас давайте пропустим ключевое слово “auto”. Просто знайте, что почти всегда пишется слово “auto” в конце каждого свойства, которое вы объявляете. Для полного обсуждения см.: Объявление свойств.)

Аналогично и в следующей строке объявляется свойство типа integer.

Далее мы видим несколько строк, начинающихся с Event и заканчивается на EndEvent. You can think of these like book ends for this particular event. Все внутри этого блока - это то, как скрипт будет отвечать на событие “OnTriggerEnter”. У вас может быть и скрипт,отвечающий на несколько событий. Используя Event/EndEvent вы отделяете все, что хотите, чтобы произошло для этого события от какого-либо другого места в скрипте.

OnTriggerEnter это названием события реализуемого скриптом. Скобки ( ) определяют какие параметры игра должна передать в ваш скрипт в событие OnTriggerEnter. "akActionRef" это название параметра, и "ObjectReference" определяет какой тип имеет этот параметр – и, если вы обратили внимание, вы также знаете, что "ObjectReference" сам по себе является скриптом на papyrus - (ObjectReference Script).

Следующая строка, начинающаяся с “if” и через строчку с “EndIf.” Это условная инструкция. В ваших скриптах вы будете писать много “if-then-else инструкций”. Здесь мы проверяем является ли объект типа ObjectReference “akActionRef” который игра передала в событие OnTriggerEnter, когда актер зашел в зону триггера игроком или нет.

Game.GetPlayer() это функция GetPlayer() из скрипта Game Script которая возвращает актера который представляет игрока. Символы == это проверка ("оператор сравнения"), которая означает “является ли эта вещь другой вещью?”. Поэтому мы спрашиваем является ли ссылка на объект, которую игра передала в наш скрипт, когда актер вошел в область треггера, такой же как ссылка на объект возвращаемая функцией Game.GetPlayer()? Другими словами “Вошел ли игрок в зону триггера?”. Если так, то вызвать функцию SetStage() на квесте, находящемся в свойстве MyQuest, и сообщить какой установить этап, который находится в свойстве StageToSet.

Если вы внимательно следовали всему сказанному, то вы можете задаться вопросом, как мы можем сравнивать ObjectReference "akActionRef" с актером (значение, возвращаемое функцией Game.GetPlayer()). А делать это мы можем по причине того, что компилятор знает, что все объекты типа Actor также принадлежат к ObjectReferences потому что скрипт Actor Script расширяет скрипт ObjectReference Script. Итак, вы видите, что расширение используется не только для собственных скриптов, но базовые скрипты также расширяют другие! В вики описан каждый скриптовой объект содержащийся в верхней части страниц этих объектов, и скрипт который он расширяет.

Теперь мы знаем как настроить скрипт, который делает проверки и совершает действия со свойствами, но может быть интересно, где мы сообщаем скрипту какие объекты и значения прицепить к свойствам? (В примере выше, какой квест, и какой этап квеста о которых мы здесь говорили?)

Установка переменных и свойств

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

Установка переменных и свойств в скрипте

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

Вы можете объявить его в одном месте, и задать значение в каком-то другом:

 ;Вверху скрипта мы объявляем переменную:
 Int MyStage
 
 ;...
 
 ;Позже, внутри блока события:
 MyStage = 100

А так же вы можете присвоить значение прямо во время объявления:

 Int MyStage = 100

Примечание: вы можете задавать значение переменной или свойства во время объявления где угодно в скрипте, до тех пор, пока они являются "литералами." Однако переменные/свойства всех остальных типов (actor например) можно задать во время объявления, но только если они объявляются внутри блока события или функции.

Например, объявляем вверху, а задаем значение где-то ещё:

 ;Объявляем вверху
 ObjectReference ThingActivatingMe
 
 ;задаем внутри события
 Event OnActivate(ObjectReference akActionRef)
    ThingActivatingMe = akActionRef  
 
 EndEvent

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

Или же, вы можете одновременно объявить и задать её (внутри функции или события) как здесь:

 ;задается внутри события
 Event OnActivate(ObjectReference akActionRef)
    ObjectReference ThingActivatingMe = akActionRef  
 
 EndEvent

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

Установка данных свойства в редакторе

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

Множество форм в редакторе имеет вкладку "Scripts", которая дает вам возможность a) добавить скрипты к объекту, и b) задать значения свойств у этих скриптов.

Что касается сейчас, давайте посмотрим в редакторе на форму объекта типа Activator, и посмотрим на зону со скриптами(Script):

Если вы выделите скрипт, щелкнув по нему, затем нажмете кнопку properties, то увидите список всех свойств скрипта. Если нажмете кнопку "Edit Value" то появится текстовое поле для ввода числового значения, чекбокс, если это булевое значение, или один или несколько выпадающих списков, если это объект.

На картинках ниже, вы можете увидеть, что свойству myQuest установлено значение MQ101, и StageToSet значение 100.

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

Доступ к вкладке Scripts у ссылки на объект

Ссылка наследует любые скрипты и свойства, которые были установлены на её базовом объекте (которые перечислены в окне Object Window).

Например, актеры и активаторы. Если вы прицепите скрипт к актеру или к активатору, то все индивидуальные ссылки на эти объекты в игре, будут так же иметь все эти же самые скрипты. Их ссылки будут всегда наследовать данные по всем свойствам, которые вы задали на их скриптах.

Вы можете переопределить значения этих свойств, открыв вкладку "Scripts" у ссылки в окне Render Window, и установить те данные у свойств, которые хотите переопределить только на данной ссылке. Вы так же вполне можете удалить наследованные скрипты с ссылки, удалив(Remove) скрипт из вкладки scripts у ссылки на объект в окне render window. Примечание: вы так же можете добавить(Add) скрипт напрямую к данной ссылке в окне Render Window.

Фрагменты на Papyrus

В дополнение к добавлению скриптов к формам в редакторе, есть некоторые формы, которые содержат поле, называемое "Papyrus fragments" (фрагменты кода на языке Papyrus), в котором вы можете писать скрипты, которые будут запущены в определенное время. Здесь так же есть специальные переменные, которые можно использовать, например, фрагменты в формах Topic Info и Package имеют переменные akSpeaker и akActor, которые вы можете использовать, для получения доступа к актеру, который произносит строку текста, или к актеру, у которого выполняется пакет.

Пример фрагмента topic info fragment:

 akSpeaker.StartCombat(Game.GetPlayer())


Самым распространенной техникой использования является установка этапа квеста, которому принадлежит форма с фрагментом скрипта на Papyrus. Некоторые типы форм могут принадлежать квестам (Например сценам, формам topic info в квесте как бы сказано, что они принадлежат этому квесту, так же как и любой пакет, у которого выбран квест в выпадающем списке "Owner quest"(квест-владелец), которому принадлежит этот пакет). Пример установки этапа квеста из поля fragment, находящегося на форме, которая принадлежит квесту:

 GetOwningQuest().SetStage(100)


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

Состояния (State)

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

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

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

Давайте теперь рассмотрим, как это может выглядеть внутри скрипта.

 Event OnActivate(ObjectReference akActionRef)
    ;ПРОИЗВОДИМ ДЕЙСТВИЯ ТУТ
 EndEvent

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

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

 Auto State AtRest
    Event OnActivate(ObjectReference akActionRef)
      GoToState("Busy")
 
      ;ПРОИЗВОДИМ ДЕЙСТВИЯ ТУТ
      ;Проигрывание анимаций, и т.д. - оставили не определенным для иллюстрации
      ;Заканчиваем действия
 
      GoToState("AtRest")
    EndEvent
 EndState
 
 State Busy
    Event OnActivate(ObjectReference akActionRef)
       debug.trace("Я занят, так что пока ничего не сделаю.")
    EndEvent
 EndState


В примере выше, вы видите две пары State/EndState, в первой из которых есть слово "auto". Ключевое слово auto здесь значит, "это состояние по-умолчанию" до вызова любых "GoToState()", то есть исходное состояние, в котором находится скрипт. Как и с событиями, строчки с "state/endState" заключают в себе часть скрипта, принадлежащего каждому из состояний. Все события и функции внутри каждого события - это версии, которые использует скрипт, находясь внутри одного из этих состояний.

Поместить скрипт в определенное состояние можно вызвав функцию GoToState("StateName"), где "StateName" это строка, такая же, как название объявленного состояния.

Любое событие вне блока State/EndState будет запущено, если скрипт не находится ни в одном из состояний. (Например, если вы объявили состояния, и ни одно из них не имеет ключевого слова "auto", то будут использоваться все события и функции вне состояний, пока скрипт не будет переведет в другое состояние через вызов "GoToState()". Вызов "GoToState()" с пустым параметром строки переведет скрипт в вариант без состояния (так же зовется "пустое состояние" - англ. empty state).

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

Чтобы узнать больше о состояниях, смотри: States (Papyrus)

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

Собственные функции

В Papyrus можно писать свои собственные функции. Затем вы можете вызывать эти функции в области скрипта, где они были созданы, а так же и из других скриптов. Это более продвинутая, но более мощная и полезная возможность скриптовго языка.

Это будет самое сложное обсуждение на странице введения. И при обсуждении написания функций, мы так же столкнемся с другими понятиями. Не расслабляйтесь. Вы сможете добиться больших достижений, с меньшей головной болью. Не часто понадобится писать свои собственные функции, но когда вы их делаете, они помогут сохранить ваше время энергию, потому что вы можете написать фрагменты скрипта (сниппеты), которые можно повторно использовать во многих местах, что позволяет сократить время на реализацию некоторых вещей, и сделать отладку намного проще.

Вызов функций

Перед обсуждением создания собственной функции, давайте поговорим ещё немного о том, что такое функции.

Функция - это участок скрипта, который вы используете (или "вызываете"), чтобы производить или получать какие-то действия. Скрипт может вызывать функцию, содержащуюся внутри него, или может "вызывать" функцию в другом скрипте.

Несколько примеров функций, существующих на базовых скриптах:

  • GetDisabled() - используется для определения, был ли объект ObjectReference отключен или нет
  • Disable() - используется для отключения объекта ObjectReference
  • Enable() - используется для включения объекта ObjectReference
  • GetDead() - используется для определения, умер ли актер или нет
  • Kill() - используется для убийства актера
  • GetActorValue() - используется для получения числового значения параметра актера (например, получить значение здоровья актера - "Health," или уровень владения навыком "TwoHanded")
  • SetActorValue() - используется для установки числового значения параметра актера (например, установить значение здоровья актера - "Health," или уровень владения навыком "TwoHanded")


В примере выше, вы заметите несколько вещей. Вы можете использовать функции для получения информации об объекте, а так же устанавливать значения и заставлять из делать действия. Функции используются определенными типами объектов (вы вызываете Enable() на объектах типа ObjectReferences, и Kill() на актерах Actors). Поначалу может быть не понятно, что объект, на котором запущена функция, также имеет и соответствующий скрипт, привязанный к нему. По существу, игра сама добавляет соответствующий базовый скрипт к объектам, во время их выполнения (актеры получают Actor Script, объекты типа ObjectReferences получают ObjectReference Script).

Если вы хотите вызвать GetDisabled(), у вас должен быть "указатель" на объект ObjectReference (к которому игра прицепляет скрипт ObjectReference Script. Если вы хотите вызвать GetDead(), то у вас должен быть "указатель" на актера (к которому игра прицепляет скрипт Actor Script).

Итак, как же получить "указатель" на объект?

Обычным способом для его получения является установка свойства в скрипте, которое будет указывать на нужный объект в мастер-файле игры. Например, вы можете установить свойство с названием "myReference" и указать в нем на большой валун, блокирующий вход в подземелье в определенной ячейке в окне Render Window, и затем написать в этом скрипте "myReference.Disable()", для отключения этого объекта, показывая вход в пещеру, которую игрок до этого не мог обследовать.

После установки свойства:

 ObjectReference Property myReference Auto   ;определяет свойство, которое нужно будет настроить в редакторе, чтобы указывать на определенную ссылку (объект)

Вы затем можете использовать на нем функции:

 myReference.Disable()

См. раздел выше "установка данных свойства в редакторе", чтобы узнать, как настроить данные у свойства, указывающие на предмет в мастер-файле.


Другим способом получения указателя на объект является его получение из другой функции.

Пример: Game.GetPlayer() вернет объект типа Actor, который указывает на игрока.

Например:

 Actor Property myActor Auto   ;определит свойство

Затем в скрипте, вы можете использовать функцию, которая вернет актера, сохранить на него указатель, и затем вызвать функцию на свойстве

 myActor = Game.GetPlayer()
 myActor.Kill() ;это убьет игрока. Не лучший вариант. Но иногда вполне заслуженный. ;)

Кроме того, можно взять и совсем пропустить присвоение к свойству, и вместо предыдущего примера, связать все вместе и написать вроде этого:

 Game.GetPlayer().Kill()

Выше написанное будет работать, потому что компилятор знает, что Game.GetPlayer() всегда в результате выдает объект типа Actor, и поэтому этот объект может использовать скриптовую функцию Kill().

А теперь, напоминание о том, что мы узнали о расширении скриптов. Некоторые базовые скрипты расширяют возможности других. Так, Actor Script расширяет ObjectReference Script. Это означает что Actor Script будет иметь все те же функции, что и ObjectReference Script, и для всех намерений и целей, все актеры могут рассматриваться как объекты ObjectReferences. Так, если у вас есть указатель на актера, вы может применить к нему функцию Disable(), и отключить его, даже если эта функция находится в скрипте ObjectReference.

Исходя из всего написанного, можно вывести следующий итог:
К игровым объектам присоединены базовые скрипты, в зависимости от их типа. Вы можете вызывать функции на конкретных экземплярах этих объектов (вызов функций в их скриптах, или в скрипте, который расширяется), получая указатель на них, и затем вызывая функцию на этом указателе. Указателями могут служить объекты, хранимые в свойствах, или объекты, возвращаемые функциями.

Вызов своих функций

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

Обычной практикой является написание функций в скрипте, привязанном к квесту. Например, допустим вы сделали скрипт с названием "myQuestScript" и написали функцию "DoSomeStuff()"):

 Scriptname myQuestScript extends Quest
 
 Function DoSomeStuff()
    ;здесь какие-то крутые действия
 EndFunction

И затем вы прицепляете этот скрипт к квесту в игре.

Скрипт знает обо всех объявленных в нем функциях. Так что если вы собираетесь вызвать функцию "DoSomeStuff()" в скрипте выше, который прикреплен к квесту, то просто вызовите функцию.

 DoSomeStuff()

Но если вы собираетесь вызвать эту функцию где-то ещё, вам нужен указатель на ваш квест (так же, как мы говорили про базовые скрипты). Однако, нужно установить тип указателя, как тип вашего скрипта. Тип вашего скрипта такой же, как и его название, объявленное на первой строчке скрипта. В данном случае "myQuestScript." Здесь мы объявляем новое свойство с этим новым типом:

 myQuestScript Property myQuest Auto

Затем вы можете открыть окно свойств скрипта, используя соответствующую кнопку, и присоединить его к этому конкретному квесту. Например, у алиаса "Quest Giver" в вашем квесте, вы добавляете скрипт, который при его смерти (того, кто дает квест), будет вызывать функцию "DoSomeStuff()" на квесте.

Давайте посмотрим, как это может выглядеть. Ниже идет пример скрипта, который можно присоединить к алиасу "Quest Giver" в вашем квесте.

 Scriptname myQuestGiverScript extends Quest
 
 myQuestScript Property myQuest Auto  ;здесь вы указываете на ваш квест во вкладке свойств скрипта у [[Вкладка Quest Alias|алиаса]] "Quest Giver".
 
 Event OnDeath(Actor akKiller)
   myQuest.DoSomeStuff()
 EndEvent


Есть и более короткий пример написанного выше. Так как алиасы принадлежат квестам, у скрипта Alias Script есть функция "GetOwningQuest()", которая возвращает объект типа Quest, который является указателем на квест, содержащий этот алиас. Таким образом, вы могли бы попытаться сделать что-то вроде этого:

Event OnDeath(Actor akKiller)
  GetOwningQuest().DoSomeStuff()  ;этот вариант выдаст ошибку компиляции
EndEvent

Но это не сработает, потому что объект, возвращаемый в GetOwningQuest() имеет тип Quest. Который знает только о функциях, объявленных внутри него. Компилятор выдаст ошибку, что нет функции с названием "DoSomeStuff" в Quest. Ваша функция "DoSomeStuff()" находится в вашем скрипте с именем "myQuestScript."

Теперь, поскольку myQuestScript расширяет Quest, вы можете пообещать компилятору, что предмет, возвращаемый функцией GetOwningQuest будет содержать скрипт myQuestScript, который к нему прицеплен. Вы делаете это "ПРЕОБРАЗУЯ" (англ. CASTING) объект типа Quest в объект типа myQuestScript.

 Scriptname myQuestGiverScript extends Quest
 
 Event OnDeath(Actor akKiller)
   (GetOwningQuest() As myQuestScript).DoSomeStuff()  ;это сработает
 EndEvent

GetOwningQuest() возвращает указатель на тип Quest, слово "As" означает, что объект будет рассматриваться КАК myQuestScript. Ограничивающие скобки () позволяют вам использовать "." для доступа к дочерним функциям и свойствам преобразованного объекта, таким образом, вы можете вызвать в нем функцию DoSomeStuff().

Написание своих функций

Мы изучим повседневные моменты написания собственных функций, рассмотрев пример того, что мы могли бы хотеть сделать, при написании квеста: отследить смерть квест-гайвера (того, кто дает квест).

Функция имеет строки начала/конца (Function/EndFunction) и может содержать объявление параметров. В данном случае, давайте создадим функцию с названием "HandleQuestGiverDeath", которая принимает в качестве параметра актера, совершившего убийство, таким образом, мы можем делать различные вещи, если игрок убьет квест-гайвера, или кто-то ещё. Мы напишем эту функцию в скрипте "myQuestScript" который мы прицепили к нашему квесту в редакторе.

 Scriptname myQuestScript extends Quest Conditional
 
 ReferenceAlias Property QuestGiver Auto
 ReferenceAlias Property QuestGiverBackup Auto
 
 int Property QuestGiverKiller auto conditional  ;0 = не задано, 1 = игрок, -1 = не игрок
 
 function HandleQuestGiverDeath(Actor Killer, ReferenceAlias DeadActorAlias)
   if DeadActorAlias == QuestGiver 
     QuestGiver.ForceRefTo(QuestGiverBackup.GetReference())
     SetStage(110)
 
      if Killer == Game.GetPlayer()
         QuestGiverKiller = 1
      else
         QuestGiverKiller = -1
      endIf
 
   endif
 
 endFunction

В примере выше было использовано новое ключевое слово. Вы наверняка заметили слово "Conditional" на строчке Scriptname, а так же при объявлении свойства QuestGiverKiller. Это позволяет свойству QuestGiverKiller находиться условием GetVMQuestVariable, таким образом, вы можете проверить значение этого свойства во время игрового процесса, чтобы отфильтровать стек диалогов и получить соответствующий диалог,   условный пакет стеков   у актера и т.д. Когда есть переменные вроде этой, отслеживающие что-то конкретное, что произошло, то это часто помогает в отладке. (Чтобы видеть значения скриптов во время игры, смотри: ShowQuestVars и ShowVars)

Теперь обратим внимание на строчку функции. Здесь вы видите пару "Function/EndFunction" ограничивающую участок скрипта внутри нашей функции. "HandleQuestGiverDeath" это название, которое м дали нашей функции. А все что внутри парных скобок () это параметры, которые ожидают получения значений при вызове "HandleQuestGiverDeath" с передачей параметров. В этом случае нам нужен параметр "Killer" (т.е. убийцы) типа Actor, и алиас ReferenceAlias, который мы назвали "DeadActorAlias."

Let's break down this line:

 QuestGiver.ForceRefTo(QuestGiverBackup.GetReference())

Здесь мы вызываем функцию ForceRefTo которая поместит ссылку в алиас, так что она сможет использовать диалоги и пакеты этого алиаса. Функция ForceRefTo принимает в качестве параметра тип Reference. Так уж получилось, что GetReference возвращает ссылку, которая находится внутри этого алиаса. So here were are asking the QuestGiverBackup alias "hey, what's the reference that is currently in you?" and then taking that result and passing it into the ForceRefTo function we are calling on QuestGiver alias, so that the QuestGiver alias now has in it the same reference that is in the QuestGiverBackupAlias.

Now what's missing is the script we attach to the QuestGiverAlias:

 Scriptname myQuestGiverAliasScript extends ReferenceAlias
 
 Event OnDeath(Actor akKiller)
    (GetOwningQuest() as myQuestScript).HandleQuestGiverDeath(akKiller, Self)
 EndEvent

Here we see that during the OnDeath event, we will be calling the "HandleQuestGiverDeath" function on the myQuestScript script we attached to the quest. Here again is the common shortcut "(GetOwningQuest() as TheNameOfYourQuestScript)" syntax so we can append the .FunctionName() to it. This works because the script we are doing this in is "owned" by the quest (all Aliases are said to be owned by the quest they are created in)

Notice that we are simply "passing in" the akKiller that the event gives us, as the "Killer" actor parameter on our function, and we are passing in "self" as the parameter for the DeadActorAlias ReferenceAlias parameter. We can use "Self" to mean, "the object this script is attached to" which in this case, is a ReferenceAlias, the type our function is looking for it's DeadActorAlias parameter. (Astute readers may ask "Why do are we bothering to pass in the DeadActorAlias, can't we assume it's the quest giver?" We could. And probably would. But I wanted to give you an example that show cased some of these other concepts such as passing in "Self" as a parameter value)

And there's one more thing to complete your basic understanding of the scripting language, and that is your custom functions can return a value just like native functions do.

For example, we could change the above script to this:

 Scriptname myQuestScript extends Quest Conditional
 
 ReferenceAlias Property QuestGiver Auto
 ReferenceAlias Property QuestGiverBackup Auto
 
 int Property QuestGiverKiller auto conditional  ;0 = unset, 1 = player, -1 not the player
 
 function HandleQuestGiverDeath(Actor Killer, ReferenceAlias DeadActorAlias)
   if IsQuestGiver(DeadActorAlias)
     QuestGiver.ForceRefTo(QuestGiverBackup.GetReference())
     SetStage(110)
 
      if Killer == Game.GetPlayer()
         QuestGiverKiller = 1
      else
         QuestGiverKiller = -1
      endIf
 
   endif
 
 endFunction
 
 bool function IsQuestGiver(ReferenceAlias RefAliasToCheck)
    if RefAliasToCheck == QuestGiver
       Return True
    else
       Return False
    endif
 endFunction

Here we put the check testing if the alias in question is the quest giver into it's own function "IsQuestGiver" that returns a value (in this case a bool) that we can then use in our comparison check. (Again, this is probably more complex than you would need for a simple function like this, but it illustrates the point that you can return your own values. You could also write a function that returns an actor, a quest, or any other Script Object. This can be a big boon in more complex scripting situations.)

Что мне стоит почитать дальше?

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

  • Прочитайте данную страницу снова, и пройдитесь по ссылкам, чтобы узнать больше деталей, которые могли опускаться или были даны слишком поверхностно.
  • Просмотрите документацию к языку здесь: категория справка по языку Papyrus
  • Исследуйте страницы в категории Скриптовые объекты и посмотрите какого рода функции доступны для различных объектов.
  • Пробегитесь по другим информационным страницам и учебникам в верхней части категории Papyrus
  • Потыкайте в разных местах редактора, и разложите существующие вещи.