Анимация полета ракеты на C# WPF

Все исходники / Язык программирования C# / OS Windows / Desktop / WPF программирование / Анимация полета ракеты на C# WPF
Оглавление:
  1. Анимация полета ракеты на экране
  2. Окно в форме ракеты
  3. Класс бесконечной анимации AnimRocket
  4. Анимация перемещения ракеты DoubleAnimation
  5. Анимация появления и исчезновения ракеты
  6. Вычисление случайных координат перемещения
  7. Доработка приложения
  8. Исходник C#

Анимация полета ракеты на экране

Приложение-шутка на платформе C# WPF запускает анимацию перемещения космической ракеты по экрану монитора. Это последовательное перемещение окна приложения, форму которого определяет анимированное изображение космической ракеты. Точки старта и финиша определяются случайным образом, создавая прикольный хаотичный полет мультяшной ракеты на экране.

Реалистичный полет ракеты создают совокупность нескольких видов анимации:
DoubleAnimation - изменение свойства Opacity и изменение свойств Left, Top главного окна;
ObjectAnimationUsingKeyFrames – покадровая анимация ракеты с работающим двигателем.

Приложение может служит стартовым кодом создания программ-приколов или использоваться для тестирования WPF анимаций. Пример подобной программы-шутки - Анимация новогоднего поздравления.

Окно в форме ракеты

Чтобы создать фигурное окно приложения на платформе WPF необходимо сделать главное окно прозрачным, для чего требуется обязательное переопределение некоторых свойств главного окна:
AllowsTransparency - окно приложения может быть прозрачным только в случае определения этого свойства значением true.
WindowStyle - единственное допустимое значение для этого свойства WindowStyle.None, если AllowsTransparency имеет значение true.
Background - необходимо выбрать прозрачную кисть для фона окна.
Только после надлежащего определения этих свойств можно получить прозрачное главное окно приложения. Эти свойства можно определить непосредственно в разметке XAML или в программном коде, смотрите коды ниже.

После создания прозрачного окна приложения любой дочерний визуальный элемент будет определять форму главного окна. В нашем случае это объект Image класса AnimRocket с анимацией последовательно показываемых файлов .png.

Разметка XAML WPF для создания окна фигурной формы:
<Window x:Class="WpfAppRocket.MainWindow"
       . . . 
        Height="500" Width="800" 
        WindowStyle="None" AllowsTransparency="True" Topmost="True"  ResizeMode="NoResize"
        Background="#000000FF" >
        
    <Grid x:Name="mainGrid"/>
</Window>
Определение прозрачности главного окна в конструкторе приложения:
public MainWindow()
{
    InitializeComponent();

    AllowsTransparency = true;
    WindowStyle = WindowStyle.None;
    Background = Brushes.Transparent;
}

Класс бесконечной анимации AnimRocket

AnimRocket – класс бесконечной анимации ракеты с работающим двигателем. Анимация построена на функциональности объекта ObjectAnimationUsingKeyFrames, который показывает коллекцию ключевых кадров в течение определенного промежутка времени, заданного свойством Duration.

В качестве ключевых кадров используются файлы .png, определенные как ресурсы приложения. Плоскостью для последовательного показа изображений ресурсов служит элемент Image, в котором подготовленный объект ObjectAnimationUsingKeyFrames периодически изменяет источник изображения.

AnimRocket имеет метод Rotate(double angle) для ориентирования изображения ракеты вдоль оси перемещения. С помощью RotateTransform программным кодом обеспечивается вращение ракеты относительно своего центра.

Метод ComputeMaxImageSize() вычисляет максимальную длину из всех сторон изображений. ComputeMaxImageSize() помогает создавать квадратное главное окно приложения с соответствующими размерами для корректного вращения ракеты вокруг центральной точки.

Программный код класса бесконечной анимации космической ракеты с работающим двигателем:
class AnimRocket
{
    // Размер массива адресных данных ключевых кадров.
    readonly int numberFrames = 0;
    // Адресные координаты изображений для ключевых кадров.
    readonly string[] uriSources = {
        "img/rocket/rocket-main-anim-frame0.png",
        "img/rocket/rocket-main-anim-frame1.png",
        "img/rocket/rocket-main-anim-frame2.png"
    };

