Этот урок создавался для 3dcenter.ru
Результат выполнения урока


Создание эффекта “разбивающихся букв” в Particle Flow

Все знают, что MAXScript можно использовать для ускорения работы в 3ds max. Причем неважно, в какой области работает пользователь – моделирование, анимация или эффекты – ускорить можно все. А во многих случаях не только ускорить, но и создать нечто такое, что “ручками” сделать невозможно, например сложный эффект в Particle Flow (поток частиц), которому кстати и посвящен этот урок. Сейчас я пойду еще дальше – покажу скрипт, который делает с нуля все содержимое урока. Поэтому вам не придется как в обычных уроках по частицам делать все ручками – нажимать кнопки, создавать объекты, добавлять операторы, тесты, связи и т.д. Вместо этого я расскажу, что должно получится, а потом выложу скрипт с открытым кодом и комментариями, который нужно только запустить в пустой сцене – и все готово, эффект у вас на компьютере. Урок делался в 3ds max 2008, в старых версиях теоретически должен работать, но я не проверял.

Смотрел я в детстве мультфильм “Остров сокровищ” и там во время ударов и всяческих столкновений появлялись на экране надписи вроде “Ба-бах”, “Тра-та-та” и т.п. Короче говоря, текстовая озвучка. При копании Particle Flow обнаружилось, что похожее можно сделать и в 3ds max причем без особых усилий. Итак, что происходит в сцене. Сверху из двигающегося и вращающегося источника сыплются красные кубики, которые, достигнув определенного возраста (Age Test), превращаются в случайные меняющиеся со временем слова, которые опять же через некоторое время (Age Test) взрываются шариками. Шарики долго не живут и быстро удаляются (оператор Delete). Цвет слов и шариков синий (оператор Display), за скорость отвечает оператор Speed, за поворот слов Shape Facing - чтобы они находились прямо по отношению к камере (камера, как и все остальное, создается автоматически). В том, что шарики выглядят как шарики, а кубики как кубики виновен оператор Shape. Вот так выглядит это хозяйство в Particle View:


Событие Event 01 отвечает за красные кубики, Event 02 за слова, которые, возникая, меняют свой размер, направление движения и скорость, Event 03 – за взрывающиеся шарики. В Event 02 слова возникают благодаря Script Operator, который подключает к системе новый скрипт, который работает внутри Particle Flow. Таким образом урок состоит из двух скриптов: первый делает урок (который в принципе можно сделать и ручками, но я бы замучался рассказывать, а вы повторять это в максе), второй просто скрипт, который используется в уроке. Теперь рассказываю, как эти скрипты запустить и посмотреть. Скрипты это текстовые файлы, их можно смотреть хоть в блокноте, но лучше непосредственно в 3ds max (Падающее меню MAXScript/Open Script). В скриптах есть комментарии на русском (зеленый текст после знака --), а если вместо русских кириллических букв в редакторе максскрипта вы увидите "крякозябры" - надо в файле maxroot/MXS_Editor.properties заменить значение code.page на 0 вместо -1.

Скрипт урока: pflow_plus_maxscript.ms (скачать)

a=getFilenamePath (getSourceFileName()) --ПОЛУЧАЮ ПУТЬ К ФАЙЛУ ЭТОГО СКРИПТА
--messagebox (b as string) --ПРОВЕРКА
flowscript="include \""+a+"pf_so.ms"+"\"" --ССЫЛКА НА СКРИПТ В Script Operator ДЛЯ Particle Flow
--messagebox (flowscript as string) --ЕЩЕ ОДНА ПРОВЕРКА

--ЕСЛИ ФАЙЛ НЕ БУДЕТ НАЙДЕН, МОЖНО ЯВНО УКАЗАТЬ ПУТЬ, НАПРИМЕР:
--flowscript="include \""+"C:\\Temp\\pf_so.ms"+"\""

--ВЫДЕЛИТЬ И УДАЛИТЬ ВСЕ, СОЗДАТЬ КАМЕРУ И ПОМЕНЯТЬ ИНТЕРВАЛ АНИМАЦИИ ДО 200 КАДРОВ
max select all
max delete
cam=targetcamera pos: [-1200, -735, 118] target: (targetObject pos:[-200, -80, -300]) --КАМЕРА
animationRange = interval animationRange.start 200f

d1 = dummy pos: [-100,0,0] --СОЗДАТЬ ПУСТЫШКУ
pf1=PF_Source X_Coord:0 Y_Coord:0 isSelected:on Logo_Size:20 Emitter_Length:20 Emitter_Width:20 Emitter_Height:0.01 pos:[-100,-50,0] --СОЗДАТЬ ИСТОЧНИК ЧАСТИЦ
pf1.parent = d1 --СВЯЗАТЬ ИСТОЧНИК С ПУСТЫШКОЙ

