Silverlight: Разработка MMORPG. Анимирование объектов (Part 2)
Способ анимации №2. CompositionTarget
Вторым методом для создания анимации является CompositionTarget. В официальной документации объект CompositionTarget может создавать анимацию покадрово. Другими словами, анимация созданная CompositionTarget основывается на событиях вызываемых при перерисовке UI, частота вызова события соответствует частоте перерисовки UI, которая является фиксированной, следовательно ее проблематично контролировать.
Но как же применить данный подход? XAML файл остается неизменным по отношению к предыдущей части, отличия лишь в code-behind файле:
public partial class MainPage : UserControl { private Rectangle rect; //устанавливает скорость движения double speed = 5; //конечная точка движения Point moveTo; public MainPage() { InitializeComponent(); rect = new Rectangle() { Fill = new SolidColorBrush(Colors.Red), Width = 50, Height = 50, RadiusX = 5, RadiusY = 5 }; Carrier.Children.Add(rect); Canvas.SetLeft(rect, 0); Canvas.SetTop(rect, 0); //регистрируем событие обновления UI CompositionTarget.Rendering += CompositionTarget_Rendering; } void CompositionTarget_Rendering(object sender, EventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed : -speed)); Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed : -speed)); } private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { moveTo = e.GetPosition(Carrier); } }
Прежде всего мы должны зарегистрировать обработчик события Rendering для CompositionTarget:
CompositionTarget.Rendering += CompositionTarget_Rendering;
Далее мы желаем переместить квадрат в любую точку, куда нажмем мышкой, следовательно я использую переменную с именем "moveTo" для хранения координат и присваиваю ей значение при каждом клике в методе Carrier_MouseLeftButtonDown.
Идем дальше, я создаю анимацию в методе CompositionTarget_Rendering, потому что он постоянно вызывается при событии перерисовке UI, для имитации движения я динамически задаю квадрату свойства Left и Top.
Обратите внимание, что скорость может быть положительной и отрицательной, это зависит от новой и старой позиции. Здесь есть что-то общее с человеческим мышлением, например, если вы нажмете левее квадрата, rect_X будет больше, чем moveTo.X, так что скорость будет отрицательной, изменяя rect_X в меньшую сторону.
Жмём Ctrl+F5, и опять убеждаемся, что при нажатии по любому месту на холсте, приводит квадрат в движение.
Но мы заметим критическую проблему в данном механизме работы. Экран постоянно мерцает, а движение далеко не плавное. Может наш код делает что-то не так?
Причиной отсутствия плавного движения квадрата является в том, что не совпадает скорость по направлениям X и Y, поэтому нам нужно использовать методы Math.Sin и Math.Cos для разделения исходной скорости на speed_X и speed_Y, основной код выглядит так:
void CompositionTarget_Rendering(object sender, EventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X)); Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y)); } private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); moveTo = e.GetPosition(Carrier); double len = Math.Sqrt(Math.Pow(moveTo.X - rect_X, 2) + Math.Pow(moveTo.Y - rect_Y, 2)); speed_X = Math.Abs(speed * (moveTo.X - rect_X) / len); speed_Y = Math.Abs(speed * (moveTo.Y - rect_Y) / len); }
Мерцании экрана зависит от скорости. Сейчас скорость равна 5. Давайте уменьшим ее до 1. Мерцание хоть и слабое, но все равно присутствует. Почему же чем больше скорость, тем ужаснее мерцание? Ответ кроется внутри метода CompositionTarget_Rendering:
Как вы знаете, функция CompositionTarget_Rendering работает в режиме нон-стоп. И следующее действие:
Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X));
будет выполняться все время, даже когда квадрат достигнул конечной точки. Не важно. Худшее развитие событий, когда квадрат так никогда и не попадет в конечную точку. Например, rect_X равен 100, а moveTo.X равен 101, speed_X = 3 и тогда значение левой точки квадрата будет последовательно становиться: 103, 100, 103, 100, а это означает, что он никогда не остановится. То же самое и с вариантом движения по Y. Вот почему вы видите постоянно мерцание экрана.
Решением данной проблемы является установка критического значения, которое равно половине скорости. И когда растояние между rect_X и moveTo.X становится меньше, чем критическое значение, мы помечаем, что квадрат прибыл в конечную точку по оси X, следовательно Canvas.SetLeft перестанет вызываться.
То же самое проделываем и для движения по оси Y. Я снова изменил метод CompositionTarget_Rendering:
void CompositionTarget_Rendering(object sender, EventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); if (Math.Abs(rect_X - moveTo.X) > speed_X / 2) Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X)); if (Math.Abs(rect_Y - moveTo.Y) > speed_Y / 2) Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y)); }
Теперь анимация выглядит так, как нужно.
Итого
Данная глава поведала нам, как использовать CompositionTarget для создания анимации основаной на кадрах, она отличается от традиционной анимации, которая меняет изображения. В её основе лежит постоянное изменения свойств объекта.
Следующая глава, познакомит нас с третьим способом создания анимации, а также мы сравним все три метода анимации. Следим за обновлениями.
Демо к данной главе можно загрузить тут
Источник - Jianqiang Bao