Как работают скрипты


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

Modding

Синяя стрелка, это указатель на текущий скрипт, из которого берется очередная команда. Зеленые метки указывают на текущие команды, готовые в исполнению в каждом скрипте и ожидающие своей очереди. А что означают красные флажки? Не догадываетесь? Программисты наверняка сообразили, а остальным (ведь для них я и пишу эту статью) поясню, это флаг готовности скрипта к выполнению, он показывает нужно ли выполнять конкретный скрипт в этом цикле или пропустить его. Я говорил, что супервайзер, выполнив команду одного скрипта, переходит к следующему, это верно. Но, перейдя к следующему скрипту супервайзер первым делом проверяет флаг готовности и, если этот флаг не установлен, то скрипт пропускается и супервайзер переходит к следующему скрипту и так далее. На самом деле, это может показаться сложным, хотя ничего сложного в этом нет и чтобы писать хорошие скрипты нужно четко в этом разобраться. Вот простой пример, все, кто пробовал разбираться в работе скриптов или писать что-то свое, встречали команду wait [кол-во миллисекунд], она означает, что скрипт должен подождать (т.е. стать неактивным) указанное время. Так вот, эта команда просто снимает флаг готовности с этого скрипта на заданное время. Как только время истечет, флаг готовности появится, НО скрипт (вернее команда скрипта следующая за командой wait) не сразу начнет выполняться, а только тогда, когда до этого скрипта дойдет указатель (синяя стрелка на схеме). То есть, команда wait будет иметь небольшую задержку, обычно в пределах нескольких миллисекунд.

Однако всё-таки удивительно неорганизованный народ эти актёры! То реплики свои забудут, то начнут галдеть хором, перебивая друг друга, так что ничего не разберешь, то перестанут двигаться, или наоборот, двигаются не туда куда нужно. Глаз да глаз за ними нужен! Если мы продолжим нашу аналогию с театром, то можем заметить, что в театре есть человек, который непосредственно управляет актерами, так сказать, в реальном времени, прямо во время спектакля. Это суфлер! Он и реплики подскажет, и укажет, куда и как должен двигаться актер, и вовремя напомнит актеру, что пора закругляться и уходить со сцены. Есть такой "суфлер" и в нашей Мафии, назовем его условно скрипт-супервайзер (script-supervisor) или надсмотрщик за скриптами, если по-русски. Фактически, это подсистема движка игры, которая отвечает за правильное, корректное выполнение скриптов всех актеров занятых в текущей сцене.

Давайте немного разберемся, как работают скрипты и как ведут себя наши актеры. Во время игры нам кажется, что несколько актеров исполняют свои роли одновременно, например, водитель ведет машину, пассажиры поливают встречных копов из автоматов, копы отстреливаются, не забывая при этом дуть в свистки, по улицам хотят пешеходы, ездят машины и т.д. Вот сколько актеров задействовано. Но, тут есть очень существенный нюанс! Дело в том, что в компьютерных операционных системах типа Windows не существует реальной многозадачности – в каждый момент времени выполняется только одна задача, иными словами действует только один актер. То, что несколько актеров действуют одновременно, нам только кажется! Нужно запомнить и четко понимать, что многозадачность в Windows, и, как следствие, во всех виндовс-приложениях виртуальная. Существует много принципиально разных способов реализации многозадачности, например, по времени, по готовности или по запросу, по прерываниям и т.д. Не буду сейчас все эти способы рассматривать, тем более, что в Мафии многозадачность для скриптов реализована все-равно иначе :)

