fallout.ru

Наш первый скрипт

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

Поехали!

Начнём с открытия редактора скриптов: запустите TES Construction Set (здесь и далее ТЕС КС), в меню “File” выберите пункт “Data Files…”, откройте в нём файл Morrowind.esm и в меню “Gameplay” выберите пункт “Edit Scripts”.

Окно скрипта

Вы можете открыть это окно, выбрав “Gameplay” – “Edit Scripts” (или нажав соответствующую кнопочку на панели инструментов – карандаш) или щёлкнув прямо в описании объекта или NPC по кнопке […], находящейся рядом с полем скрипта. Как Вы можете видеть окно редактора незамысловато:

Взглянем на панель инструментов слева направо: кнопочка “Open” позволяет выбрать, какой из существующих скриптов Вы хотите открыть для редактирования. “Save” проверяет код на наличие ошибок и либо компилирует его, либо выводит сообщение об ошибке – заметьте, что после нажатия на эту кнопку плагин, а, следовательно, и наш скрипт не сохраняются на жесткий диск. Так что после этого стоит сохранить плагин в главном окне ТЕС КС на случай, если Конструктор вдруг «вылетит» в Windows. Стрелки “Forward” и “Backward” перемещают Вас к следующему или предыдущему скрипту соответственно. Если в начале имени скрипта Вы вставите характерные символы, то это позволит Вам быстрее перемещаться между различными скриптами Вашего проекта. То есть, если Вы назовёте все свои скрипты по шаблону “AA_Scriptname”, то они всегда будут находиться вверху списка, что значительно облегчит к ним доступ. “Compile All” перекомпилирует все скрипты, однако то, чем же эта функция полезна, так и осталось для меня загадкой. Наконец, “Delete” уничтожает скрипт, а последняя кнопка, представляющая собой стрелку вниз, закрывает окно редактора.

Меню “Help” позволяет быстро получить доступ к информации относительно интересующей Вас функции или иного фрагмента кода. Вы можете использовать «горячие» клавиши для копирования и вставки кусков скрипта: копировать – CTRL+C, вырезать – CTRL+X, вставить – CTRL+V.

Так что же мы хотим?

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

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

Написание скрипта

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

Название скрипта. Begin и End

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

Begin my_first_script

Следует отметить, что имя должно быть одним словом. Запомните также то, что ТЕС Скрипт нерегистрозависим, так что Вы можете написать “Begin” и с маленькой буквы. Имя должно быть уникальным, ТЕС КС будет определять Ваш скрипт именно по нему. Попробуйте нажать кнопку “Save” и получите сообщение об ошибке, сообщающее вам, что «вам нужно обозначить границу скрипта строкой end scriptname». Так что для того, чтобы редактор прочитал ваш код, на новой строчке напишите:

End

Как видите, мы можем опустить имя в конце и просто написать “End”. Нажав “Save” теперь, вы увидите, что имя вашего скрипта появилось в строке заголовка окна редактора, что показывает Вам, что скрипт был принят компилятором. Это минимально возможный код, и вполне естественно, что он не делает ничего.

Определение действия игрока

Теперь нам нужно определить, пытается ли игрок открыть сундук. В ТЕС Скрипте существуют объекты функции и команды. Осталось решить, что же нам необходимо.

Объекты – все вещи в игре, будь то видимые объекты, стены, существа, NPC или звуки.

Функции – это определённые «слова» ТЕС Скрипта, которые позволяют нам либо управлять объектами, либо получать информацию о них.

Команды – это тоже «слова», которые, однако, не влияют на игровой процесс. Например, “Begin” указывает лишь на начало кода скрипта. Чтобы сообщить, какому объекту необходимо выполнить функцию, мы используем «стрелки», состоящие из дефиса и знака «больше»: ->
Причём слева от знака стоит объект, к которому применяется функция, стоящая справа.

имя_объекта -> функция, [параметры]

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

Для нашего сундука нам необходима функция OnActivate, помогающая определить взаимодействует ли игрок с предметом. Функция возвращает единицу (в программировании это означает «ИСТИНА»), если объект был активирован, то есть в ходе игры пользователь нажал на нём «использовать» (клавиша «пробел» по умолчанию). Так что нам в любой момент времени игры нужно проверить, открывал герой сундук или нет. Исправьте код Вашего скрипта на следующий:

Begin my_first_script

If ( OnActivate == 1 )
            ; здесь мы будем определять действия, происходящие при активации объекта
endif

End

