Работа с математикой Number при твининге DisplayObject
Казалось бы, какие тут могут быть сложности? В конструктор собственного твинера передаём ссылку на объект, параметр, который надо бы менять, конечное значение и время/кадры, за которое этот параметр должен плавно принять конечный вид. Для простоты возьмём случай покадрового изменения значения:
public class SomeTweener { private var _obj:Object; private var _paramName:String; private var _endValue:Number; private var _frames:Number; public function SomeTweener(obj:Object, paramName:String, endValue:Number, frames:Number) { _obj = obj; _paramName = paramName; _endValue = endValue; _frames = frames; } }
Цитата:
Любой DisplayObject, даже не добавленный в DisplayList исправно получает событие входа на кадр.
import flash.display.Shape; import flash.events.Event; import flash.events.IEventDispatcher; public class SomeTweener { private var _obj:Object; private var _paramName:String; private var _endValue:Number; private var _frames:Number; private var eventDispatcher:IEventDispatcher; public function SomeTweener(obj:Object, paramName:String, endValue:Number, frames:Number) { _obj = obj; _paramName = paramName; _endValue = endValue; _frames = frames; eventDispatcher = new Shape(); eventDispatcher.addEventListener(Event.ENTER_FRAME, tween); } private function tween(e:Event):void { } }
Идём дальше. Опять же по наипростейшему пути - каждый вызов tween мы меняем переданное значение на одну и ту же величину (твининг типа easeNone - то есть равномерный). Для этого лучше ещё в конструкторе рассчитать покадровый инкремент, исходя из разницы между конечным и стартовым значениями и продолжительностью твина, и записать инкремент в поле класса. В самом методе tween мы будем проверять, сколько кадров твининг уже длится и по достижении заданного значения - прерывать твининг:
import flash.display.Shape; import flash.events.Event; import flash.events.IEventDispatcher; public class SomeTweener { private var _obj:Object; private var _paramName:String; private var _endValue:Number; private var _frames:Number; private var eventDispatcher:IEventDispatcher; private var increment:Number; public function SomeTweener(obj:Object, paramName:String, endValue:Number, frames:Number) { _obj = obj; _paramName = paramName; _endValue = endValue; _frames = frames; increment = (endValue - Number(obj[paramName])/frames; eventDispatcher = new Shape(); eventDispatcher.addEventListener(Event.ENTER_FRAME, tween); } private function tween(e:Event):void { if (_frames == 0) { e.currentTarget.removeEventListener(e.type, tween); return; } obj[paramName] += increment; _frames--; } }
Казалось бы - всё, твинер готов. И он даже будет работать, а в большинстве случаев - ещё и правильно. Но есть один случай, когда он ни в коем случае не выдаст нормального результата. Приведу небольшой пример:
import flash.display.Sprite; import flash.events.Event; public class SomeClass extends Sprite { private var sprite:Sprite; public function SomeClass() { sprite = new Sprite(); sprite.x = 1; sprite.addEventListener(Event.ENTER_FRAME, traceSome); var tween:SomeTweener = new SomeTweener(sprite, 'x', -1, 40); } private function traceSome(e:Event):void { trace(sprite.x); } }
Есть и другая особенность. В среде исполнения FlashPlayer тип Number является "64х битным числом с плавающей запятой" © i.o. Из-за этого достаточно часто случаются накладки. Проще всего объяснить на примере:
Так ведёт себя флеш со всеми значениями типа Number (к коим относятся и поля координат DisplayObject), тут уж ничего не поделаешь. Вполне естественно, что пример работы метода traceSome нашего класса SomeClass будет сбоить даже при твининге на 40 кадров. Практика показала, что sprite.x не сможет сдвинуться именно со значения -0.35 будучи каждый кадр до него округлённым. Цикл таков:
Цитата:
1) Берём значение поля объекта (-0.35)
2) Наращиваем его на значение инкремента (-0.35 + (-0.05) = -0.39(9)97)
3) Записываем его в поле объекта (-0.39(9)97)
4) (Скрытый обязательный пункт) Значение поля координаты экземпляра DisplayObject округляется (-0.35)
5) Входим на следующий кадр и берём значение поля объекта (-0.35)
6) GOTO 2)
2) Наращиваем его на значение инкремента (-0.35 + (-0.05) = -0.39(9)97)
3) Записываем его в поле объекта (-0.39(9)97)
4) (Скрытый обязательный пункт) Значение поля координаты экземпляра DisplayObject округляется (-0.35)
5) Входим на следующий кадр и берём значение поля объекта (-0.35)
6) GOTO 2)
import flash.display.Shape; import flash.events.Event; import flash.events.IEventDispatcher; public class SomeTweener { private var _obj:Object; private var _paramName:String; private var _endValue:Number; private var _frames:Number; private var eventDispatcher:IEventDispatcher; private var increment:Number; private var currentValue:Number; public function SomeTweener(obj:Object, paramName:String, endValue:Number, frames:Number) { _obj = obj; _paramName = paramName; _endValue = endValue; _frames = frames; currentValue = Number(obj[paramName]); increment = (endValue - Number(obj[paramName])/frames; eventDispatcher = new Shape(); eventDispatcher.addEventListener(Event.ENTER_FRAME, tween); } private function tween(e:Event):void { if (_frames == 0) { e.currentTarget.removeEventListener(e.type, tween); return; } currentValue += increment; obj[paramName] = currentValue; _frames--; } }
P.S.: Да, существующий код твинера можно и нужно улучшать. Добавить отложенный запуск, рассылку сообщений. Можно добавить easing и прочий мультифилд-твин. И да, весьма полезным, хоть и расходующим память, будет подход с предварительным предрассчётом массива всех позиций параметра, подвергаемого твинингу. Но к данной задаче это не относится, а потому оставим в качестве домашнего задания.
Всего комментариев 24
Комментарии
06.03.2012 11:55 | |
Отлично! Молодец!
Очень полезно. Ps: дайте ссылку на формулы easing. |
06.03.2012 12:13 | |
Инкремент не нужен. Easing как делать будете?
значение = Начальное * (1 - alpha) + конечное * alpha |
06.03.2012 12:42 | |
Поищите формулы в исходниках любого твинера с открытым исходным кодом.
Того же твинмакса. Ну или у себя на компьютере, вот здесь: Код:
C:\Program Files (x86)\Adobe\Adobe Flash CS5.5\Common\First Run\Classes\mx\transitions\easing C:\Program Files (x86)\Adobe\Adobe Flash CS5.5\Common\Configuration\ActionScript 3.0\projects\Flash\src\fl\transitions\easing C:\Program Files (x86)\Adobe\Adobe Flash CS5.5\Common\Configuration\ActionScript 3.0\projects\Flash\src\fl\motion\easing |
|
Обновил(-а) ChuwY 06.03.2012 в 12:46
|
06.03.2012 12:57 | |
А как при этом гарантируется, что по окончанию твина значение будет конечным? Твин в 3 кадра длиной там такое насчитает тогда.
|
06.03.2012 13:11 | |
ChuwY, спасибо.
|
06.03.2012 16:13 | |
Цитата:
Это в доках по событию так?
|
06.03.2012 17:48 | |
Попытка реализовать интерполяцию циклом с инкрементами всегда сопряжена с накоплением погрешности. В данном случае все усугубляется еще и округлением до 1/20. Не надо так делать твины.
Цитата:
Ps: дайте ссылку на формулы easing.
|
06.03.2012 23:38 | |
Хоть кто-то справку читает!
|
07.03.2012 00:04 | |
Спасибо за критику, исправил.
|
07.03.2012 00:14 | |
Теперь ждем твининга невизуальных объектов.
|
07.03.2012 00:15 | |
07.03.2012 01:07 | |
Будет.
|
07.03.2012 20:49 | |
Вообще я советую increment узнавать каждый раз.
По-моему, так точности больше, хотя точно я не знаю. |
07.03.2012 21:19 | |
Топик не про твининг =)
|
07.03.2012 21:56 | |
Я вам один умный вещь скажу, но только вы не обижайтесь. Привяжите к текущему времени. Каждый следующи ENTER_FRAME лишь повод подсчитать реальное передвижение объекта с момента предыдущего вызова ENTER_FRAME. Нужен getTimer().
|
07.03.2012 21:57 | |
Цитата:
Топик не про твининг =)
|
08.03.2012 11:51 | |
Цитата:
Нужен getTimer()
|
08.03.2012 21:59 | |
Ничем не сложнее. Только я специально отметил, что опускаю множество маленьких и изящных улучшений, ибо написать твинер - не цель этого топика
|
Последние записи от BuKT
- О вопросе энтропии в реализации интерфейсов (02.07.2012)
- Работа с математикой Number при твининге DisplayObject (06.03.2012)
- Bloom и HDR пост-эффекты. (09.01.2012)
- Продажа игр (18.10.2011)
- forEach. Вскрытие (14.04.2011)