Создание автоматной очереди: MEL, Maya Expressions

Здравствуйте :)

Решил написать про MEL и Maya Expressions, на примере создания стрельбы из автомата. Т.е. напишу про то, как автоматизировать создание автоматных очередей. Про один из вариантов решения задачи.

Сразу уточню, что я пишу урок про MEL, а не про то, как смоделировать автомат.

Моделируем автомат.

Скачать тестовую модель

Итак, у нас есть модель автомата. Теперь нужно заставить его стрелять. Сидеть и создавать ключи для каждого кадра было бы очень трудно, долго и вообще глупо. Нам нужна очередь и не одна. Проще всего этого добиться с помощью Expressions. Expression это не совсем MEL: они немного отличаются внешне и работают немного быстрее чем тоже самое, сделанное на MEL. Лучше не загромождать сцену большим кол-вом expression`ов так как они тяжело потом читаются. Делайте связи как можно более удобными и понятными. И желательно их комментировать (// - так обозначаются комментарии на MEL).

Создайте модель вспышки. Это может быть всё, что хотите: плоскость, флюид, пейнт эффект. Я решил не усложнять и использовать простые плоскости, на которых можно разместить текстуры с огнём. Размещаем её на место, где она должна быть, и замораживаем её координаты (Freeze Transformations).

Теперь нужно заставить её смахивать на выстрелы. Для этого открываем Expression editor. И выбираем создание экспрешена по имени:

Так нам будет удобно ориентироваться в экспрешенах автомата (они все будут находиться в одном месте).

fire.visibility=rand(1);

Это начальный экспрешен. При проигрывании анимации мы видим как наш объект мерцает. «Fire» Это имя ноды (т.е. имя нашего объекта) .visibility – имя его атрибута. rand – функция генерации случайных чисел. Имя нужного вам атрибута можно увидеть в channel box или в Expression editor (выбрав выделение экспрешена по объекту).

Огонь мерцает сам. Но делает он это когда захочет. Нужно заставить его мерцать только когда нужно нам. Для этого потребуется новый атрибут, который можно будет анимировать. Выбираем модель автомата и создаём для него новый атрибут:

Теперь назовём его shoot и выберем тип Boolean. Т.е. переменная может иметь значения True или False, 1 или 0. Для определения стрельбы нашего автомата.

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

Теперь атрибут gun.shoot включает и выключает мерцание (для того чтобы чётко это увидеть, нужно не забыть включить проигрывание всех кадров в режиме реального времени). Далее немного усложняем экспрешен огня (добавляя различные трансформации). Так же можно добавить появление трассеров (тем же самым образом). И можно связать интенсивность света от вспышки.

Скачать эту сцену

Но это самый простой пример, который можно придумать.

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

И вот автомат уже подаёт признаки жизни. И управляется стрельба всего одним атрибутом. Но чего-то не хватает :) А не хватает сыплющихся гильз. А гильз нам нужно очень много и чтобы они сыпались как надо в кучку, ударяясь друг об друга, катаясь по земле. И вообще были похожи на настоящие гильзы. Вот тут-то и придётся подключить MEL. Вам не нужно писать сложные физические уравнения. Их уже написали за вас. И называются они Maya Dynamic. Но этот чудо плагин не позволяет нам получить того, что мы хотим стандартными инструментами. Нужно писать собственные.

Я надеюсь, что вы знакомы с встроенным плагином Maya Dynamic. Интерфейс у него довольно удобен и понятен. Но в сложных сценах возможны тормоза, сбои, всё вплоть до вылетов. Так, что при создании скрипта, нужно не забыть про оптимизацию. А так же про «запекание» (bake keys).

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

Создаём гильзу и отключаем её из просчёта (коллизии и активность)

Скачать эту сцену

(для того чтобы увидеть траекторию полёта, нужно включить активность гильзы, коллизии, игнор)

Пишем скрипт:

// объявляем переменные. int – целые, float – дробные, string – строковые (текст)

// так же есть vector, boolean (true или false)

int $t=1,$last=50;

string $name,$expr,$sstate;

// выключаем просчёт динамики, убирая галку state

setAttr rigidSolver.state 0;

// запоминаем имя нашей исходной гильзы

string $ObjS[]= `ls -sl`;

// запускаем цикл создания дубликатов, где $t=1 начальный кадр, а $last=50 конечный.

while ($t <= $last)

{

// начало цикла, исходный объект дублируем и назначаем ему новое имя с номером кадра, в котором он был создан

duplicate;

$name = ("name"+$t);

rename $name;

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

setAttr ".active" on;

setAttr ".collisions" on;

// убиваем все ключи

cutKey -clear -time ":" -hierarchy none -controlPoints 0 ;

// продолжаем расставлять атрибуты, где .v – отрисовка объекта

setAttr ".ignore" on;

setAttr ".v" off;

// перемещаем timeslider в нужный нам момент

currentTime $t ;

// расставляем ключи, для того чтобы дубликат появлялся и начинал считать динамику, тогда, когда нужно

setKeyframe ".ignore";

setKeyframe ".v";

// объявляем переменную с временем, следующего кадра

int $tt= $t+1;

// переходим к следующему кадру и расставляем там ключи

currentTime $tt ;

setAttr ".ignore" off;

setAttr ".v" on;

setKeyframe ".ignore";

setKeyframe ".v";

// добавляем случайное начальное вращение каждому дубликату (для большего реализма)

setAttr ".initialSpinX" `rand -100 100`;

setAttr ".initialSpinY" `rand -100 100`;

setAttr ".initialSpinZ" `rand -100 100`;

// так как сдублированный объект не имеет никаких связей в гиперграфе, мы присоединяем к нему гравитациию

connectDynamic -f "gravityField1" $name;

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

int $ttt= $t+80;

int $tttt= $ttt+1;

// для отключения активности, нужно использовать перемещение timeslider без обновления экрана -update false. Так же как, если назначать кадр средней кнопкой на timeslider.

currentTime -update false $ttt ;

setKeyframe ".active";

currentTime -update false $tttt ;

setAttr ".active" off;

setKeyframe ".active";

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

select $ObjS;

// и перемещаем время на 5 единиц вперёд, т.е. каждые пять кадров будет вылетать новая гильза

$t= $t+5;

}

// конец цикла. Включаем обратно просчёт динамики.

currentTime 0 ;

setAttr rigidSolver.state 1;

// конец

Скачать этот скрипт

Выбираем гильзу и запускаем скрипт. И, при проигрывании анимации, видим, как посыпались гильзы.

Данный скрипт, позволяет анимировать исходный объект. Но тяжек в использовании и пока нельзя связать его c нашим волшебным атрибутом (.shoot), для определения промежутков стрельбы. А так же мы видим, что при Playblast и Bake Simulation происходят какие-то чудеса. Ну ничего :) Пофиксим всё это, написав диалоговое окно (window) с различными функциям (включая собственный и удобный bake).

Пишем новый скрипт:

// назначаем действия для наших кнопок

// это действие запускает цикл с производством дубликатов

proc emitV(int $t, int $last, string $solv,int $Hz,string $field, string $tDel, int $optTime, int $inSpin)

{

int $tt;

string $name,$expr,$sstate;

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

$sstate = ($solv+".state");

setAttr $sstate 0;

string $ObjS[]= `ls -sl`;

while ($t <= $last)

{

// многие атрибуты будут снимать значения с диалогового окна (для снятия значения есть другая процедура)

duplicate;

$name = ("name"+$t);

rename $name;

setAttr ".active" on;

setAttr ".collisions" on;

cutKey -clear -time ":" -hierarchy none -controlPoints 0 ;

setAttr ".ignore" on;

setAttr ".v" off;

currentTime $t ;

setKeyframe ".ignore";

setKeyframe ".v";

int $tt= $t+1;

currentTime $tt ;

setAttr ".ignore" off;

setAttr ".v" on;

setKeyframe ".ignore";

setKeyframe ".v";

int $inSpinMin;

$inSpinMin = $inSpin*(-1);

setAttr ".initialSpinX" `rand $inSpinMin $inSpin`;

setAttr ".initialSpinY" `rand $inSpinMin $inSpin`;

setAttr ".initialSpinZ" `rand $inSpinMin $inSpin`;

connectDynamic -f $field $name;

int $ttt= $t+$optTime;

int $tttt= $ttt+1;

currentTime -update false $ttt ;

setKeyframe ".active";

currentTime -update false $tttt ;

setAttr ".active" off;

setKeyframe ".active";

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

addAttr -ln start -at double ;

setAttr -e -keyable true .start;

addAttr -ln stop -at double ;

setAttr -e -keyable true .stop;

setAttr ".start" $t;

setAttr ".stop" $ttt;

// добавляем в сценарий условие, при котором дубликат будет удалён. Т.е. если, например, атрибут gun.shoot не равен true (нулю), то объект будет удалён. Это необходимо для определения промежутков стрельбы

float $testDel = `getAttr $tDel`;

if ($testDel<1){doDelete;}

select $ObjS;

$t= $t+$Hz;

}

currentTime 0 ;

setAttr $sstate 1;

}

// процедура запекания

proc ppBaker()

{

// выделяем только трансформы всех сгенерированных объектом. Путём выделения по имени и исключения ненужных нам node.

select "name*";

select -d -adn;

select -d `ls -sl -shapes`;

// запускаем bake для каждого выделенного объекта в отдельности, используя заранее подготовленные атрибуты у каждого объекта.

string $forBake[]= `ls -sl`;

string $forPP;

for ($forPP in $forBake)

{

select $forPP;

// иногда просчёты бывают долгими и хочется посмотреть на стадию происходящего. Для этого используем оператор print. Это очень полезный оператор :) т.к. с помощью него удобно опрашивать переменные при написании сценария. \n – это символ переноса на новую строку

print ("Baking "+$forPP+":\n");

float $pStart = `getAttr .start`;

float $pStop = `getAttr .stop`;

string $pTime;

$pTime = ($pStart+":"+$pStop);

bakeResults -simulation true -t $pTime -sampleBy 1 -disableImplicitControl true -preserveOutsideKeys true -sparseAnimCurveBake true -controlPoints false -shape false ;

rename ("baked_"+$forPP);

}

select "baked_name*";

// удаляем динамику со всех заранее переименованных запечённых объектов

deleteSelectRigidBodies;

}

// процедура снятия значений с диалогового окна

proc valueR(string $frF,string $frL, string $Ssolv, string $HzF,string $Sfield, string $ttDel, string $optT, string $inSpinV)

{

int $t = `intField -q -value $frF`;

int $last = `intField -q -value $frL`;

string $solv = `textField -q -text $Ssolv`;

int $Hz = `intField -q -value $HzF`;

string $field = `textField -q -text $Sfield`;

string $tDel = `textField -q -text $ttDel`;

int $optTime = `intField -q -value $optT`;

int $inSpin = `intField -q -value $inSpinV`;

// после присваивания новых переменных в конце действия запускаем генерацию дубликатов. Т.е. необходимо в начале опросить переменные в window, присвоить значения новым переменным, а затем, имея новые значения, пускать действие, использующее нужные нам переменные

emitV($t,$last,$solv,$Hz,$field,$tDel,$optTime,$inSpin);

}

// добавляем действия кнопок

string $allSelect = "select \"name*\";";

string $allDelete = "select \"name*\";doDelete;";

string $allBakedSel = "select \"baked_name*\";";

// создаём диалоговое окно

string $window = `window -title "Shooter (by Fiend3d)"