    public readonly Size maxImgSize = new(0, 0);
    private readonly Panel? _parent = null;

    // Инициализация полей и запуск анимации.
    public AnimRocket(Panel parent)
    {
        _parent = parent;
        numberFrames = uriSources.Length;
        maxImgSize = ComputeMaxImageSize();

        Animation();
    }

    // Вращение ракеты для расположения вдоль оси перемещения.
    public void Rotate(double angle)
    {
        if (_parent != null)
        {
            // Создаем трансформацию вращения.
            RotateTransform rt = new(angle);
            // Вращаем контейнер изображения.
            _parent.Children[0].RenderTransform = rt;
        }
    }

    // Метод бесконечной анимации по ключевым кадрам.
    private void Animation()
    {
        // Создание объекта анимации с заданными свойствами.
        ObjectAnimationUsingKeyFrames objectAnimation = new()
        {
            Duration = new Duration(TimeSpan.FromSeconds(0.3)),
            RepeatBehavior = RepeatBehavior.Forever
        };

        // Создание ключевых кадров.
        for (int i = 0; i < numberFrames; i++)
        {
            // Количество времени на показ данного кадра.
            double quota = (double)i / numberFrames;
            // В коллекцию ключевых кадров добавляется кадр
            // со своим изображением и временем показа.
            objectAnimation.KeyFrames.Add(
                new DiscreteObjectKeyFrame()
                {
                    Value = new BitmapImage(new Uri(uriSources[i], UriKind.Relative)),
                    KeyTime = KeyTime.FromPercent(quota)
                }
            );
        }

        // Объект для анимации. Создается локально,
        // но жизненный цикл до закрытия приложения.
        Image image = new()
        {
            Margin = new Thickness(0, 0, 0, 0),
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center,
            Stretch = Stretch.None,
            // Изображение должно вращаться относительно
            // своей центральной точки.
            RenderTransformOrigin = new Point(0.5, 0.5)
        };

        _parent?.Children.Add(image);
        // Непосредственно запуск анимации космической ракеты.
        image.BeginAnimation(Image.SourceProperty, objectAnimation);
    }

    // Получение максимальных размеров объекта
    private Size ComputeMaxImageSize()
    {
        List maxW = new();
        List maxH = new();

        // Вычисляем габаритный размер анимации
        for (int i = 0; i < numberFrames; i++)
        {
            BitmapImage bmp = new(new Uri(uriSources[i], UriKind.Relative));
            maxW.Add(bmp.Width);
            maxH.Add(bmp.Height);
        }

        // Отправляем наибольший размер из всех полученных.
        return new Size(maxW.Max(x => x), maxH.Max(y => y));
    }
}

Анимация перемещения ракеты DoubleAnimation

Перемещения ракеты, а точнее главного окна приложения, основано на объекте анимации типа DoubleAnimation, который обеспечивает поступательное движение ракеты между двумя указанными точками и автоматически вычисляет промежуточные положения методом линейной интерполяции. При этом позиция окна приложения, а значит и ракеты, вычисляется относительно его центра.

Движения ракеты происходят между двумя случайными точками периметра экрана. Для повышения разнообразия перемещений каждая сторона периметра экрана делится на определённое количество точек, в данном исходном коде на каждую сторону 10 точек. Метод ComputeCoordinates() выдает случайные координаты старта и финиша ракеты между случайно выбранными сторонами периметра экрана.

Перед началом движения вычисляется вектор направления и его угол относительно базового вектора. Далее, при помощи метода animObject.Rotate(angle) анимированное изображение космической ракеты разворачивается точно по оси движения между двумя точками.

После события окончания перемещения локальный таймер, через указанную паузу, запускает анимацию перемещения вновь с новыми, случайными координатами и так далее, вплоть до закрытия приложения. Ракета потешно летает поверх всех приложений радуя пользователя своим изяществом 😊.