--СОЗДАНИЕ ПЕРВОГО СОБЫТИЯ И ЕГО ОПЕРАТОРОВ
particleFlow.BeginEdit()
op1 = Birth Amount: 35 Emit_Start: 160 Emit_Stop: 16000 Type: 0 --ВРЕМЯ В ТИКАХ, В ОДНОЙ 30-КАДРОВОЙ СЕКУНДЕ ИХ 4800, В ОДНОМ КАДРЕ СООТВЕТСТВЕННО 160
op2 = Position_Icon Location: 0 Inherit_Emitter_Movement:true
op3 = Speed Speed: 400
--op4 = Rotation()
op5 = ShapeStandard Shape: 1 --1 - КОД КУБИКА
op6 = RenderParticles()
op7 = DisplayParticles Type: 6 --6 - КОД ОТОБРАЖЕНИЯ ГЕОМЕТРИИ
op7.color = red --pf1.wireColor
op8= Age_Test Test_Value: 4800 Variation: 0 --6400 3200
ev1 = Event()
ev1.SetPViewLocation (pf1.X_Coord) (pf1.Y_Coord+100)
particleFlow.EndEdit()
--ДОБАВЛЕНИЕ ОПЕРАТОРОВ К СОБЫТИЮ
ev1.AppendAction op1
ev1.AppendAction op2
ev1.AppendAction op3
--ev1.AppendAction op4
ev1.AppendAction op5
ev1.AppendAction op7
ev1.AppendAction op8
pf1.AppendInitialActionList ev1
pf1.AppendAction op6

--СОЗДАНИЕ ВТОРОГО СОБЫТИЯ АНАЛОГИЧНО
ev2 = Event()
particleFlow.BeginEdit()
op9= Shape_Facing Look_At_Object: cam --АГА, СМОТРИМ В КАМЕРУ
op10 = Script_Operator Proceed_Script: flowscript --ВОТ ТУТ ВТОРОЙ СКРИПТ ПОДКЛЮЧАЕТСЯ
op11 = ScaleParticles Type: 0 X_Scale_Factor: 25 Y_Scale_Factor: 25 Z_Scale_Factor: 25 \
    X_Scale_Variation: 5 Y_Scale_Variation: 5 Z_Scale_Variation: 5

op12 = Speed Speed: 220 Direction: 5 Reverse: true Divergence: 50 --5 ОЗНАЧАЕТ НАСЛЕДОВАТЬ ДВИЖЕНИЕ, Reverse - ПОМЕНЯТЬ НАПРАВЛЕНИЕ
op13 = DisplayParticles Type: 6
op13.color=blue
op14= Age_Test Test_Value: 9000 Variation: 0 --6400 3200
particleFlow.EndEdit()
ev2.SetPViewLocation (pf1.X_Coord) (pf1.Y_Coord+300)
ev2.AppendAction op9
ev2.AppendAction op10
ev2.AppendAction op11
ev2.AppendAction op12
ev2.AppendAction op13
ev2.AppendAction op14
op8.setNextActionList ev2 op8 -- УСТАНАВЛИВАЮ СВЯЗЬ МЕЖДУ Age Test ПЕРВОГО СОБЫТИЯ И ВТОРЫМ СОБЫТИЕМ

--СОЗДАНИЕ ТРЕТЬЕГО СОБЫТИЯ АНАЛОГИЧНО
ev3 = Event()
particleFlow.BeginEdit()
op15= Spawn Spawn_Type: 0 Delete_Parent: true Number_of_Offsprings: 32
op16 = ShapeStandard Shape: 2 --КОД ШАРИКА
op17 = Speed Speed: 250 Variation: 50 Direction: 3 --3 - КОД ХАОТИЧНОГО ДВИЖЕНИЯ ВО ВСЕ СТОРОНЫ Random 3D
op18 = deleteParticles Type: 2 Life_Span: 1000 Variation: 400--1600
op19 = DisplayParticles Type: 6
op19.color=blue

particleFlow.EndEdit()
ev3.SetPViewLocation (pf1.X_Coord) (pf1.Y_Coord+500)
ev3.AppendAction op15
ev3.AppendAction op16
ev3.AppendAction op17
ev3.AppendAction op18
ev3.AppendAction op19
op14.setNextActionList ev3 op14 -- УСТАНАВЛИВАЮ СВЯЗЬ МЕЖДУ Age Test ВТОРОГО СОБЫТИЯ И ТРЕТЬИМ СОБЫТИЕМ

sliderTime = 0f --ПЕРЕХОЖУ НА НУЛЕВОЙ КАДР

--АНИМИРУЮ ДВИЖЕНИЕ И ПОВОРОТ ПУСТЫШКИ
animate on
(
at time 0 (d1.pos=[-100,0,0]; d1.rotation= quat 0 [0,0,1])
at time 100 (d1.pos=[200,0,0]; d1.rotation= quat 180 [0,0,1])
)



