|
"Aurora
Toolset": Анимация NPC
ЧАСТЬ I
АНИМАЦИЯ NPC
- ПРЕДИСЛОВИЕ
Здравствуйте!
Если Вы добрались и до этого чтива, значит Вам интересно попробовать
себя в анимации пресонажей. Позволим себе опустить подробности,
которые можно и так прочитать в Lexicon'e и попробуем изложить некоторый
накопленный опыт, касающийся АНИМАЦИИ NPC.
Все, что здесь изложено и будет изложено в дальнейшем, было написано
и протестировано в процессе работы в составе команды WRG!
над проектом "Проклятие Левора".
Если Вы вполне владеете этой темой - возможно Вы и не найдете здесь
для себя ничего нового, но если что-то Вам покажется интересным,
мы будем рады.
Если Вы обнаружите какие-либо неточности или Ваше мнение в чем либо
расходится с данным материалом, напишите нам на poplar85@yandex.ru
- и мы с интересом Вас выслушаем.
С уважением, Lex & LexxL.
... Данное исследование
не претендует на полноту - это скорее анализ собственного скудного
опыта. Все вновь вводимые термины остаются на совести авторов ...
Для начала все же договоримся о терминах, которые мы
будем использовать и которые не присутствуют в Lexicon и
Aurora Toolset:
Параметры NPC -- набор переменных,
описывающих текущее состояние NPC
Сценарий -- некоторая логически
завершенная последовательность действий (Actions)
Сюжет -- некоторый связный
набор сценариев
Замкнутый NPC -- NPC, параметры
которого изменяются внутри его собственных скриптов.
Открытый NPC -- NPC, параметры
которого могут изменяются как внутри его собственных скриптов, так
и извне.
Здесь вы сможете скачать вспомогательную
утилиту COA 1.30,
написанную нами для подробного изучения анимации и составления анимационных
роликов.
АНИМАЦИЯ NPC -
ВСТУПЛЕНИЕ
Как Вы уже успели заметить, относительно нормально функционирующими
персонажами в модах обычно являются хенчмены (спутники игрока).
Прочие сюжетные персонажи как правило имеют только диалоги и лишь
изредка выполняют какие-либо действия.
Второстепенные (фоновые) жители локаций либо стоят столбами, либо
анимированы при помощи стандартных средств ToolSet: (MOBILE_AMBIENT_ANIMATION,
IMMOBILE_AMBIENT_ANIMATION).
Возникло желание не столько "анимировать" персонажи, сколько "оживить"
их, т.е. придать их деятельности некоторую видимость осмысленной.
При использовании стандартных скриптов персонажи в основном реагируют
друг на друга только когда воюют.
Естественно, появляется желание "отскриптовать" всех NPC, находящихся
в локации, так, чтобы их поведение было бы, по крайней мере, поверхностно
осмысленным.
Как показала практика, это весьма непросто сделать не перегрузив
машину. Даже при использовании стандартных скриптов 30-40 анимированных
персонажей в локации могут заметно перегрузить компьютер. Что уж
говорить о тех вариантах, когда их поведение хотелось бы сделать
нестандартным.
Попробуем выделить основные вопросы, связанные с анимацией персонажей.
АНИМАЦИЯ NPC -
ЧАСТЬ I
Все обычно начинается с трех вопросов:
1. ЧЕГО ЖЕ МЫ ХОТИМ? - что же Вы собственно ждете от себя и хотите
от NPC.
2. СРЕДСТВА - что и как использовать при написании скриптов.
3. КАК ОРГАНИЗОВАТЬ? - каким образом донести Ваши желания и умения
до NPC
1. ЧЕГО ЖЕ МЫ ХОТИМ?
При написании собственного модуля возникает желание анимировать
в первую очередь сюжетных персонажей.
Например, Ваш герой приходит в кузницу и видит стоящего столбом
кузнеца, который замер в ожидании Игрока - в лучшем случае он пододвинется,
если вы попытаетесь пройти сквозь него. Да и при диалоге не сделает
ни движения.
Почему бы не заставить его немного пошевелиться? - Пусть он постоянно
что-то делает - кует, лазает в ящики с инструментами и серьем, смотрит
чертежи, пьет что-нибуть, в конце концов. Да и при разговоре с вами
пусть немного пожестикулирует (особенно своим молотом у вас под
носом).
Создание подобных сценариев достаточно несложно
и они будут содержать последовательность необходимых перемещений
от объекта к объекту, проигрывание соответствующих анимации самого
NPC и Placeables. Это будет Замкнутый
NPC.
Но нам мало! Мы хотим, чтобы он реагировал на наши просьбы. Например
если мы попросим его починить броню, он должен осмотреть ее, что-то
проворчать и приступить к работе. Т.е. он выполнит это только когда
Вы (или NPC) его об этом попросит. Это уже
труднее. Тут придется повозиться, так как условия, по которым он
выбирает сценарий меняются игроком (или NPC). Такой кузнец уже является
Открытым NPC.
ПОЭТОМУ ВАЖНО правильно расставить акценты
- что же Вы хотите заставить делать тех или иных Ваших NPC, насколько
сложным должно быть их поведение, как они должны (или не должны)
взаимодействовать с окружающим миром (в том числе и с прочими NPC).
Расписав для себя это в начале, Вы снимете массу
проблем при дальнейшем программировании.
В разделе КАК ОРГАНИЗОВАТЬ? мы попытаемся разобрать модели управления
NPC - от самой простой, до достаточно сложной.
2. СРЕДСТВА
2.1 НЕОБХОДИМЫЕ
ФУНКЦИИ.
Для построения сценариев Вам в основном понадобятся
следующие функции:
...перемещения:
ActionMoveToObject(object,int(mode),float(distance));
...подойти к объекту object, mode=TRUE-бежать/FALSE-идти, distance
- дистанция на которую надо подойти в футах.
ActionForceMoveToObject(object,int(mode),float(distance),float(time));
...во что бы то ни было подойти к объекту object, mode=TRUE-бежать/FALSE-идти,
distance - дистанция на которую надо подойти в футах, time -время,
которое Вы отводите на нормальный подход (если NPC по каким то причинам
не укладывается в диапазон времени, происходит Jump к этому самому
объекту).
ActionMoveAwayFromObject(int(mode),float(distance));
...отойти от объекта object, mode=TRUE-бежать/FALSE-идти, distance
- дистанция на которую надо отойти в футах.
ActionRandomWalk(); - бродить по случайному маршруту
...проигрывания анимации:
ActionPlayAnimation(ANIMATION_*,float(speed),float(time));
Проиграть анимацию ANIMATION_* со скоростью speed и временем проигрывания
time
тип Action -> становится в очередь действий (*).
PlayAnimation(ANIMATION_*,float(speed),float(time));
то же самое, что и предыдущая, тип void- > не становится в очередь
действий
(*)
- очередная сказка BioWare, поскольку в отношениях этой функции
с очередью действий больше исключений, чем правил.
..."строка текста над головой"
ActionSpeakString(); - строка текста над
головой объекта (становится в очередь действий)
SpeakString(); - строка текста над головой
объекта (не становится в очередь действий)
...имитация кастования спелла
ActionCastFakeSpellAtObject();
ActionCastFakeSpellAtLocation();
а также...
ActionOpenDoor(); - открыть дверь
ActionCloseDoor(); - закрыть дверь
SetCommandable(); разрещить/запретить
модификацию стека акций, иными словами, разрешить/запретить добавление
команд в очередь
ClearAllActions(); - очистить очередь
команд
ActionWait(x.x); - ожидать x.x сек
ActionDoCommand(); - выполнить команду
(Void a не Action) - способ поставить НЕ Action команду в очередь;
AssignCommand();- присвоить команду объекту
(любому другому, как частный случай - самому себе)
DelayCommand();- задержать выполнение
команды на float секунд
SetLocalInt(Float,String,Object,Location);
- задать значение локальной переменной с заданным именем для заданного
объекта выбранного типа
GetLocalInt(Float,String,Object,Location);
- получить значение локальной переменной с заданным именем для заданного
объекта выбранного типа
+ конструкции
if-else
switch-case
+ еще множество всяких функций, которые Вам придется использовать,
чтобы разнообразить поведение объекта.
2.2 ОЧЕРЕДЬ ДЕЙСТВИЙ.
Основа любого сценария - последовательное выполнение команд.
Все команды типа Action можно поставить в очередь-стэк и в этом
случае они будут выполняться друг за другом по протоколу FIFO =
FirstInputFirstOutput.
Формирование очереди происходит при запуске скрипта:
например, цепочка команд
...
ActionMoveToObject(object,...);
ActionPlayAnimation(ANIMATION_*,...);
ActionSpeakString("Я делаю это");
...
будет выполняться именно в той последовательности, как она
записана
Если же Вы хотите поставить в очередь FUNCTION типа void, это можно
сделать через ActionDoCommand(FUNCTION);
например следующие строки равнозначны
ActionSpeakString("Я делаю это");
ActionDoCommand(SpeakString("Я делаю это"));
и та и другая команда будут поставлены в очередь.
Есть и еще один важный момент - в очередь NPC (или любого другого
объекта) могут быть добавлены действия для другого объекта (или
NPC) Это играет очень большую роль, если у вас NPC взаимодействует
с объектом. Простой пример - кузнец-NPC открывает ящик. Т.е. кузнец
подходит, протягивает руку (чтобы ящик не сам открылся, а как бы
кузнец его открыл), и ящик открывается. Дело в том, что ящик нужно
открыть после того, как кузнец протянул руку, т.е. открытие ящика
(это проигрывание анимации открытия) должно встать в очередь действий
Кузнеца.
ActionPlayAnimation(ANIMATION_LOOPING_GET_MID,0.7,0.5);
ActionDoCommand(AssignCommand(oBox,ActionPlayAnimation(ANIMATION_PLACEABLE_OPEN)));
Конструкция ActionDoCommand(AssignCommand(...));
ставит анимацию открытия ящика (это действие производит САМ ЯЩИК)
в очередь Кузнеца. Если это же записать без ActionDoCommand(..),
то ящик откроется сразу же, как только будет запущена часть скрипта,
содержащая команду открытия ящика. ...пока все....................
2.3 АНИМАЦИОННЫЕ КОНСТАНТЫ.
АНИМАЦИОННАЯ КОНСТАНТА
|
int
|
Описание действия
|
ANIMATION_LOOPING_PAUSE |
0
|
NPC стоит |
ANIMATION_LOOPING_PAUSE2 |
1
|
NPC стоит |
ANIMATION_LOOPING_LISTEN |
2
|
NPC слушает |
ANIMATION_LOOPING_MEDITATE |
3
|
NPC встает на колени и складывает
руки перед собой |
ANIMATION_LOOPING_WORSHIP |
4
|
NPC встает на колени и кланяется |
ANIMATION_LOOPING_LOOK_FAR |
5
|
NPC прикладывает руку ко
лбу, имитируя взгляд в даль |
ANIMATION_LOOPING_SIT_CHAIR |
6
|
NPC имитирует сидение на
стуле. На самом деле он делает шаг вперед и садится на воздух
над тем местом, где секунду назад были его ноги |
ANIMATION_LOOPING_SIT_CROSS |
7
|
NPC садится на пол скрестив
ноги |
ANIMATION_LOOPING_TALK_NORMAL |
8
|
NPC имитирует нормальный
разговор |
ANIMATION_LOOPING_TALK_PLEADING |
9
|
NPC умоляюще протягивает
руки. |
ANIMATION_LOOPING_TALK_FORCEFUL |
10
|
NPC убеждающе машет руками. |
ANIMATION_LOOPING_TALK_LAUGHING |
11
|
NPC смеется. |
ANIMATION_LOOPING_GET_LOW |
12
|
NPC приседает на корточки
и копошит руками на уровне пола. |
ANIMATION_LOOPING_GET_MID |
13
|
NPC протягивает руку, как-бы
поворачивая дверную ручку. |
ANIMATION_LOOPING_PAUSE_TIRED |
14
|
NPC потягивает спину, немного
заломив за нее руки |
ANIMATION_LOOPING_PAUSE_DRUNK |
15
|
NPC пьяно покачивается. |
ANIMATION_LOOPING_DEAD_FRONT |
16
|
NPC сначала падает на колени,
а потом распластывается по полу. |
ANIMATION_FIREFORGET_HEAD_TURN_LEFT |
100
|
NPC поворачивает голову влево. |
ANIMATION_FIREFORGET_HEAD_TURN_RIGHT |
101
|
NPC поворачивает голову вправо. |
ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD |
102
|
NPC чешет голову. |
ANIMATION_FIREFORGET_PAUSE_BORED |
103
|
NPC слегка покачивается,
как-бы под тяжестью ноши или от усталости.. |
ANIMATION_FIREFORGET_SALUTE |
104
|
NPC отдает честь. |
ANIMATION_FIREFORGET_BOW |
105
|
NPC кланяется в пояс. |
ANIMATION_FIREFORGET_STEAL |
106
|
NPC имитирует воровские движения. |
ANIMATION_FIREFORGET_GREETING |
107
|
NPC приветственно машет рукой. |
ANIMATION_FIREFORGET_TAUNT |
108
|
NPC насмехается. |
ANIMATION_FIREFORGET_VICTORY1 |
109
|
NPC радуется. |
ANIMATION_FIREFORGET_VICTORY2 |
110
|
NPC радуется. |
ANIMATION_FIREFORGET_VICTORY3 |
111
|
NPC радуется. |
ANIMATION_FIREFORGET_READ |
112
|
NPC достает сивток и читает
его. |
ANIMATION_FIREFORGET_DRINK |
113
|
NPC достает бутылку и пьет. |
unknown |
114
|
unknown |
unknown |
115
|
unknown |
unknown |
116
|
unknown |
ANIMATION_PLACEABLE_ACTIVATE |
200
|
Анимация активации placeable
(пламя, канделябр, фонарный столб, факел, итд.) |
ANIMATION_PLACEABLE_DEACTIVATE |
201
|
Анимация деактивации placeable
(пламя, канделябр, фонарный столб, факел, итд.) |
ANIMATION_PLACEABLE_OPEN |
202
|
Анимация открывания placeable
(сундук, ящик, шкаф с дверями, итд.) |
ANIMATION_PLACEABLE_CLOSE |
203
|
Анимация закрывания placeable
(сундук, ящик, шкаф с дверями, итд.) |
Немного подробнее о самих базовых анимационных роликах, которые
"скрываются" за анимационными константами.
В таблице и так уже все переведено на русский, поэтому остановимся
на различиях FIREFORGET и LOOPING.
При проигрывании анимации движок Aurora оперирует следующими параметрами:
где нам доступны первые три параметра - ANIMATION(собственно анимационная константа), PLAYBACK_SPEED - скорость проигрывания, LENGTH - время проигрывания,
а DESIRED_LENGTH (желательное время)
определено движком и недоступно.
Вообще стоит различать _FIREFORGET_ и
_LOOPING_ анимации.
2.3.1 АНИМАЦИОННЫЕ КОНСТАНТЫ ТИПА
_FIREFORGET_.
_FIREFORGET_ - это одиночные анимации,
т.е. NPC выполняет последовательность движениий один раз, например
ANIMATION_FIREFORGET_STEAL заставит NPC
сделать последовательность движений "воровства" ТОЛЬКО один раз.
диаг.FIREFORGET 1 T_BEG - стартовый фрагмент, T_INTERNAL - внутреняя часть, T_END - завершающий фрагмент
Примерная временная схема анимации типа FIREFORGET при скорости
= 1.0f и общем времени LENGTH <= DESIRED_LENGTH.
Единственное, чем она может отличаться от анимации по умолчанию
- если LENGTH много меньше DESIRED_LENGTH - основной ролик INTERNAL
будет обрезан, а начальный и конечный ролики - ускорены. Aurora
как бы пытается втиснуть анимацию в отведенное время.
ВНИМАНИЕ!!!
Для констант _FIREFORGET_* без вспомогательного
предмета (свиток, бутылка) регулируется ТОЛЬКО скорость PLAYBACK
SPEED, при этом, какое бы Вы не выставили время LENGTH, время проигрывания
определено движком и равно DESIRED_LENGTH.
Например:
...
ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING,1.0f,10.0f);
ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD,1.0f,10.0f);
...
ничем не отличаются при проигрывании от:
...
ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING,1.0f);
ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD,1.0f);
...
Причем, для большинства _FIREFORGET_*
эффект изменения скорости PLAYBACK SPEED заметен при увеличении
> 1.0f
Для констант _FIREFORGET_* с вспомогательным
предметом (свиток, бутылка) время регулируется, НО! это время не
влияет на реальное время проигрывания анимации, время LENGTH как
бы "резервируется" движком для этой анимациию. Т.е следующая за
ней в очереди анимация начнется по истечении LENGTH. Например:
...
ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK,1.0f,10.0f);
ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD,1.0f);
...
заметно отличаются при проигрывании от:
...
ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK,1.0f);
ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD,1.0f);
...
тем, что после анимации питья NPC еще (10.0f - DESIRED_LENGTH(для
_DRINK)) сек просто стоит.
2.3.1 ОСОБЕННОСТИ _FIREFORGET_DRINK
и _FIREFORGET_READ.
Рассмотрим случай, когда Вы указали не только скорость, но и время
при проигрывании ANIMATION_FIREFORGET_DRINK
или ANIMATION_FIREFORGET_READ .
При этом если скорость равна 1.0f, то
все будет проигрываться нормально практически при любом указанном
времени. Забавность в том, что время визуализации
T VIS вспомогательного объекта (бутылка, свиток) рассчитана
для нормальной скорости 1.0f
диаг.FIREFORGET 2
Примерная временная схема анимации типа _FIREFORGET* при скорости
PLAYBACK_SPEED < 1.0f и общем времени LENGTH > DESIRED_LENGTH
Иначе говоря, бутылка или свиток могут остаться в руке NPC еще некоторое
время при скорости < 1.0f Например,
при
ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK,0.1f,15.0f);
NPC c нормальной скоростью глотнет из бутылки (но никак НЕ медленно!)
и опустит руку с бутылкой вниз. Бутылка исчезнет из руки только
по окончанию 15.0f .
диаг.FIREFORGET 3
Примерная временная схема анимации типа _FIREFORGET_* при скорости
PLAYBACK_SPEED > 1.0f и общем времени LENGTH > DESIRED_LENGTH
Иначе говоря, бутылка или свиток могут испариться из руки NPC много
раньше, чем должны были бы при скорости = 1.0f.
Например, NPC может и не успеть заглотнуть чего-либо из
бутылки, поскольку она норовит исчезнуть из руки в самый неподходящий
момент при скорости = 3.0f .
ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK,3.0f,15.0f);
Примерная зависимость времени визуализации дополнительного объекта:
T_VIS = T_NORM / PLAYBACK_SPEED
2.3.2 АНИМАЦИОННЫЕ КОНСТАНТЫ ТИПА _LOOPING_.
_LOOPING_ - многократное повторение
последовательности движений = начальный ролик + (основной ролик)*N
+ конечный ролик.
ANIMATION_LOOPING_GET_LOW заставляет NPC
присесть на корточки и указанное время (на самом деле равное LENGTH
- T_BEG - T_END) шевелить руками.
Причем скорость и время вполне наглядно меняют характер проигрывания
анимации.
диаг.LOOPING 1 Примерная временная схема анимации типа LOOPING
НАЛОЖЕНИЕ АНИМАЦИЙ
ПРИ ПОМОЩИ ОЧЕРЕДИ ДЕЙСТВИЙ.
( только для ActionPlayAnimation(); )
Накладывать можно только _FIREFORGET_* на _LOOPING_*.
Рассмотрим простой пример:
...
ActionPlayAnimation(ANIMATION_LOOPING_MEDITATE,1.0,10.0f);
ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK,1.0,7.0);
ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING,1.0,6.0);
ActionPlayAnimation(ANIMATION_FIREFORGET_READ,1.0,8.0);
...
Реально происходит следующее:
1. Проигрывается начальный ролик ANIMATION_LOOPING_MEDITATE
длиной T_BEG
2. Проигрывается основной ролик ANIMATION_LOOPING_MEDITATE
длиной LENGTH = 10.0 сек., НО завершающий
ролик НЕ проигрывается, т.е. NPC продолжает "медитировать" !!!
3. Проигрывается ANIMATION_FIREFORGET_DRINK,
причем реально _DRINK проигрывается DESIRED_LENGTHсек,
а оставшееся время (7.0 - DESIRED_LENGTH)
сек. NPC продолжает "медитировать"!!!
4. Проигрывается ANIMATION_FIREFORGET_GREETING,
причем реально _GREETING проигрывается
DESIRED_LENGTH сек, а не 6.0. (см. FIREFORGET
без предмета)
5. Проигрывается ANIMATION_FIREFORGET_READ,
по тем же правилам, что и DRINK: т.е.
NPC снова "медитирует" после чтения (8.0 - DESIRED_LENGTH)сек.
6. Проигрывается завершающий ролик _LOOPING
длиной T_END. ТОЛЬКО СЕЙЧАС NPC встанет.
НЕБОЛЬШОЕ ДОПОЛНЕНИЕ:
Если в каком-либо из ANIMATION_FIREFORGET_*
будут задействованы ноги NPC, то он обязательно встанет для выполнения
этого действия, а по завершению СНОВА СЯДЕТ.
2.4. АНИМАЦИЯ NPC В ДИАЛОГЕ:
Самое простое, что Вы можете сделать средствами ToolSet-a, вообще
ничего не программируя, это заставить NPC хотя бы поприветствовать
Вас в начале диалога или выполнить какую-нибудь другую стандартную
анимацию.
В диалоге на любой строке Вы можете воспользоваться следующими возможностями:
в закладке OtherActions - поля Play Animation (проиграть анимацию)
и Play Sound (проиграть звук)
(см. рис.1 - 1,2,3)
рис.1
Продолжение следует...
Вторая часть
статьи >>>
Внимание!
Данный
текст является интеллектуальной собственностью. Любое цитирование
материалов допустимо только со ссылкой на наш сайт!
|