Ещё несколько вещей, нуждающихся в пояснении. Команда “if” проверяет, верно ли условие в скобках. Если да, то выполняется код программы до зарезервированного слова “endif”. “==” проверяет, равно ли значение выражения слева (в нашем случае функции OnActivate) значению выражения справа (в нашем случае 1). Если в конце условной конструкции Вы забудете поставить endif, то компилятор выдаст Вам соответствующее сообщение об ошибке. Знак точка с запятой – “;” – определяет начало комментария. Что бы Вы ни написали за этим знаком, во время выполнения скрипта будет проигнорировано. Если Вы планируете писать огромные скрипты, Вам стоит принять за правило ставить комментарии по ходу написание кода.

Текстовые сообщения и определение выбора игрока

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

MessageBox "Без голоса кричит, без крыльев летает, без зубов кусает, безо рта шепчет. Что это?", "Летучая мышь", "Старая женщина", "Ветер", "Призрак"

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

Как выполняются локальные скрипты

Скрипт, прикреплённый к активному объекту выполняется каждую долю секунды, так называемый фрейм, величина которого зависит от быстродействия вашего компьютера. Всё помещение, в котором находится игрок активно. Так что все скрипты (а не только какая-то их строчка) могут выполняться 10-60 раз в секунду! Так что все скрипты оказываются включёнными в некую гигантскую конструкцию “while-loop”:

while (Object is in active cell) ; Пока объекты находятся в одной локации с игроком

[Your Script Code] ; Выполняются все скрипты, закреплённые за объектами этой локации

endwhile

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

Begin Message_script

MessageBox “Тысяча бесполезных сообщений”

End Message_script

Этот пример относительно безвреден, но представьте, что случится, если Вы будете подобным образом добавлять в инвентарь игрока определённую вещь или ставить перед ним монстра!

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

Begin my_first_script

Short controlvar

If ( OnActivate == 1 )
            If ( controlvar == 0)
                        MessageBox "Без голоса кричит, без крыльев летит, без зубов кусает, безо рта шепчет. Что это?", "Летучая мышь", "Старая женщина", "Ветер", "Призрак"
                        Set controlvar to 1
            endif
endif

End

(Стоит отметить, что функция MessageBox в окне редактора должна быть написана в одну строку)

“Short controlvar” объявляет новую переменную типа “Short” с именем “controlvar”. Для начала достаточно знать, что эта переменная будет обрабатывать целочисленные значения. Переменная – это некое поименованное зарезервированное место в оперативной памяти для хранения определённых значений. Команда Set собой ничего сложного не представляет – она лишь меняет значение нашей переменной с 0 (все переменные ТЕС Скрипта инициализируются нулём) на 1. Вместе со строкой if ( controlvar == 0 ) это создаёт одноразовую конструкцию – при повторной активации сообщение появляться не будет, поскольку наша переменная уже будет иметь значение не ноль, а один.

Теперь наш скрипт готов, так что давайте его протестируем:

  • Сохраните скрипт и закройте окно редактора.
  • Зайдите в Окно Объектов ТЕС КС, на вкладке “Container” выберите и откройте “chest_small_01”.
  • Измените ID этого сундука на “tutorial_chest”.
  • В поле скрипта выберите “my_first_script”.
  • Сохраните свойства объекта, Вам предложат создать новый – соглашайтесь.
  • Сохраните плагин и закройте Конструктор.
  • Запустите Морровинд совместно с Вашим модом (в меню Data Files необходимо пометить крестиком подключаемый плагин) и загрузите последнюю сохранённую игру. (Не обращайте внимания на предупреждения о возможных ошибках)
  • Кнопкой клавиатуры «тильда» – “~” – (по умолчанию), что левее “1”, вызовите консоль, где пропишите:
    PlaceAtPC tutorial_chest 1,1,1
  • Закройте консоль и отойдите персонажем немного назад.

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

На что бы Вы ни нажали, окно сообщения просто закроется, однако при попытке открыть сундук снова ничего не произойдёт. Это значит, что наше одноразовое условие работает.
Выйдите из игры и вернитесь к редактированию нашего плагина в Конструкторе.
Теперь нам нужно проанализировать, какой ответ дал игрок и в зависимости от этого определить выполняемое действие. В этом нам поможет функция “GetButtonPressed”, возвращающая номер выбранной кнопки: 0 – для первой (в нашем примере это «Летучая мышь»), и далее 1,2 и 3 соответственно. Пока ответ не был выбран, функция возвращает -1, поэтому нам придётся позаботиться и об этом.

