Создание миссии: шаг за шагом. Часть 1
Думаю, многие из вас хотели бы создать свои миссии. В голове куча идей, но как это реализовать? В данном цикле статей мы, шаг за шагом, попробуем написать новую миссию.
Но, прежде чем начать, необходимо выяснить ряд отдельных моментов. Не имеет смысла писать скрипты, если вы не понимаете логики их работы. Поэтому сначала разберемся, как же собственно они работают. Сразу отмечу, что скрипты мы будем разбирать и писать на основе файла stripped.txt, который поставляется вместе с Sanny Builder. Ищите его в папке {SB}DataSA.
Не нашли папку Data? Убедитесь, что вы используете свежайшую версию SB. На момент написания данной статьи таковой являлась версия 2.96
Теперь откройте данный файл и внимательно посмотрите на его содержимое.
В самом верху вы увидите несколько строк, начинающихся со слова DEFINE
DEFINE OBJECTS 0
DEFINE MISSIONS 0
DEFINE EXTERNAL_SCRIPTS -1 // Use -1 to not write AAA script
DEFINE UNKNOWN_EMPTY_SEGMENT 0
DEFINE UNKNOWN_THREADS_MEMORY 0
Это так называемый заголовок файла Main.SCM. Значения этих элементов напрямую отражают его структуру и содержимое отдельных сегментов. К описанию полей заголовка мы перейдем немного позже.
Ниже заголовка вы увидите несколько закомментированных строк. Напомню, что они игнорируются компилятором, не играют никакой роли и необходимы лишь для ориентации в скриптах.
// --------------------
// THE STRIPPED MAIN THREAD
// contains initial info
// --------------------
thread 'MAIN'
var
$PLAYER_CHAR: Player
end // var
01F0: set_max_wanted_level_to 6
set_wb_check_to 0
Сразу после комментариев следуют первые скриптовые команды. Именно с этих строк и начинается игра. Т.е. при запуске, когда игра подгружает файл main.scm, движок игры начинает последовательно, строка за строкой выполнять скриптовые команды.
Текущую активную команду потока, которая выполняется в данный момент, я бы назвал позицией невидимого курсора в данном потоке, по аналогии с текстовым редактором.
После загрузки заголовка, первой командой, которую он выполнит, будет thread 'MAIN'. И далее игра будет проходить все строки по порядку. Исключением станет ситуация, когда игра встретит переход (условный или безусловный). Например:
$var = 0
jump @label
$var += 1
:label
$var -= 1
Допустим, выполнение скриптов привело игру к команде $var = 0. Следующей командой будет jump @label, а это значит, что игра пропустит все последующие строки и перейдет сразу на метку @label. Это называется безусловный переход. Таким образом, после этого игра выполнит команду $var -= 1. Значение переменной $var станет -1.
Существуют еще другие виды переходов, о них мы будет говорить отдельно. Сейчас важно понять логику работы скриптов.
Вы можете задать вопрос, а как в игре работают сразу множество отдельных игровых блоков: одновременно работают парикмахерские, залы, можно запустить несколько разных миссий и т.д? Неужели игра обрабатывает эти скрипты последовательно друг за другом? Конечно же нет. Такая многозадачность обеспечивается тем, что движок игры способен выполнять параллельно несколько одновременно запущенных потоков (см. Вопрос #17 в MB FAQ). При этом каждый из этих потоков будет выполняться независимо от других. Значения локальных переменных одного потока будут недоступны в другом.
Потоки можно сравнить с двумя (тремя..) одновременно запущенными проигрывателями. Представьте, что у вас запущено два WinAmp'а, в каждом из которых играет песня. Песни в данном случае играются параллельно, независимо друг от друга. Так же работают и потоки.
Потоки запускаются командой create_thread. Это еще один вид безусловного перехода. Однако его отличие от jump состоит в том, что игра продолжает выполнять последующие команды. Поэтому довольно часто встречается такая ошибка:
create_thread @CreateCar
:CreateCar
end_thread
После того как игра создала поток CreateCar, появится еще один невидимый "курсор", установленный на позицию метки CreateCar. При этом первый поток будет продолжать последовательно выполняться (потоки же работают параллельно). Получится такая ситуация, когда на одну и ту же команду придут два потока одновременно. В данном примере дважды будет выполняться блок создания машины. Вот почему часто встречаются сообщения неопытных скриптеров о проблеме двух создаваемых актеров, машин.
Чтобы не допустить такой ситуации, достаточно вставить безусловный переход на метку, которая будет лежать за пределами нового потока:
create_thread @CreateCar
jump @SkipThread
:CreateCar
end_thread
:SkipThread
Если первый поток вам больше не нужен, его можно совсем завершить:
create_thread @CreateCar
end_thread
:CreateCar
end_thread
Команда end_thread моментально останавливает выполнение потока. Игра забывает позицию "курсора" в этом потоке. Однако если он вам понадобится, можно заново запустить его:
:Thread_1
create_thread @CreateCar
end_thread
:CreateCar
wait 10000
create_thread @Thread_1
end_thread
Надеюсь, вы понимаете логику работы данного скрипта. Игра пришла на метку Thread_1 и выполнила команду create_thread @CreateCar. Теперь в памяти игры будет два одновременно работающих потока (Thread_1 и CreateCar).
Первый после это завершится командой end_thread, а второй продолжит работу. В нем будет создана машина, после чего игра выполнит команду wait 10000. После задержки в 10 секунд, игра опять создаст новый (старый :)) поток Thread_1, а второй поток завершится.
Таким образом в игре каждые 10 секунд будет создаваться машина.
Потоки можно создавать не только с первой метки. Иногда существует необходимость запуска с определенной позиции:
...
:Thread_1
end_thread
:CreateCar
:CreateActor
jump @Thread_1
...
...
create_thread @CreateCar
...
create_thread @CreateActor
Когда игра придет на команду end_thread, она завершит какой-то вышележащий поток. Машина и актер, естественно, создаваться не будут. Однако мы может создавать их запуском "субпотока". После create_thread @CreateCar игра создаст и машину и актера. Сам поток после перехода завершится.
Почему? - спросите вы, - ведь команда завершения лежит за пределами потока (выше). После перехода jump @Thread_1 позиция "курсора" (т.е. текущая выполняемая команда в потоке) переместится на команду end_thread. Игра завершит именно тот поток, который дошел сюда. Это означает, что в скриптах может быть всего одна-единственная команда end_thread. Остальные потоки могут заканчиваться переходом на нее.
Запуском "субпотока" @CreateActor можно добиться того, что будет создан только актер, без машины.
Итак, вернемся к нашему файлу. Как видите, в нем нет команд create_thread. Значит будет запущен только один поток, автоматически создаваемый в начале новой игры. В конце файла вы увидите команду завершения. Это значит, что после нее в игре не будет запущено вообще ни одного потока, т.е. выполнение файла main.scm прекратится. Такое в принципе позволительно для урезанного майна.
Говоря о запущенных потоках, стоит сказать, что их текущее состояние запоминается после сохранения. Т.е. при загрузке старой игры, все потоки продолжат выполнение с того момента, где они были в момент сохранения. А это значит, что первые команды майна (thread 'MAIN' и далее) выполняются лишь единожды, при запуске новой игры. Именно поэтому в начале помещаются все одноразовые действия, такие как создание игрока, указание стартовых значений переменных и т.п. После этого первый поток либо переходит в бесконечный цикл (например проверка текущего процента прохождения), либо завершается (как в указанном файле).
Между командами thread 'MAIN' и end_thread в stripped.txt написаны опкоды, смысл которых, думаю, должен быть вам ясен. Их назначение состоит в том, чтобы создать в игре жизненно необходимые объекты и установить важные стартовые значения.
Таково поле наших будущих действий. В следующей статье мы рассмотрим понятие триггера и макета миссии.
Автор: Seemann
Внимание! Данная статья была взята с информационного ресурса www.gtacoding.nm.ru (с)
1362 Прочтений • [Создание миссии: шаг за шагом. Часть 1] [13.05.2012] [Комментариев: 0]