Анимируем бабочек в 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/
Продолжение следует! Исходники приложения в прикрепленном архиве.