// Анимация перемещения окна
private void AnimationWindow()
{
    // Габариты перемещения основного окна
    double minX = 0;
    double maxX = SystemParameters.PrimaryScreenWidth;
    double minY = 0;
    double maxY = SystemParameters.PrimaryScreenHeight;

    // Получение координат перемещения объекта 
    (double fromX, double toX, double fromY, double toY) = ComputeCoordinates(minX, maxX, minY, maxY);

    // Вычисляем угол поворота объекта относительно горизонтали.
    Vector vectorBase = new(30, 0);
    Vector vectorDirection = new(toX - fromX, toY - fromY);
    double angle = Vector.AngleBetween(vectorBase, vectorDirection);
    animObject.Rotate(angle);


    // Координата Х расположения окна на экране.
    DoubleAnimation posLeft = new();
    // Координата Y расположения окна на экране.
    DoubleAnimation posTop = new();

    // Характеристики поведения координат идентичны.
    posLeft.Duration = posTop.Duration = new Duration(TimeSpan.FromSeconds(_duration));
    posLeft.FillBehavior = posTop.FillBehavior = FillBehavior.Stop;

    // Позиция окна вычисляется относительно его центра.
    posLeft.From = fromX - Width / 2;
    posLeft.To = toX - Width / 2;
    posTop.From = fromY - Height / 2;
    posTop.To = toY - Height / 2;


    // Анимация появления окна
    double opacityShow = 0.2;
    DoubleAnimation opacityAnimationShow = new()
    {
        From = 0.0,
        To = 1.0,
        Duration = new Duration(TimeSpan.FromSeconds(opacityShow)),
        FillBehavior = FillBehavior.Stop
    };


    // Анимация исчезновения окна.
    double opacityHide = 0.2;
    // Защита от отрицательного временного промежутка.
    double beginTimeHide = _duration >= (opacityShow + opacityHide) ? (_duration - (opacityShow + opacityHide)) : 0.0;
    DoubleAnimation opacityAnimationHide = new()
    {
        From = 1.0,
        To = 0.0,
        Duration = new Duration(TimeSpan.FromSeconds(opacityHide)),
        BeginTime = TimeSpan.FromSeconds(beginTimeHide),
        FillBehavior = FillBehavior.Stop
    };

    // Событие окончания перемещения
    posLeft.Completed += (sender, eArgs) =>
    {
        // Запуск новой анимации
        DispatcherTimer timer = new() { Interval = TimeSpan.FromSeconds(2) };
        timer.Tick += (sender, args) =>
        {
            timer.Stop();

            AnimationWindow();
        };

        timer.Start();
    };

    // Событие появления окна
   opacityAnimationShow.Completed += (sender, eArgs) =>
    {
        this.Opacity = 1;

        // Повторная анимация прозрачности запускается точно после окончания первой,
        // иначе управление одним свойством двумя анимациями создает артефакты.
        BeginAnimation(OpacityProperty, opacityAnimationHide);
    };

    // Окончание исчезновения.
    opacityAnimationHide.Completed += (sender, eArgs) =>
    {
        Opacity = 0;
    };

    // Запуск анимаций
    BeginAnimation(OpacityProperty, opacityAnimationShow);
    BeginAnimation(LeftProperty, posLeft);
    BeginAnimation(TopProperty, posTop);
}

Анимация появления и исчезновения ракеты

Начальная и конечная координата находятся на периметре экрана. Для плавного вхождения и исчезновения ракеты применяются две анимации прозрачности: при старте ракеты и симуляции выхода ракеты за пределы экрана.

Для появления и исчезновения ракеты применяется анимация свойства главного окна Opacity с помощью объектов DoubleAnimation. Анимации перемещения posLeft, posTop и появления ракеты opacityAnimationShow запускаются одновременно, смотрите код выше.

Анимация исчезновения opacityAnimationHide запускается только после завершения работы opacityAnimationShow. Запускается с отложенным началом, чтобы исчезание происходило логично в конце подлета ракеты к финишной точке. Можно было бы запустить все анимации одновременно, но параллельное управление свойством Opacity приложения двумя анимациями, даже если одна с отложенным началом, может вызвать нежелательные визуальные артефакты.