В движке Mafia реализовано покомандное выполнение скриптов. В каждый момент времени выполняется только одна команда и только для одного актера! В следующий момент выполняется команда для другого актера и так далее. Представьте, если говорить очень упрощенно, что у нас есть три актера – один шагает по тротуару, второй стреляет из автомата, третий ведет машину. Тогда эти три скрипта будут выполняться таким образом – первый человек делает шаг, второй выстрел, третий проезжает несколько сантиметров, управление передается первому актеру, и он делает еще шаг, второй актер делает еще выстрел, третий проезжает еще немного и т.д. по кругу. Если мы будем смотреть на этот спектакль, то нам покажется, что первый актер непрерывно шагает, второй без остановки стреляет, а третий плавно ведет машину, хотя в определенный момент времени активен только один актер, а другие его ждут. Это очень важный момент! Его нужно понять! Ведь тогда получается, что актер не будет продолжать свою роль, пока не отыграют свои реплики (команды) другие актеры, занятые в сцене. А если у нас тысяча актеров? Кстати, это вполне реальное число актеров в игровой сцене! Не будет ли действие каждого из них слишком заторможено ожиданием своей очереди? Тут-то нам и поможет наш супервайзер, одна из основных задач которого как раз и состоит в оптимизации работы скриптов.

Как же работает супервайзер скриптов? Выглядит это примерно следующим образом, после загрузки скриптов (если мы загружаем миссию с нуля, а не сохраненку!) супервайзер берет первую команду первого скрипта и передает ее обработчику команд. Обратите внимание, супервайзер не обрабатывает команды сам, он только строит последовательность из команд для обработчика! Передав первую команду, супервайзер запоминает, что следующая команда для выполнения в первом скрипте будет вторая, для этого он помечает ее специальной меткой, назовем её указателем. А сам переходит к первой команде второго скрипта и т.д. до обработки последнего скрипта в сцене. После этого супервайзер переходит опять к первому скрипту, только уже ко второй команде, и все повторяется со сдвигом указателей и, естественно, с запоминанием их положения. Таким образом, всегда активной, т.е. выполняемой в данный момент, является только одна команда из всех скриптов сцены, а остальные ждут своей очереди.

Выше дан пример про трех актеров: шагающего, стреляющего и ведущего машину. Пример, как я говорил, был немного упрощенный, только для понимания работы скриптов, на самом деле все работает немного сложнее :) Скриптовые команды могут вызывать как элементарные, простые (например, логические) действия, так и заставлять актеров выполнять какие-то сложные, законченные, растянутые во времени действия. Давайте посмотрим на несколько команд для примера.

findactor
let flt[0] = 10
enemy_changeanim
enemy_vidim

Эти команды выполняются практически мгновенно (несколько микросекунд, в зависимости от производительности компьютера) и не сбрасывают флаг готовности, следующая из ними команда будет выполняться в следующем цикле выполнения скриптов. Условно пометим такие команды зеленым цветом.

enemy_usecar
enemy_car_moveto
enemy_move_to_frame

Эти команды (правильнее называть их функциями) требуют времени на исполнение: первая ждет пока человек не сядет в машину, вторая – пока машина не доедет до указанного места, третья – пока актер не дойдет до пункта назначения пешком. На время выполнения указанных действий (т.е. пока не сел, не доехал, не дошел) флаг готовности со скрипта снимается, т.е. никаких команд из скрипта этого актера выполняться не будет! Фактически актер перестанет реагировать на все игровые события! А представьте, что получится, если актер не может сесть в машину, допустим, что дверь заблокирована другой машиной, или не может доехать до цели, т.к. попал в пробку, что тогда? Тогда актер будет тупо бегать вокруг машины или гудеть в пробке, пытаясь растолкать бампером другие машины. И как долго они будут этим заниматься? Часами? К счастью, движок Мафии гораздо "умнее" чем многие думают :) Проанализировав ситуацию, движок через какое-то время даст команду актерам прекратить бесполезные старания. Например, водитель, попавший в пробку, может вылезти из машины и продолжить свой путь пешком и т.п. В общем, нужно быть очень осторожным, использую эти команды, поэтому условно помечаю их красным цветом.

enemy_playanim
enemy_talk

