Работа с математикой 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
Комментарии
![]() ![]() |
|
Отлично! Молодец!
Очень полезно. Ps: дайте ссылку на формулы easing. |
![]() ![]() |
|
Инкремент не нужен. Easing как делать будете?
значение = Начальное * (1 - alpha) + конечное * alpha |
![]() ![]() |
|
Поищите формулы в исходниках любого твинера с открытым исходным кодом.
Того же твинмакса. Ну или у себя на компьютере, вот здесь: Код:
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
|
![]() ![]() |
|
А как при этом гарантируется, что по окончанию твина значение будет конечным? Твин в 3 кадра длиной там такое насчитает тогда.
|
![]() ![]() |
|
ChuwY, спасибо.
|
![]() ![]() |
|
Цитата:
Это в доках по событию так?
|
![]() ![]() |
|
Попытка реализовать интерполяцию циклом с инкрементами всегда сопряжена с накоплением погрешности. В данном случае все усугубляется еще и округлением до 1/20. Не надо так делать твины.
Цитата:
Ps: дайте ссылку на формулы easing.
|
![]() ![]() |
|
Хоть кто-то справку читает!
|
![]() ![]() |
|
Спасибо за критику, исправил.
|
![]() ![]() |
|
Теперь ждем твининга невизуальных объектов.
|
![]() ![]() |
|
![]() ![]() |
|
Будет.
|
![]() ![]() |
|
Вообще я советую increment узнавать каждый раз.
По-моему, так точности больше, хотя точно я не знаю. |
![]() ![]() |
|
Топик не про твининг =)
|
![]() ![]() |
|
Я вам один умный вещь скажу, но только вы не обижайтесь. Привяжите к текущему времени. Каждый следующи ENTER_FRAME лишь повод подсчитать реальное передвижение объекта с момента предыдущего вызова ENTER_FRAME. Нужен getTimer().
|
![]() ![]() |
|
Цитата:
Топик не про твининг =)
![]() |
![]() ![]() |
|
Цитата:
Нужен getTimer()
|
![]() ![]() |
|
Ничем не сложнее. Только я специально отметил, что опускаю множество маленьких и изящных улучшений, ибо написать твинер - не цель этого топика
|
Последние записи от BuKT
- О вопросе энтропии в реализации интерфейсов (02.07.2012)
- Работа с математикой Number при твининге DisplayObject (06.03.2012)
- Bloom и HDR пост-эффекты. (09.01.2012)
- Продажа игр (18.10.2011)
- forEach. Вскрытие (14.04.2011)