Вычисление случайных координат перемещения

Вычисление координат перемещения не такая уж и простая задача для такого шуточного приложения. Для большей забавности ракета должна всегда пересекать экран, каждый раз начиная полет с новыми координатами старта и финиша.

Для получения приемлемого результата периметр экрана виртуально делится на стороны, для каждой стороны выбирается определенное количество координатных точек, при запросе из этого количества выбирается случайная точка. Именно так работает метод
GetPointMove(double minX, double maxX, double minY, double maxY, int variant).

Затем в методе
ComputeCoordinates(double minX, double maxX, double minY, double maxY) указывается случайный номер варианта выбора стороны, и мы получаем случайные координаты перемещения ракеты между смежными или противоположными сторонами экрана, исключая выбор координат только на одной стороне.

// Подготовка случайных координат для перемещения анимации объекта.
private (double fromX, double toX, double fromY, double toY) 
    ComputeCoordinates(double minX, double maxX, double minY, double maxY)
{
    // Подготовка для выбора случайных координат начала и конца перемещения.
    List randPos = new() { 0, 1, 2, 3 };
    randPos = randPos.OrderBy(a => random.Next()).ToList();

    // Получение случайной координаты старта.
    Point start = GetPointMove(minX, maxX, minY, maxY, randPos[0]);
    double fromX = start.X;
    double fromY = start.Y;

    // Получение координаты финиша.
    Point end = GetPointMove(minX, maxX, minY, maxY, randPos[1]);
    double toX = end.X;
    double toY = end.Y;

    // Получение длины вектора перемещения
    Vector vecDirection = new()
    {
        X = toX - fromX,
        Y = toY - fromY,
    };

    // Скорость перемещения будет всегда одинакова,
    // независимо от расстояния перемещения.
    _duration = vecDirection.Length / 1100;


    return (fromX, toX, fromY, toY);
}

// Получение одной случайной координаты перемещения 
private Point GetPointMove(double minX, double maxX, double minY, double maxY, int variant)
{
    // Количество координатных точек на выбранную сторону экрана.
    int num = 10;

    // Список хранения точек выбранной стороны периметра.
    List points = new();


    // Варианты получения координатных точек.
    switch (variant)
    {
        case 0:
            // Верхняя сторона экрана.
            for (int i = 0; i <= num; i++)
            {
                Point p = new() { X = maxX / num * i, Y = minY };
                points.Add(p);
            }
            break;

        case 1:
            // Нижняя сторона экрана
            for (int i = 0; i <= num; i++)
            {
                Point p = new() { X = maxX / num * i, Y = maxY };
                points.Add(p);
            }
            break;

        case 2:
            // Левая сторона экрана
            for (int i = 0; i <= num; i++)
            {
                Point p = new() { X = minX, Y = maxY / num * i };
                points.Add(p);
            }
            break;

        case 3:
            // Правая сторона экрана
            for (int i = 0; i <= num; i++)
            {
                Point p = new() { X = maxX, Y = maxY / num * i };
                points.Add(p);
            }
            break;
    }

    // Случайный выбор одной точки для возрата.
    return points[random.Next(0, points.Count)];
}

Доработка приложения

Для настоящей программы-шутки требуется небольшая доработка кода приложения. Для отложенного запуска анимации ракеты необходимо добавить еще один таймер. Кроме того, необходим начальный интерфейс запуска анимации, где можно настроить время до запуска анимации и количества повторений до закрытия программы. Также желательно предусмотреть механизм для нормального закрытия приложения: горячие клавиши или действия кнопками мыши.

Исходник C#

Прикрепленный архив содержит исходный код анимационной программы-шутки полета космической ракеты на экране монитора. Исходник написан на языке C# в MS Visual Studio 2022, программная платформа .NET6. Исходник включает исполнительный файл программы для быстрого тестирования.

Скачать исходник

Тема: «Анимация полета ракеты на C# WPF» Язык программирования C# WpfAppRocket-vs17.zip Размер:165 КбайтЗагрузки:44