Эти команды не снимают флаг готовности, но, по своей сути, требуют времени на исполнение! Первая команда заставляет актера выполнять какую-то анимацию, делать какие-то жесты или просто замереть в определенной позе. По второй команде актер должен произнести какую-то заданную фразу. Если не дать дополнительного времени на анимацию или фразу, то выглядеть будет довольно забавно, но не всегда так как требуется. Допустим, что за командой enemy_talk сразу следует команда enemy_move_to_frame, тогда актер начнет от нас (от Томми) уходить, бубня что-то нечленораздельное себе под нос. Нам этого, скорее всего, не нужно. А чтобы актер сказал фразу Тому, а потом удалился нужно вставить после enemy_talk команду wait со временем задержки равным или немного больше времени длины фразы. То есть, получается, что мы командой wait принудительно сбрасываем флаг готовности, т.к. команда enemy_talk сама этого делать не умеет. На самом деле, можно рассматривать такое ограничение не как минус, а как плюс! Ведь мы можем более гибко использовать готовые анимации и фразы, "нарезая" из них нужные куски и комбинируя. Эту группу команд помечаю желтым цветом, т.к. серьезных багов она не вызовет, однако работать с ней нужно все-таки аккуратно.

Естественно, тут не рассматриваются все скриптовые команды из Мафии, это было бы просто невозможно в рамках популярной статьи, но все команды, которые мы используем, относятся к одной из вышеназванных групп: к зеленой, красной или желтой. Вы должны научиться понимать это и четко классифицировать используемые вами команды. Если поняли логику, по которой супервайзер расставляет флаги готовности, то наверняка уже поняли и за счет чего происходит оптимизация скорости работы всей игровой сцены – из нее просто временно исключаются ненужные в данном цикле скрипты актеров. В результате, доступ к каждому активному актеру происходит значительно быстрее. Есть еще несколько "фокусов", которые супервайзер выполняет с флажками, это тоже весьма важный момент! Вспомните, когда мы движемся по Lost Heaven`у, то нам кажется, что весь город живой, везде ездят машины и ходят люди, это то, что мы называем городским трафиком. Однако, как совершенно справедливо рассудили разработчики Mafia, зачем трафик пускать там, где Томми (или игрок, как угодно) видеть его не способен! Например, Том находится в Маленькой Италии, зачем же машинам ездить в Хобокене? Их ведь там все-равно никто не увидит! Только никчемная затрата компьютерных ресурсов. В результате решили сделать так (и это правильно!), что трафик присутствует и движется только вокруг камеры с помощью которой мы видим Lost Heaven. Камера является как бы центром круга с радиусом примерно 300-400 метров, в котором и происходит вся городская жизнь :) Этот "круг" сопровождает Тома, куда бы тот ни двигался, так как камера "привязана" к нему, вот нам и кажется, что все районы города живые, а на самом деле, если там нет Томми, то нет и жизни :) Вы наверняка видели в игре как машина или пешеход внезапно исчезают или наоборот появляются из неоткуда, это значит, что вам удалось приблизиться к краю круга, значит супервайзер за вами не уследил :)

Важный момент! Если актер-человек убит, то есть его здоровье стало равно нулю, то скрипт этого актера исключается из последовательности обрабатываемых скриптов и восстановить его нельзя!

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

Несколько примеров таких команд ниже.

end — удаляет скрипт из последовательности обрабатываемых скриптов, вернуть скрипт обратно нельзя(!!!), для актеров-людей выполнение такой команды приведет к тому, что человек превратится в "привидение" — замрет в той позе, в которой его застало выполнение команды и станет "прозрачным". Применяйте эту команду с большой осторожностью, только для актеров, которые уже вам не нужны, и, желательно, выведены за пределы игровой сцены. А лучше вообще не применяйте

act_setstate xx, active|inactive — соответственно, устанавливает или снимает флаг готовности, скрипт актера будет или не будет обрабатываться. Установить снятый этой командой флаг готовности можно с помощью других команд.

enemy_forcescript nn — принудительно выставляет флаг готовности! Действует, даже если актер в данный момент выполняет одну из сложных функций, например, ведет машину и т.п.

commandblock 1 — принуждает супервайзер выполнять подряд несколько команд из скрипта одного актёра, не переходя к следующему актёру (т.е. без сдвига синей стрелки, см. рисунок) до момента получения отменяющей блок команды commandblock 0.

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

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

— MikeMouse


 Даём игроку динамит
 Портрет Поли
 Из ленты новостей за 1930-й год
 Как делать скриншоты в игре Mafia?

Войдите, чтобы комментировать