Motion Path
Motion Path
Предисловие
Да, да, это та, самая обычная кривая, к которой можно привязать объекты и двигать их туда-сюда. Отличие лишь в том, что это программный motionPath, которого мне так давно не хватало, хотя и не знаю для чего точно. Всё началось с игры Zuma, которая однажды поставила меня перед фактом, что я совершенно не понимаю как двигать такие шарики по нужной траектории, плюс ещё и активно манипулировать всем этим процессом. А затем я окончательно добил себя идеей dragAndDrop объекта по такой кривой так, чтобы он смещался именно туда, куда я его тяну мышкой, но чтобы он не выходил за пределы своего motionPath.
Представляю сколько велосипедов уже сделано до меня на эту тему. Если честно, то просто не искал варианты, хотелось просто сделать самому, с нуля. Чтобы долго не тянуть и не заставлять вас читать много текста, ожидая, когда же начнутся примеры, я сейчас расскажу, как пользоваться моими классами, а в конце стати, если смогу, то опишу более подробно.
Краткий обзор
Для активного использования motionPath, вам понадобятся всего три класса и несколько их методов и свойств это –
MotionPath.as – основной класс, который и создаёт кривую, (хотя она совсем не кривая на самом деле). Это не визуальный класс, но увидеть кривую будет возможно. Сам путь не является математической кривой, а состоит из набора точек. Единственный его метод, без которого не обойтись это initPath, в который и нужно будет передать массив из Point, составляющих путь. Плюс можно (и нужно) задать смягчение для кривой пути. Что за смягчение такое? Для чего нужно и почему я узнаю об этом последний? Всё узнаете чуть позже.
Locator.as – самый востребованный класс, который расширяет Sprite и конечно же имеет простые и удобные свойства и методы, для того, чтобы ездить вдоль пути куда угодно. Само название Locator я позаимствовал у программы Maya, в которой когда-то сидел днями и ночами. Возможно название не самое удачное на слух, но вы всегда сможете его изменить, например на FlowObject.
Итак, двигать локатор можно с помощью двух свойств, это:
value:Number – указывающее относительное положение на кривой, где 0 – это начало пути, а 1 – конец пути. Очень просто, как и в BezierSegment, только работает по-другому.
uv:Number - значение, указывающее положение локатора на пути в пикселях. Так как одним свойством value не очень удобно пользоваться, то пришла идея создать и такое свойство, которое будет просто в понимании, как например свойство «х» спрайта. Ведь путь тоже имеет длину, почему бы этим не воспользоваться. Если вы захотите установить ваш объект на двадцатый пиксель пути, то просто присвойте это значение свойству uv.
p.s. я забыл, что означает на самом деле две буквы «uv», помню что это было как-то связано с NURBS плоскостями (3d-шники скорее всего поймут), и определяло положение точки на кривой.
Так же есть второстепенные свойства, которые будут вам полезны, о них кратко:
orientToPath:Boolean – популярное название выдало само себя и стало понятно, что это вкл/выкл разворота локатора вдоль пути.
cycleValue:Boolean – а это уже вкл/выкл цикличности движения. Другими словами разрешить локатору появится с противоположной стороны пути, если он вышел за пределы с какого-то конца. Таким образом вы можете спокойно прибавлять/убавлять к свойствам uv или value любое значение, заставляя бегать локатор по кругу. А если цикличность будет отключена, то локатор просто остановится, когда двигаться уже будет некуда.
Во флешке ниже, два верхних локатора (вагонетки) цикличны, а два нижних – не цикличны. Нажимая стрелочки на клавиатуре вправо и влево, вы будете менять скорость для вагонеток, которая для всех одинакова.
rotateInterpolation:Boolean – это название меня веселит своей замудрённостью, но другого, подходящего не удалось придумать. Это вкл/выкл, усреднения поворота локатора, когда он находится между вершин пути. Такой параметр полезен, если ваш путь состоит из небольшого количества точек. Флешка ниже, лучше всего объяснит, что я имею ввиду. Два верхних локатора имеют значение true для rotateInterpolation, а два нижних – false.
path:MotionPath – ссылка на путь, которая поможет в любой момент перекинуть локатор с одного пути на другой.
Больше свойств у локатора нет, но остался один метод, который имитирует dragAndDrop эффект, это -
dragTo(targetX:Number, targetY:Number, slow:Number=0.4):Number
Такой метод нужно вызывать каждый кадр, передавая ему координаты мыши. Так же можно сделать перетаскивание плавным, отрегулировав параметр slow. На следующей флешке, можно лично опробовать этот метод в работе. Свойства orientToPath у локаторов имеют разные значения, а плавность перетаскивания равняется значению по умолчанию.
И как вы наверняка заметили, что этот метод возвращает число. Это число равняется текущему смещению локатора, точнее насколько изменилось его свойство uv после перетаскивания. Это число, конечно, может быть положительным и отрицательным.
И если ваш путь полностью забит локаторами, которые должны двигаться, как змейка, то почему бы не использовать такое число, для смещения всех остальных локаторов на пути! Ведь будет достаточно пробежаться по массиву с локаторами (игнорируя перетаскиваемый) и присвоить им текущий сдвиг по uv. Здесь тот же dragAndDrop;
(Main3.swf 700x350)
О локаторе почти всё уже рассказал. Но ещё хочу отметить нюанс: тянуть локатор (методом dragTo) вдоль пути по кругу вы сможете только если путь замкнут (при инициализации пути initPath, параметр closePath равнялся true) и если свойство локатора cycleValue тоже равняется true.
PolyLineEditor .as – Тот самый класс, который писался с мыслями о сохранении моих нервных клеток и ваших конечно тоже. Это очень простой редактор, для одной, единственной цели – создания точек для кривой пути. Просто поместите экземпляр этого класса в отображение и легко создайте самый сложный путь. А после того, как путь будет готов, вы сможете сохранить все базовые точки в буфер обмена, и затем просто вставить их в метод initPath, класса MotionPath. Это вспомогательный класс, которым удобно пользоваться только при разработке. Ещё удобнее просто создать и сохранить отдельную флешку с этим редактором, чтобы в любой момент можно было его открыть и поэкспериментировать.
В общем, лучше один раз увидеть, чем сто раз услышать.
Об управлении редактором вы заметили на его информационной панели. Поэтому с меня лишь краткое описание основных моментов:
- кликая на пустом месте, вы создаёте базовые точки, которые будут основой пути.
- новая точка всегда будет соединена с последней созданной точкой.
- но так же можно создать дополнительные точки, кликнув на ребре, между базовыми точками.
- для удаления точки, нужно её выделить и нажать backspace.
- чтобы сделать путь замкнутым, вам не нужно ставить последнюю точку рядом с первой, достаточно лишь нажать space. Это автоматически создаст дополнительную точку в конце пути и поместит её в координаты первой точки.
Например, чтобы получить кольцо, достаточно поставить четыре точки, образующих квадрат и замкнуть путь (space). Потом увеличить мягкость для наглядной убедительности.
- чтобы увидеть путь с разными степенями смягчения, нажимайте плюс и минус на клавиатуре.
- клавиши Enter и “с” копируют точки в буфер обмена в удобном виде, например:
Код:
new <Point>[new Point(593,39),new Point(652,148)];
Важно! Копируются точки текущей степени смягчённости, не забывайте об этом!!! Поэтому после того, как вы создали ваш путь, переходите на нулевую степень мягкости и копируйте точки. Ведь при инициализации пути, вы автоматически сможете задать смягчение в методе initPath класса MotionPath, помните? И там же отметить замкнутость пути, если он должен таким быть.
Например для того же кольца, достаточно скопировать всего четыре основных точки, которые не загромоздят ваш код метровой строкой, которая может появиться, если бы вы скопировали смягчённый вариант, с сотнями точек.
Надеюсь тут всё понятно, и теперь вы сможете быстро построить нужную траекторию. Например во флешке ниже – спираль, которая создалась за минуту или даже меньше. Не забудьте постучать по клавиатуре:
Немного кода (для новичков)
Давайте теперь создадим очень простой путь, в форме волны. Хотя вы можете сделать и любую другую форму. В редакторе у меня выглядело примерно так, на этот путь понадобилось всего шесть точек:
Не забываем перейти на нулевое смягчение и копируем точки в буфер. Затем создайте класс с любым именем, расширяющий класс Locator и нарисуйте в нём какое-нибудь графическое содержимое. У меня это простой квадрат и думаю этот класс нет смысла показывать здесь.
Следующий код показывает, как создать путь, и запустить по нему объект-локатор, который циклично бегает он начала в конец, или стремиться к курсору, смотря какую строчку вы оставите в методе enterFrame.
(код из класса MainBlog.as)
package { import AS3.motionPath.MotionPath; import flash.display.Sprite; import flash.events.Event; import flash.geom.Point; import mainBlog.Box; /** * ... * @author samana */ public class MainBlog extends Sprite { //массив с точками для пути private var _ponts:Vector.<Point>=new <Point>[new Point(43,148),new Point(127,37),new Point(257,178),new Point(377,21),new Point(511,181),new Point(586,55)]; //путь private var _motionPath:MotionPath; //это наш локатор, просто класс Box расширяет класс Locator private var _box:Box; public function MainBlog() { //создаём путь и смягчаем его в три раза _motionPath = new MotionPath(); _motionPath.initPath(_ponts, 3); //создаём квадрат-локатор, //передаём ему ссылку на путь, //ставим ему цикличность передвижения, //и мягкость на поворотах _box = new Box(_motionPath, true, true); //и включем поворот, относительно пути, //но можно и не включать _box.orientToPath = true; //таким образом можно увидеть путь на экране addChild(_motionPath.displayShape); //добавляем и локатор на экран addChild(_box); //подписываемся на ентерФрейм, где будем менять положение //локатора на 5 пикселей вперёд addEventListener(Event.ENTER_FRAME, enterFrame); } private function enterFrame(e:Event):void { //двигаться вперёд на 5 пикселей _box.uv += 5; //двигаться вперёд на 1% от длины пути //_box.value+= 0.01; //стремиться к курсору //_box.dragTo(mouseX, mouseY); } } }
Попробуйте поэкспериментировать, например создайте замкнутый путь и запустите по нему много объектов.
Все исходники флешек из этой статьи, будут конечно же доступны и вы сможете более детально посмотреть, как всё это работает.
Дополнительная информация
Самое важное, что пригодится в работе с MotionPath, уже рассказано. Главные свойства и методы – раскрыты. Но не хочется оставлять больших пробелов, поэтому расскажу ещё о том, о чём умалчивал всё это время.
* Класс Vertex.as вряд ли пригодится вам когда нибудь. По сути, это простой набор данных, которые хранит вершина на пути.
* Ещё у класса MotionPath есть три публичных метода, это:
- getValue(value:Number):Vertex – этот метод принимает значение от нуля, до единицы и находит, где лежит запрашиваемая точка на пути и возвращает вершину со всеми данными о ней. Это координаты, поворот к следующей точке, к предыдущей, нормаль, значение uv.
- getValueUV(valueUv:Number):Vertex – этот метод делает тоже самое, только он работает с длиной пути. Так же как и локатор, где uv=32 найдёт ту точку, которая лежит на 32-ом пикселе пути.
- redrawPathData(pathLine:int=1, vertexes:int=0, normals:int=0, nextAng:int=0, prevAng:int=0):void – а этот метод рисует путь. Значение у параметров означают: что нарисовать, а что нет.
И ещё у MotionPath есть свойство displayShape, в который и рисуется путь методом redrawPathData. И если вам понадобится всё таки увидеть линию пути на экране, то добавьте этот displayShape в отображение.
И немного о параметре closePath в методе:
Этот параметр влияет на смягчение замкнутого пути. Картинка ниже наглядно покажет различные ситуации. Там показан замкнутый путь из редактора, который имеет разный результат, зависящий от параметра closePath в методе initPath.
Просто не забывайте, что если вы планируете замкнутый путь, то обязательно отметьте это при его инициализации. К тому же напомню, что метод dragTo у Locator-а так же проверяет путь на замкнутость, и не позволит перетащить локатор на границе начала и конца, … или конца и начала… в общем вы понимаете.
Послесловие
И по старинке, я всё так же буду рад, если кому-то пригодятся эти классы. Если кто-то с их помощью сделает свою Зуму или что-то ещё интереснее. Надеюсь что багов больше не осталось и вы не разочаруетесь.
К сожалению сейчас дома уже давно нет интернета и вряд ли он скоро появится. Пишу, находясь у друга в гостях, поэтому нет возможности отправить эти классы на GitHub. И ответить на ваши комментарии тоже пока не смогу, так что не сочтите это за мою невежливость.
Исходник всех флешек находится в архиве «MotionPath examples.rar». Это проект FlashDevelop и всё находится одном проекте. Вам нужно будет лишь выбрать любой Main(1,2,3…) и сделать его документКлассом.
Пакет классов motionPath находится в архиве «AS3(MotionPath pack).rar», и там ещё дополнительный мой класс математики SMath, который необходим, так как активно используется внутри пакета.
Хочу пожелать вам хорошего настроения и творческого азарта! Пусть всё будет хорошо!
P.S. Интернет появился. Ссылка на Github - https://github.com/samana1407/AS3/tr...ter/motionPath
Кстати, оказалось, что на таком пути, довольно просто сделать что-то на подобии физических столкновений, чтобы объекты катались туда-сюда, как бусы. Для этого просто берём точку на один шаг к началу пути и к концу. И перемещаем локатор туда, где "y" меньше. Потом ещё добавляем столкновения и всё.
Здесь по нажатию клавиатуры появляется новый локатор, и мышкой можно тоже тянуть.
Всего комментариев 28
Комментарии
23.01.2015 17:29 | |
статью не читал, но осуждаю, не учитывается радиус, а это плохо. например в зуме шарики будут накладываются.
|
23.01.2015 17:30 | |
drag and drop можно в гонках юзать
|
23.01.2015 19:49 | |
статью читал, автор красавелла, nubideus - ворчун.
|
25.01.2015 16:53 | |
Интересная статья.
Предлагаю залить это дело на гитхаб. |
26.01.2015 02:00 | |
Цитата:
Предлагаю залить это дело на гитхаб.
Цитата:
нет возможности отправить эти классы на GitHub
|
26.01.2015 19:19 | |
Я когда-то давно тоже делал кривые по контрольным точкам. Для вот этого проекта. Там есть режим "змейка" (аналог Zooma), в котором шарики двигаются по кривой.
Но я не использовал точки для позиционирования на кривой, вместо этого применялось понятие отсупа от начала кривой. Зная отступ и контрольные точки кривой безье можно математически посчитать где будет находиться шарик. Даже захотелось декомпилировать и вспомнить, как я это все делал =) |
27.01.2015 10:26 | |
Нынче почти не работаю с флэшем, перебрался потихоньку на js/html. Зашел на флэшер, прочитал блог. samana, это отлично! Я очень люблю и уважаю самостоятельный разбор алгоритмических задач, это несравненно лучше и интереснее тех сотен js-статей про примитивнейшие действия в сверхсовременных браузерах.
Единственное, я бы по коду некоторые правки внёс, например "enterFrame" в "enterFrameHandler" переименовал и т.п. samana, в общем, респект. |
|
Обновил(-а) Hauts 27.01.2015 в 10:40
|
27.01.2015 16:39 | |
Hauts, а я бы enterFrameHandler переименовал в enterFrameHandle(по конвенциям, имя метода должно быть глаголом).
Это же логичнее. |
27.01.2015 23:37 | |
Логичнее как раз таки handler, т.е. обработчик.
https://cwiki.apache.org/confluence/...actices-Naming Ну а если не обработчик события, то тогда уж handleEnterFrame. |
28.01.2015 02:33 | |
В большинстве случаев обработчик не используется напрямую, т.е. мы не вызываем его самостоятельно (т.е. не командуем "doSomething()"), а лишь передаём ссылку на эту функцию, и основной его задачей является обработка события. Дать оригинальное название такому методу, отражающее его конкретные действия, помимо общего - обработки события, было бы крайне трудно. Так как он может выполнять микс каких угодно действий. Поэтому кроме путаницы такое именование ничего не добавит. А вот название myButton_clickHandler или someData_propertyChangedHandler (или схожее именование), в особенности, когда таких обработчиков > 10, сразу даёт понять, какую функцию несет данный метод.
Бывают случаи, когда удобно в качестве обработчика передать какой-то "самостоятельный" метод, такой как complete() например, тогда в этот метод дописывают необязательный параметр event:Event = null, но не наоборот, изобретают обработчик, который можно было бы использовать вне его основного предназначения. Исходя из вышесказанного название обработчика не должно быть глаголом, до тех пор пока это не "самостоятельный" метод используемый в качестве обработчика. |
|
Обновил(-а) udaaff 28.01.2015 в 03:15
|
28.01.2015 20:17 | |
А я бы не называл метод и сделал замыкание!
|
30.01.2015 21:53 | |
Котяра, это возрастное, я щитаю.
|
30.01.2015 22:40 | |
loop еще есть
|
30.01.2015 23:13 | |
У тебя подростковое значит )
|
31.01.2015 10:43 | |
31.01.2015 20:18 | |
nubideus, какой некрасивый код получился!
|
01.02.2015 19:38 | |
nubideus, это вы сейчас не тем козырем пошли.
|
01.02.2015 21:29 | |
я пытался перенести на as подход, который используется например в явке
Код:
public void run() { do { if(!Thread.interrupted()) //Проверка прерывания { } else return; //Завершение потока try{ Thread.sleep(1000); //Приостановка потока на 1 сек. }catch(InterruptedException e){ return; //Завершение потока после прерывания } } while(true); } |
02.02.2015 10:41 | |
Переводить многопоточный подход в однопоточный язык
|
02.02.2015 11:56 | |
Psycho Tiger, правда что ли? http://goo.gl/EIfOqm
или тебе тоже рассказать какие проблемы будут если ты будешь юзать анонимную функцию? что блин, как назвать enterframe? серьезно? а какая разница? как принято в проекте так и называйте, начинаете проект - пишите по соглашению. важность названия обработчика enterframe пренебрежительно мала. в 18 из 26 комментариях пишут про какие то слушатели, зачем флудите |
02.02.2015 14:43 | |
Цитата:
Сообщение от nubideus
не учитывается радиус, а это плохо. например в зуме шарики будут накладываются.
Спасибо всем, за комментарии!! Всегда приятно узнать, что кому-то нравятся твои наработки. Некоторые классы претерпели изменения, к тому же залил код на Github, ссылка появилась в дополнении в конце статьи. |
|
Обновил(-а) samana 15.02.2015 в 14:25
|
Последние записи от samana
- Motion Path (23.01.2015)
- RastrMovieClip или свой велосипед (13.06.2014)
- Зажигаем свет в 2d. (11.12.2013)