Функция “Activate” будет открывать наш сундук. Фактически она просто провоцирует стандартное для объекта действие, то есть открывание двери, начало разговора с NPC и т.д.
Следующий скрипт также наглядно показывает, как можно использовать контрольные переменные, чтобы функции выполнялись одна за другой, несмотря на то, что каждую секунду должен выполняться весь скрипт. Этого можно достичь простым условным блоком if – endif. Это самый безопасный способ написания скриптов, хотя он может быть целесообразным далеко не всегда.

Измените свой скрипт на следующий:

Begin my_first_script

Short controlvar
Short button

If ( OnActivate == 1 )
            If ( controlvar == 0 )
                        MessageBox "Без голоса кричит, без крыльев летит, без зубов кусает, безо рта шепчет. Что это?", "Летучая мышь", "Старая женщина", "Ветер", "Призрак"
                        Set controlvar to 1
            elseif ( controlvar > 1 )
                        activate
            endif
endif

if ( controlvar == 1 )
            set button to GetButtonPressed
            if ( button == -1 )
                        return
            elseif ( button == 2 )
                        MessageBox "Правильно!"
                        Activate
                        set controlvar to 2
            else
                        MessageBox "Неверно!"
                        set controlvar to -1
            endif
endif

End

Взгляните на кусок кода, начинающийся со строки “if (controlvar == 1)”. Мы присваиваем переменной значение 1, как только игрок открывает сундук. Теперь нам нужно проверить, какая кнопка была нажата. Для этого мы объявляем переменную, значение которой будет возвращать функция GetButtonPressed. Наш скрипт работает даже в тот момент, когда игра вроде бы ждёт от игрока решения, и в этот самый момент происходит проверка, не нажимал ли игрок кнопок до этого. Команда “return” сообщает движку, что выполнение скрипта далее продолжать не надо.

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

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

А теперь взгляните на небольшое добавление:

If ( OnActivate == 1 )
If ( controlvar == 0 )
            MessageBox "Без голоса кричит, без крыльев летит, без зубов кусает, безо рта шепчет. Что это?", "Летучая мышь", "Старая женщина", "Ветер", "Призрак"
            Set controlvar to 1
elseif controlvar >= 1
            activate
endif
endif

Это значит, что когда бы наш сундук впоследствии не открывали, он откроется только, если контрольная переменная будет не меньше единицы. Соответственно, если игрок отвечает неверно, controlvar принимает значение –1, в результате чего он никогда не сможет открыть сундук. Но если он даёт верный ответ, controlvar принимает значение 2, следовательно, игрок сможет открыть сундук в любой момент, когда он пожелает. Сохраните и протестируйте ваш плагин.

Наша первая ошибка

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

if (controlvar == 1)
            set button to GetButtonPressed
            if ( button == -1 )
                        return
            elseif ( button == 2 )
                        MessageBox "Правильно!"
                        set controlvar to 2
            else
                        MessageBox "Неверно!"
                        set controlvar to -1
            endif
elseif ( controlvar == 2 )
            Activate
endif

Заметили, что я переместил команду, активации в блок “if – endif”, проверяющий, соответствует ли значение контрольной переменной 2? Это обеспечивает чёткую последовательность действий, что является одним из главных условий работоспособности скриптов Морровинда. Старайтесь избегать нагромождения всего и сразу! Проверьте плагин.
Отлично, сундук теперь открывается, как мы и хотели, однако, что случилось? Курсор перемещается очень медленно, и мы не можем закрыть окно инвентаря! Посмотрите на код: контрольной переменной присваивается 2, которая так там и остаётся – игра продолжает постоянно выполнять команду “Activate”, вот почему мы никак не можем закрыть сундук, он мгновенно открывается вновь. Так что измените соответствующую часть кода следующим образом:

elseif ( controlvar == 2 )
            Activate
            Set controlvar to 3
endif

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

Чего же не хватает? Мы забыли поставить на сундук ловушку!

Наложение заклинания на игрока

Ловушка должна срабатывать только, если игрок дал неверный ответ. Что же для этого надо?
В окне объектов перейдите на вкладку “Spellmaking”, и в контекстном меню выберите пункт “new”. В поле ID введите “Frost_Curse”, а в поле имени – “Frost Curse”. Задайте тип заклинания “curse” и ущерб, допустим, от 1 до 5. Всё должно выглядеть, как на картинке ниже:

