Анимируем бабочек в Expression Blend
Накануне мартовского праздника (да и весна началась!) мы будем учиться анимировать бабочек.
Задача
В первом приближении задачу можно поставить следующим образом: созданить анимацию бабочки, машущей крыльями.
Препарирование бабочки
Для работы нам понадобится изображение бабочки, желательно, натуралистичное (за готовыми изображениями направляю в интернеты ;), в png и с прозрачным фоном, например, такое:
Чтобы бабочка “махала” крыльями, это изображение необходимо разрезать не две части и сохранить как отдельные картинки:
и
User Control Butterfly
В проекте Silverlight в Expression Blend импортируем два изображения (можно напрямую перетащить на рабочее пространство):
Выделив оба фрагмента, из контекстного меню выбираем “Make Into UserControl” (или просто F8):
У нас появился новый контрол Butterfly.
Анимация бабочки
У нас будет три анимации: взлет бабочки, приземление бабочки и взмах крыльями.
Предварительно в нашем контроле нужно сделать следующие изменения:
- Дать названия каждому из крыльев, например, “Left” и “Right” — так будет проще ориентироваться.
- Меняем положение центра. Для этого в панели “Transform” на вкладке “Translate” выбираем: центральную правую точку для левого крыла и центральную левую для правого:
(Аналогичное изменение модно сделать на вкладке “Center Point”: 1 и 0.5 для левого крыла, 0 и 0.5 — для правого. Числа эти относительные: {0, 0} — верхний левый угол объекта, {1, 1} — нижний правый.) - Уменьшить масштаб. Для этого в панели “Transform” открываем вкладку “Scale” и для каждого из крыльев выставляем значения 0.75 для X и Y:
- Меняем положение центра проекции. Для этого в панели “Transform” в части “Projection” на вкладке “Center of Rotation” выставляем значения, аналогичные п.2: 1 и 0.5 для левого крыла, 0 и 0.5 — для правого
Предварительная настройка окончена.
Взлет и посадка
Анимации взлета и посадки будут довольно простыми: при взлете бабочка увеличивается в размере, поднимает и опускает крылья; при посадке — уменьшается, поднимает и опускает крылья.
Начнем со взлета. Переходим в панель “Objects and Timeline”, нажимаем на плюсик для создания новой анимации StoryBoard:
При этом рабочая область немного изменится — появится линейка времени для записи анимации:
(В принципе, можно переключить рабочее пространство в режим анимации — F6 или Windows → Workspaces → Animation).
Для взлета достаточно ~0.5 секунды. Выбираем первое крыло, ставим текущее время на 0.5:
На вкладке “Scale” панели “Transform” выставляем значения X и Y в 1:
Аналогичную операцию проделываем для второго крыла.
При этом на линейке времени вы увидите отметки, что в некоторые моменты времени происходят изменения (если раскрыть объекты, можно увидеть, какие именно свойства изменены):
Если проиграть анимацию, бабочка увеличивается в размере.
Давайте теперь добавим взмахи крыльями. Выделим момент времени ~0.25 секунды, “Transform” → “Projection” → “Rotation”, ставим Y=-70 для левого крыла и Y=70 для правого:
При этом бабочка должна немного “сжаться”:
Можно запустить анимацию и посмотреть, что получилось. На самом деле бабочка поднимается, еще не успев оттолкнуться крыльями, поэтому изменение размера нужно сделать после подъема крыльев.
Самый простой способ внести изменение — выбрать нулевой момент времени и для каждого из крыльев добавить KeyFrame (овал с плюсом):
(Для удобства внизу можно поменять масштаб временной линейки.)
Далее для RenderTransform каждого из крыльев перемещаем отметки на временной шкале в нужные моменты:
Теперь стало получше ;)
Продолжим посадкой. Все практически аналогично взлету, только наоборот. Для этого создадим новый StoryBoard “Down”.
Чтобы сильно не повторяться, сделаем дупликат (Duplicate) и переименуем в “Down”:
Далее нужно поправить изменение масштабов (RenderTransform для каждого из крыльев): начать с 1 и закончить 0.75:
Полет нормальный
Делаем еще один дупликат, называем “Fly”. Удаляем RenderTransform для каждого из крыльев:
Сжимаем по времени:
На этом анимации закрываем.
Обидеть бабочку каждый может
Следующий шаг — заставить анимации работать последовательно, а бабочку летать.
(Тут нужно было бы воскликнуть: “Открываем Visual Studio и лезем в код”. Но мы воспользуемся редактором Expression Blend, благо много кода писать не придется.)
Начнем с того, что нужно повесить событие, которое будет запускать анимацию по наведению курсора мыши на бабочку:
public Butterfly()
{
// Required to initialize variables
InitializeComponent();
MouseEnter += ButterflyControl_MouseEnter;
}
void ButterflyControl_MouseEnter(object sender, MouseEventArgs e)
{
Start();
}
Далее (раскрываю карты) нам мы будем запускать бабочку в полет в случайном направлении на случайную дистанцию и приземлять.
Важный момент: при этом нужно блокировать (!) повторный запуск анимации. В данном случае мы обойдемся простым булевским флагом:
bool isActive = false;
public bool IsActive
{
get { return isActive; }
}
void ButterflyControl_MouseEnter(object sender, MouseEventArgs e)
{
if (!isActive)
Start();
}
Для управления полетом нужно будет добавить еще несколько параметров:
double angle = 0;
double direction = 0;
double x = 0;
double y = 0;
int counter;
static Random RND = new Random();
Обратите внимание на то, что RND сделан статическим, чтобы для разных бабочек не генерировать одни и те же последовательности.
counter — счетчик, сколько раз нужно проиграть анимацию “Fly". На каждом таком шаге бабочка будет немного поворачиваться и пролетать некоторое расстояние в этом направлении.
Start(); и запуск полета
public void Start()
{
counter = Buttrefly.RND.Next(20) + 5;
direction = 20 * Buttrefly.RND.NextDouble() - 10;
Storyboard up = this.Resources["Up"] as Storyboard;
up.Begin();
Run();
isActive = true;
}
direction — на время полета постоянная случайная составляющая поворота бабочки, чтобы бабочка куда-то устремлялась, а не совсем хаотически летала.
Обратите внимание, что мы цепляем запуск одного события за другое.
Run(); и приземление
Тут нам придется делать несколько важных вещей, поэтому рассмотрим по порядку.
Во-первых, нам нужно будет применить три трансформации: поворот, перемещение по X и перемещение по Y, для каждой из них своя анимация:
private void Run()
{
if (counter > 0)
{
Storyboard sb = new Storyboard();
DoubleAnimation rotateAnimation = new DoubleAnimation();
DoubleAnimation moveXAnimation = new DoubleAnimation();
DoubleAnimation moveYAnimation = new DoubleAnimation();
Во-вторых, нужно установить время анимации (у нас оно было 0.3c), цель, к которой применяются анимации, и соответствующие свойства цели:
rotateAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.3));
moveXAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.3));
moveYAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.3));
Storyboard.SetTarget(rotateAnimation, LayoutRoot);
Storyboard.SetTarget(moveXAnimation, LayoutRoot);
Storyboard.SetTarget(moveYAnimation, LayoutRoot);
Storyboard.SetTargetProperty(rotateAnimation, new PropertyPath("(UIControl.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"));
Storyboard.SetTargetProperty(moveXAnimation, new PropertyPath("(UIControl.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"));
Storyboard.SetTargetProperty(moveYAnimation, new PropertyPath("(UIControl.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)"));
В PropertyPath указывается магический путь до нужного свойства ;)
В-третьих, нужно вспомнить немного геометрии и прописать соответствующие трансформации, имея в виду, что угол задается в градусах и по часовой стрелке:
rotateAnimation.From = angle;
angle = angle + direction + (80 * Buttrefly.RND.NextDouble() - 40);
rotateAnimation.To = angle;
moveXAnimation.From = x;
x = x + (40 * Math.Sin((angle) / 180 * Math.PI));
moveXAnimation.To = x;
moveYAnimation.From = y;
y = y - (40 * Math.Cos((angle) / 180 * Math.PI));
moveYAnimation.To = y;
В-четвертых, объединяем анимации в одну StoryBoard, прописываем событие на завершение, запускаем анимацию трансформации и анимацию “Fly” махания крыльями и уменьшаем счетчик:
sb.Children.Add(rotateAnimation);
sb.Children.Add(moveXAnimation);
sb.Children.Add(moveYAnimation);
sb.Completed += new EventHandler(sb_Completed);
sb.Begin();
(this.Resources["Fly"] as Storyboard).Begin();
counter--;
}
}
В-пятых, приземляемся.
void sb_Completed(object sender, EventArgs e)
{
if (counter > 0)
{
Run();
}
else
{
(this.Resources["Down"] as Storyboard).Begin();
isActive = false;
}
}
Готово.
Кстати, убедитесь, что у вас в XAML-коде заданы заглушки для трансформаций, иначе они динамически не найдутся:
<Grid x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Grid.RenderTransform>
Проверка
Разместите несколько Butterfly-контролов на главной странице и запускайте приложение. Теперь бабочки летают!
https://constantin.kichinsky.ru/projects/butterfly/
Продолжение следует! Исходники приложения в прикрепленном архиве.
Comments
Anonymous
March 14, 2010
Это мой первый опыт в Expression. Урок супер, хотел бы выполнить. У меня вопрос к уроку: Не отображается в рабочем прострастве сама бабочка...Отображается только квадратный контур...Что делать?Anonymous
March 14, 2010
Урок отличный. Я буквально с этого урока начал изучать Expression Studio и у меня вопрос к этому уроку: У меня не отображются крылья бабочки. Отображается только их контур в виде прямоугольников...Как исправить?Кмопилируется и запускается проект нормально. Бабочка есть.Anonymous
March 14, 2010
Все разрешилось установкой Service Pack 1. =)Anonymous
March 22, 2010
Урок отличный,только не пойму почему у меня бабочка взлетает,но не летит( я уже урок почти наизусть выучил,вставка кода с исходника не помогает,даже не знаю что делать дальше. Она взлетает и все,дальше ничего не происходит,с чем такое может быть сказано,как я понимаю это что в Run,но что именно и как исправить не знаю,те более что в бленде с дебагом туго.Anonymous
March 24, 2010
- Работает ли проложенный к статье проект?
- Этот же проект можно открыть в Visual Studio, где уже можно запустить отладку.
Anonymous
March 26, 2010
проект работает,ошибку нашел: <Grid x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5"> <Grid.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform Angle="0"/> <TranslateTransform/> </TransformGroup> </Grid.RenderTransform> нужно было вставить в Buttrefly.xaml,а я Main вставлял) теперь смотрю все доступное видео на английском),урок с бабочками очень крут,жду еще в том же духе;) P.S. если можно объясните на фига эти заглушки трансформации,и когда их надо ставить?,про магический путь PropertyPath тоже интересно)Anonymous
March 28, 2010
Заглушки с трансформациями нужны только для того, чтобы с ними работать из кода: если они объявлены в xaml, я могу к ним обращаться из кода. В идеале, конечно, в коде нужно проверять, объявлены они или нет -- и если не объявлены, создавать в коде эти объекты трансформаций.Anonymous
April 05, 2010
Уважаемые, нашел этот пост через http://silverlighter.ru/ У меня не отображаются изображения в рабочей области, только рамки. Кто нибудь сталкивался с этим?Anonymous
June 07, 2010
Была таже проблема! Нашел решение forums.silverlight.net/.../312794.aspx Оказывается, что проблема в кирилице, которая используется в пути/названии проэкта или картинки!