-iconName "Shooter"

-widthHeight 210 465`;

columnLayout -adjustableColumn true;

text -label "First frame:";

$frF = `intField`;

text -label "Last frame:";

$frL = `intField`;

text -label "Solver name:";

$Ssolv = `textField -text "rigidSolver"`;

text -label "Frame per Obj:";

$HzF = `intField -value 5`;

text -label "Connect Field:";

$Sfield = `textField -text "gravityField1"`;

text -label "If This <1 then Delete:";

$ttDel = `textField -text ".v"`;

text -label "Work time PP:";

$optT = `intField -value 120`;

button -label "Emit Dynamic Obj" -command "valueR($frF,$frL,$Ssolv,$HzF,$Sfield,$ttDel,$optT,$inSpinV)" -bgc 0.3 0.8 0;

button -label "Select All Emited Obj" -command $allSelect;

button -label "Delete All Emited Obj" -command $allDelete;

button -label "Bake Emited Obj" -command "ppBaker()" ;

button -label "Select All Baked Obj" -command $allBakedSel;

text -label "Rand initialSpinXYZ:";

$inSpinV = `intField -value 100`;

button -label "Close" -command ("deleteUI -window " + $window);

setParent ..;

// отрисовка окна

showWindow $window;

//конец

скачать финальный вариант

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

посмотреть видео (mpeg 300 кб) или скачать сцену

Если при работе со сценарием майя начинает ругаться, то решение проблемы вы найдёте в Script editor (почти всегда). Не забывайте ещё то, что динамика корректно работает только при проигрывании всех кадров (а не в реальном времени).

При крупном плане (падения гильз) эта методика запекания не годится. Лучше увеличить «время жизни» и запечь стандартными средствами.

Ну вот и всё.

Так же при написании сценария удобно выделять часть текста и нажимать ctrl+enter для проигрывания выделенного куска и это не затирает то, что вы написали ранее. Пользуйтесь хелпом, он на самом деле очень удобен. И имеет ответы на многие вопросы. Существуют великолепные редакторы текста с подсветкой операторов и подключаемыми синтаксисами (включая MEL). Мне лично очень нравится textpad. Многие рекомендуют писать сценарий в стороннем редакторе. Потому, что есть шанс потерять свой скрипт (майя также может рухнуть или свет вырубится :) ). Размер шрифта в редакторах майя можно регулировать, зажав ctrl и колёсиком мыши (так обстоят дела не только в майя).

Иногда случается крупный план, и качества даже очень сложных уравнений явно не хватает. Чаще всего в таких случаях самый лучший выход – это не полениться и сделать всё руками. Но так же ещё можно преобразовать экспрешен в ключи и поправить там, где нужно. А ещё есть Trax Editor, но это уже другая история ;)

Приложение

Мало я написал про замечательную вещь Expressions. Подумал, что надо исправить это.

Опять на примере стрельбы :) Но этот раз четырёх стволов, с дульным тормозом (не знаю как это точно называется). Так же это можно применить к ходу затвора.

И так у нас есть четыре замечательных ствола, которые должны по очереди стрелять. Для этого удобнее использовать всем известную функцию y=Sin(x).

y=Sin(x):

Значения Y меньше нуля нам не нужны.

y=Sin(x)>0:

Уже что-то в этом есть :) Но нам нужны большие промежутки между скачками. А нужна нам для этого функция, которая будет обнулять график в нужный момент.

Sin(x)>0:

На графике видно, что Y может применять два значения: ноль и бесконечность. Так или так, да и нет, true и false. Т.е. это получится график Boolean и в майя будет принимать значения нуля и единицы.

Sin(x)>0;

Sin(2x)=y>0;

Два уравнения и график «поредел».

Но в майя всё куда проще и гибче. Такой график можно получить тысячами способов. И даже не обязательно помнить какие-то формулы из школы. Но операции с известными функциями, как правило короче.

Нужно построить зависимость от времени, т.е. Y=Sin(time), где Y – любой атрибут. Так же можно использовать переменные. Но помните, что большинство операторов из MEL не будут работать интерактивно.

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

Пишем expression подёргивания:

//объявляем переменную времени, умножая её на 10 (ускоряя)

float $t=time*10;

// объявляем переменную «обнуления» графика

int $Nul;

// назначаем ей в два раза большие промежутки между «выстрелом», для удлинения промежутков стрельбы

$Nul=sin($t/2)>=0;

// финальный sin($t) умножаем на $Nul и обрезаем (clamp) значения выходящие за ноль и единицу, чтобы ствол не скакал в обе стороны.

float $graf=clamp(0,1,sin($t)*$Nul);

// присваиваем атрибуту .translateY значение $graf, умножая его на -1 для действия в обратную сторону.

pCylinder1.translateY=$graf*(-1);

Ствол начал ходить, как бы «прожидая». Теперь дублируем ствол (pCylinder1) до нужного числа. Я думаю мне четырёх хватит :)… Дублируя незабываем, что Expression – это тоже входящая нода и её нужно включить в процесс дублирования (Duplicate Input Graph).

И смещаем у остальных объектов график немного вправо. Для этого просто вычитаем по две лишних единицы из переменной времени $t (чтобы сдвинулся вправо нужно вычитать). Т.е. из второй вычитаем 2, из третей 4, из четвёртой 6:

float $t=time*10-2;

int $Nul;

$Nul=sin($t/2)>=0;

float $graf=clamp(0,1,sin($t)*$Nul);

pCylinder2.translateY=$graf*(-1);

скачать эту сцену

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

Так же могу посоветовать раздобыть программки для отрисовки графиков. Мне лично нравится GrafEq. Очень удобно просматривать действия тех или иных функций, а так же тестировать и совершенствовать свои. Это может пригодиться не только в написании экспрешеннов, но и при написании своих шейдеров (на RSL их писать не очень трудно и интересно) Например, всем до боли известный checker:

Sin(u)*Sin(v)>0

Полностью раскрыть тему про MEL и Expressions, думаю, невозможно. И я не пытался. В интернете мало статей с аргументированием логики скрипта. А именно разбором готового несложного решения проще всего овладеть начальными знаниями (книги так же очень желательно прочесть). Не думайте, что кто-то уже решил все проблемы за вас. Более вероятно, что вы сами найдёте наиболее комфортное решение своей задачи, нужное вам в какой-то конкретной ситуации. Ведь написать абсолютно универсальное GUI (интерфейс программы) нереально.

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

Надеюсь, что вам было интересно :)

Всем спасибо. С удовольствием отвечу на вопросы.

640 0 850 40
22
2006-09-21
Великий респект автору! Максимум баллов и спасибо.
2006-09-21
Что-то перечитал свой скрипт :) Вместо: select -d "nameShape*" ; Наверно лучше: select -d `ls -sl -shapes`; Ну… не ошибается тот кто ничего не делает.
2006-09-21
2Fiend3d: В тексте исправили на select -d `ls -sl -shapes`;, ок?
2006-09-21
Юлия Ефремова не обязательно :) Работает и так.
2006-09-22
Вот за что обожаю Майу - в ней можно ВСЕ! А вот за что ненавижу - в ней можно ВСЕ! p.s.: не-на-ви-жу скрипты :) p.p.s.: автору - так держать.
2006-09-22
2. Автор Капсюли пробей на гильзах ))))
2006-09-22
лови питёрки!
2006-09-22
ЛМА Ууууё =)))) Доделывал в четыре утра… и видимо… не учёл :( так то я хотел ещё и дым… исправлюсь
2006-09-22
2 Fiend3d: ЛМА шутит... ;о) а то он тебя щас еще и фотореалистично отрендерить попросит.. ;о)))
2006-09-22
Интересненько! Сейчас буду читать...
2006-09-27
Для меня слишком сложно. Попроще никак??
2006-09-27
crest что именно тебя интересует?
2006-09-29
В Maxe я бы это сделал с помощью частиц без всяких скриптов. Не понимаю зачем такие заморочки. Правда maya очень поверхостно знаю, может быть там всё так делается?
2006-09-29
crest Ничего хорошего у тебя максовыми частицами не получилось бы. И никогда не получится. Потому, что это невозможно. В майя приблизительного, но скоростного результата с помощью частиц добиться можно. Но это гораздо сложнее чем то, что я описал выше. Этот метод позволяет иметь взаимодействие с окружающими объектами, без каких либо проблем: гильзы будут скатываться по одежде, набираться в кучку и вообще будет реалистично, а не простейшее описание траектории полёта (как ты сделал бы в максе). В максе невозможно следить за динамикой в реальном времени (то что есть – совершенно не то)… по этому контроль за динамикой минимальный и модификаторная система запутает даже самого опытного человека (особенно если учесть отсутствие гиперграфа) . Если ты найдёшь в себе силы изучить майя. То через неделю, ты поймёшь, что это гораздо более логичный, удобный, быстрый пакет чем макс. Отрицание скриптов говорит только о некомпетентности аниматора. Описанный выше метод – это самое простое решение данной задачи, на мой взгляд. Ничего сложного в нём нет.
2006-10-01
2Fiend3d > Ничего хорошего у тебя максовыми частицами не получилось бы. И никогда не получится. Потому, что это невозможно Не будь столь категоричен. На текущий момент уже есть система частиц для макса со встроенной поддержкой физики, другая на подходе. Да и сам скрипт такой написать для макса не проблема, была бы такая нужда. >В максе невозможно следить за динамикой в реальном времени (то что есть – совершенно не то)… по этому контроль за динамикой минимальный и модификаторная система запутает даже самого опытного человека (особенно если учесть отсутствие гиперграфа) . Вот это высказавание не мужа а мальчика. Как и всегда дело опыта и привычки. >Если ты найдёшь в себе силы изучить майя. То через неделю, ты поймёшь, что это гораздо более логичный, удобный, быстрый пакет чем макс. А это уже реклама) Удобный для чего? Быстрый в чем ? Каждый пакет решает задачи в своей области. Отрицание скриптов говорит только о некомпетентности аниматора. Тут полностью согласен. Хотя аниматор и не обязан знать как писать скрипты, но с какой стороны взаться должен быть в курсе. PS. Хороший урок. Молодец.
2006-10-01
Savin Denis Реклама не реклама… =) Я хочу как лучше для всех.
2006-10-03
Большое спасибо автору. Урок для того и урок, что бы учится. Научимся, и сами, может быть что-нибудь приготовим для других. А хороший урок всегда пригодится. Пять!
2006-10-06
Салидная работа, ценю.
2006-10-10
Уже тянет на отдельную статью, а не просто урок. РЕСПЕКТ АВТОРУ!
2007-01-26
Урок клевый, хоть я многое чего не понял, но я думаю это придет со временем, меня вот что интересует, а окуда вы скрипты вылавливаете????
2008-07-03
Поправьте пожалуйста :) Ссылку http://render.ru/images/uploads/Image/Tutor/maya/442/gun_part1.rar На http://render.ru/images/uploads/Image/Tutor/maya/442/gun_part2.rar А то ссылка не на то указывает во второй раз.
2020-05-27
Здравствуйте!

Искал как настроить, чтобы глаза моргали через интервал времени, кажется, что можно будет воспользоваться Expressions, но не знаю как конкретно, можете подсказать?
RENDER.RU