Скрипт Particle Flow: pf_so.ms (скачать)

--ЭТОТ СКРИПТ СОЗДАН НА ОСНОВЕ СТАНДАРТНОГО ПРИМЕРА Flying Letters from a Custom Text С НЕБОЛЬШИМИ ИЗМЕНЕНИЯМИ
on ChannelsUsed pCont do (
pCont.useShape = true --ВКЛЮЧАЕМ ВОЗМОЖНОСТЬ МЕНЯТЬ ГЕОМЕТРИЮ ЧАСТИЦ ИЗ СКРИПТА
)


--ИНИЦИАЛИЗАЦИЯ НАЧАЛЬНЫХ ЗНАЧЕНИЙ
on Init pCont do (

global pflow_word_array --ТУТ ХРАНЯТСЯ СЛОВА ПО ОТДЕЛЬНОСТИ
local text_source = $PFlowText --ОБЪЕКТ ТЕКСТА

--ЕСЛИ ТЕКСТОВОГО ОБЪЕКТА НЕТ
if not isValidNode text_source do (
text_source = text text: "Бац!Бум!Бам!Опа!Ура!Ааа!Ыыы!"
text_source.name = "PFlowText"
addmodifier text_source (extrude amount:10)
text_source.renderable = false
)

--ЕСЛИ МАССИВ ОПРЕДЕЛЕН, ТО ЭТОТ БЛОК ОТВЕЧАЕТ ЗА НЕМЕНЯЮЩИЕСЯ СЛОВА ПРИ АНИМАЦИИ - НЕ ИСПОЛЬЗУЮ
-- if pflow_word_array != undefined do (
-- txtpos = -3
-- for i in pflow_word_array do (
-- txtpos += 4
-- if not (isValidNode i) or i.font != text_source.font or i.text != (substring text_source.text txtpos 4) do
-- pflow_word_array = undefined
-- )
-- )

--ЕСЛИ МАССИВ СЛОВ НЕОПРЕДЕЛЕН
if pflow_word_array == undefined do (

--СОЗДАЮ ПУСТОЙ МАССИВ И ЗАПОЛНЯЮ ЕГО ОБЪЕКТАМИ ОТДЕЛЬНЫХ СЛОВ
pflow_word_array = #()
local str = "Бац!Бум!Бам!Опа!Ура!Ааа!Ыыы!"
delete $PFlow_word_* --УДАЛЯЮ СТАРЫЕ СЛОВА

--СОЗДАЮ НОВЫЕ
for i = 1 to str.count by 4 do (
word = copy text_source
word.text = (substring str i 4)
word.name = (uniquename "PFlow_word_")
word.font = text_source.font
append pflow_word_array word
hide word
)
)

) --КОНЕЦ ИНИЦИАЛИЗАЦИИ

--НАЗНАЧАЮ СЛОВА ЧАСТИЦАМ
on Proceed pCont do (
count = pCont.numParticles() --СКОЛЬКО ВСЕГО ЧАСТИЦ
maxcount = pflow_word_array.count --СКОЛЬКО СЛОВ В МАССИВЕ
empty_mesh = triMesh() --ПУСТОЙ МЕШ

--ЦИКЛ ПО ВСЕМ ЧАСТИЦАМ, НАЗНАЧАЮ ИМ СЛОВА
for i = 1 to count do (

pCont.particleIndex = i
if i <= maxcount then ( --ЕСЛИ НОМЕР ЧАСТИЦЫ МЕНЬШЕ НОМЕРА СЛОВА, ТО БУДЕТ ЕЙ СЛОВО
if isValidNode pflow_word_array[i] do pCont.particleShape = pflow_word_array[i].mesh
)

else ( --ЕЖЕЛИ НОМЕР БОЛЬШЕ, ТО ПУСТОЙ МЕШ ЧАСТИЦЕ НАЗНАЧАЕТСЯ - ТУТ МОЖНО В ПРИНЦИПЕ ПОМУДРИТЬ И ПОМЕНЯТЬ
pCont.particleShape = empty_mesh
)
)
)

on Release pCont do () --КОГДА ГОТОВО, ДЕЛАТЬ БОЛЬШЕ НЕЧЕГО :)


Как запускать, чтобы все заработало

Оба скрипта должны находиться в одной папке, запускать (Падающее меню MAXScript/Run Script) нужно только файл pflow_plus_maxscript.ms, не забыв перед этим подготовить сцену следующим образом:
  1. Новая полностью пустая сцена.
  2. System Units и Display Units – Millimeters.
  3. Time Configuration: NTSC 30 frames per second
После того как скрипт отработал, переключите один из видов на Camera 01 и проиграйте анимацию.
“Рендерить” лучше на белом фоне

Ну вот и все, желаю успехов в освоении MAXScript и Particle Flow!