Теперь нам надо как-то наложить это проклятье на игрока. Для этого мы будем использовать функцию AddSpell. Через некоторое время мы снимем заклинание с помощью функции RemoveSpell, поэтому нам нужно некое подобие секундомера. Исправьте Ваш скрипт:

Begin my_first_script

Short controlvar
Short button
Float timer

If ( OnActivate == 1 )
            If ( controlvar == 0 )
                        MessageBox "Без голоса кричит, без крыльев летит, без зубов кусает, безо рта шепчет. Что это?", "Летучая мышь", "Старая женщина", "Ветер", "Призрак"
                        Set controlvar to 1
            elseif ( controlvar > 1 )
                        activate
            endif
endif

if ( controlvar == 1 )
            set button to GetButtonPressed
            if ( button == -1 )
                        return
            elseif ( button == 2)
                        MessageBox "Правильно!"
                        Activate
                        set controlvar to 2
            else
                        MessageBox "Неверно!"
                        Player -> AddSpell, "Frost_Curse"
                        set controlvar to –1
            Endif
elseif ( controlvar == 2 )
            Activate
            Set controlvar to 3
elseif ( controlvar == -1 )
            Set timer to ( timer + GetSecondsPassed )
            if ( timer > 10 )
                        Player -> RemoveSpell, "Frost_Curse"
                        set controlvar to -2
            endif
endif

End

Итак. Player -> AddSpell, "Frost_Curse" накладывает на игрока проклятье, созданное нами немного раньше. Заметьте, что мы используем “Player ->”, чтобы заклинание воздействовало именно на игрока. Иначе мы проклянем сам сундук, ведь для скрипта он стоит объектом по умолчанию. Как Вы сами прекрасно понимаете, особого эффекта мы от этого не получим.

Float timer
Set timer to ( timer + GetSecondsPassed )
            if timer > 10

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

Player -> RemoveSpell, "Frost_Curse"
set controlvar to –2

Отлично, теперь сохраните и протестируйте плагин. Работает превосходно, не правда ли? Ну, почти… Попробуйте вот что: наложите на себя проклятье, откройте окно инвентаря и немного подождите. Через некоторое время эффект проклятья исчезает, не нанеся игроку никакого вреда. Так происходит, потому что скрипт в это время всё ещё работает, но эффекты заклинаний не подсчитываются, когда открыто какое-либо меню. Мы же не хотим, чтобы игроку удалось так просто улизнуть! Для этого нам необходимо поместить в наш скрипт нечто, что будет останавливать его выполнение, если игрок открыл меню. Специально для этого существует функция MenuMode, возвращающая 1, если открыто какое-либо окошко. Подобную проверку необходимо поставить в самое начало нашего скрипта:

If ( MenuMode == 1 )
            Return
Endif

Запомните, что return прекращает выполнение скрипта в данную долю секунды. Теперь наш первый скрипт полностью готов. Примите мои поздравления! Если Вы хотите немного поэкспериментировать с плагином, поместите наш сундук в какую-нибудь локацию и закройте его. Затем попробуйте открыть его непосредственно из кода скрипта (функция Unlock). Или добавьте звуковой проигрыш, если игрок дал верный ответ (например, PlaySound3D, "skillraise"). Можете также попробовать функцию Cast вместо AddSpell.

Итак, конечный вид нашего скрипта:

Begin my_first_script

Short controlvar
Short button
Float timer

If ( MenuMode == 1 )
            Return
Endif

If ( OnActivate == 1 )
            If ( controlvar == 0 )
                        MessageBox "Без голоса кричит, без крыльев летит, без зубов кусает, безо рта шепчет. Что это?", "Летучая мышь", "Старая женщина", "Ветер", "Призрак"
                        Set controlvar to 1
            elseif ( controlvar > 1 )
                        activate
            endif
endif

if ( controlvar == 1 )
            set button to GetButtonPressed
            if ( button == -1 )
                        return
            elseif ( button == 2 )
                        MessageBox "Правильно!"
                        Activate
                        set controlvar to 2
            else
                        MessageBox "Неверно!"
                        Player -> AddSpell, "Frost_Curse"
                        set controlvar to –1
            Endif
elseif ( controlvar == 2 )
            Activate
            Set controlvar to 3
elseif ( controlvar == -1 )
            Set timer to ( timer + GetSecondsPassed )
            if ( timer > 10 )
                        Player -> RemoveSpell, "Frost_Curse"
                        set controlvar to -2
            endif
endif

End