欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

WPF实现三种粒子背景动画效果

程序员文章站 2022-05-29 07:51:37
...

WPF中粒子特效的实现多数是由DispatcherTimer计时器驱动,绑定事件:每1/60s执行一帧动作来实现的,这里主要参考大神的粒子群3D动画转圈示例博文,实现效果如下:

WPF实现三种粒子背景动画效果

3D相关知识可以参考WPF 3D 知识点大全以及实例

实现步骤:

1、由于要实现的效果是在平面坐标下,所以将Particle类中的3D坐标/向量修改为2D坐标/向量

    public class Particle
    {
        public double Decay;//消散系数
        public double Life; //存在时长
        public Point Position;//位置
        public double Size;//尺寸
        public double StartLife;//开始时长
        public double StartSize;//开始尺寸
        public Vector Velocity;//位移数
    }

2、ParticleSystem类中加入Dispose方法,在改变RadioButton选项时调用;同时由于有些效果中X轴方向不需要运动,所以修改SpawnParticle方法

        public void SpawnParticle(Point position, double speedX, double speedY, double size, double life)
        {
            if (_particleList.Count > MaxParticleCount)
                return;
            var p = new Particle
            {
                Position = position,
                StartLife = life,
                Life = life,
                StartSize = size,
                Size = size
            };

            var x = 1.0f - (float)_rand.NextDouble() * 2.0f;
            var y = 1.0f - (float)_rand.NextDouble() * 2.0f;

            var v = new Vector(x, y);
            v.Normalize();
            v.X *= ((float)_rand.NextDouble() + 0.25f) * (float)speedX;
            v.Y *= ((float)_rand.NextDouble() + 0.25f) * (float)speedY;

            p.Velocity = new Vector(v.X, v.Y);
            p.Decay = 1.0f;
            _particleList.Add(p);
        }

        public void Dispose()
        {
            _particleList.Clear();
        }

3、ParticleSystemManager类中做同样的修改

        public void SpawnParticle(Point position, double speedX, double speedY, Color color, double size, double life)
        {
            try
            {
                var ps = _particleSystems[color];
                ps.SpawnParticle(position, speedX, speedY, size, life);
            }
            catch
            {
                // ignored
            }
        }

        public void Dispose()
        {
            foreach (var ps in _particleSystems.Values)
            {
                ps.Dispose();
            }
        }

4、主窗体xaml

<Window x:Class="ParticlesDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ParticlesDemo"
        mc:Ignorable="d"
        Title="粒子效果" 
        Height="450" Width="800"
        WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
    <Grid x:Name="mainGrid" Background="Black">
        <Viewport3D Name="World">
            <Viewport3D.Camera>
                <OrthographicCamera Position="0,0,50" LookDirection="0,0,-50" UpDirection="0,1,0" Width="128" />
            </Viewport3D.Camera>
            <Viewport3D.Children>
                <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <Model3DGroup x:Name="WorldModels">
                            <AmbientLight Color="#FFFFFFFF" />
                        </Model3DGroup>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
            </Viewport3D.Children>
        </Viewport3D>
        <DockPanel Background="#01000000">
            <Label Visibility="Visible" Name="FrameRateLabel" DockPanel.Dock="Top" Foreground="Yellow" Content="FPS: 0" />
            <StackPanel Orientation="Horizontal">
                <RadioButton x:Name="rdoSnowflake" Content="雪花" Foreground="White" Cursor="Hand" IsChecked="True" Checked="RadioButton_Checked"></RadioButton>
                <RadioButton x:Name="rdoNeonLights" Content="霓虹" Foreground="Red" Cursor="Hand" Checked="RadioButton_Checked"></RadioButton>
                <RadioButton x:Name="rdoTechnology" Content="黑客" Foreground="Green" Cursor="Hand" Checked="RadioButton_Checked"></RadioButton>
            </StackPanel>
        </DockPanel>
    </Grid>
</Window>

后台交互逻辑:

        private readonly ParticleSystemManager _pm;
        private readonly Random _rand;
        private int _currentTick;
        private double _elapsed;
        private int _frameCount;
        private double _frameCountTime;
        private int _frameRate;
        private int _lastTick;
        private Point _spawnPoint;
        private double _totalElapsed;
        private DispatcherTimer _frameTimer;

        public MainWindow()
        {
            InitializeComponent();

            _frameTimer = new DispatcherTimer();
            _frameTimer.Tick += OnFrame;
            _frameTimer.Interval = TimeSpan.FromSeconds(1.0 / 60.0);
            _frameTimer.Start();

            _lastTick = Environment.TickCount;

            _pm = new ParticleSystemManager();

            WorldModels.Children.Add(_pm.CreateParticleSystem(1000, Colors.White));
            WorldModels.Children.Add(_pm.CreateParticleSystem(200, Colors.Red));
            WorldModels.Children.Add(_pm.CreateParticleSystem(200, Colors.Orange));
            WorldModels.Children.Add(_pm.CreateParticleSystem(200, Colors.Silver));
            WorldModels.Children.Add(_pm.CreateParticleSystem(4000, Colors.Green));

            _rand = new Random(GetHashCode());

            KeyDown += Window_KeyDown;
            Cursor = Cursors.None;
        }

        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Escape)
                Close();
        }

        private void RadioButton_Checked(object sender, RoutedEventArgs e)
        {
            RadioButton rdo = sender as RadioButton;
            if(rdo != null)
            {
                switch (rdo.Name)
                {
                    case "rdoSnowflake":
                        this.mainGrid.Background = new ImageBrush(new BitmapImage(new Uri("pack://application:,,,/Images/snowbg.jpg")));
                        break;
                    case "rdoNeonLights":
                        this.mainGrid.Background = new ImageBrush(new BitmapImage(new Uri("pack://application:,,,/Images/streetbg.jpg")));
                        break;
                    case "rdoTechnology":
                        this.mainGrid.Background = new ImageBrush(new BitmapImage(new Uri("pack://application:,,,/Images/hacker.jpg")));
                        break;
                    default:
                        this.mainGrid.Background = new SolidColorBrush(Colors.Black);
                        break;
                }
            }

            if (_frameTimer == null) return;
            _frameTimer.Stop();
            _pm.Dispose();
            _frameTimer.Start();
        }

        private void OnFrame(object sender, EventArgs e)
        {
            // Calculate frame time;
            _currentTick = Environment.TickCount;
            _elapsed = (_currentTick - _lastTick) / 1000.0;
            _totalElapsed += _elapsed;
            _lastTick = _currentTick;

            _frameCount++;
            _frameCountTime += _elapsed;
            if (_frameCountTime >= 1.0)
            {
                _frameCountTime -= 1.0;
                _frameRate = _frameCount;
                _frameCount = 0;
                FrameRateLabel.Content = "FPS: " + _frameRate + "  Particles: " + _pm.ActiveParticleCount;
            }

            _pm.Update((float)_elapsed);

            if (this.rdoSnowflake.IsChecked == true)
            {
                SnowflakeEffect();
            }
            else if(this.rdoNeonLights.IsChecked == true)
            {
                NeonLightsEffect();
            }
            else if(this.rdoTechnology.IsChecked == true)
            {
                TechnologyEffect();
            }
        }

        /// <summary>
        /// 雪花效果
        /// </summary>
        private void SnowflakeEffect()
        {
            _spawnPoint = new Point(0.0, 50);
            _pm.SpawnParticle(_spawnPoint, 10.0, 10.0, Colors.White, _rand.NextDouble() * 6, 20 * _rand.NextDouble());
        }

        /// <summary>
        /// 霓虹效果
        /// </summary>
        private void NeonLightsEffect()
        {
            _spawnPoint = new Point(_rand.NextDouble() * this.ActualWidth, -_rand.NextDouble() * 30);
            _pm.SpawnParticle(_spawnPoint, 0, 0, Colors.Red, _rand.NextDouble() * 16, 20 * _rand.NextDouble());
            _spawnPoint = new Point(_rand.NextDouble() * this.ActualWidth, -_rand.NextDouble() * 30);
            _pm.SpawnParticle(_spawnPoint, 0, 0, Colors.Orange, _rand.NextDouble() * 16, 20 * _rand.NextDouble());
            _spawnPoint = new Point(_rand.NextDouble() * this.ActualWidth, -_rand.NextDouble() * 30);
            _pm.SpawnParticle(_spawnPoint, 0, 0, Colors.Silver, _rand.NextDouble() * 16, 20 * _rand.NextDouble());

            _spawnPoint = new Point(-_rand.NextDouble() * this.ActualWidth, -_rand.NextDouble() * 30);
            _pm.SpawnParticle(_spawnPoint, 0, 0, Colors.Red, _rand.NextDouble() * 16, 20 * _rand.NextDouble());
            _spawnPoint = new Point(-_rand.NextDouble() * this.ActualWidth, -_rand.NextDouble() * 30);
            _pm.SpawnParticle(_spawnPoint, 0, 0, Colors.Orange, _rand.NextDouble() * 16, 20 * _rand.NextDouble());
            _spawnPoint = new Point(-_rand.NextDouble() * this.ActualWidth, -_rand.NextDouble() * 30);
            _pm.SpawnParticle(_spawnPoint, 0, 0, Colors.Silver, _rand.NextDouble() * 16, 20 * _rand.NextDouble());
        }

        /// <summary>
        /// 黑客效果
        /// </summary>
        private void TechnologyEffect()
        {
            _spawnPoint = new Point(0, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(10, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(-10, -_rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(15, -_rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(-20, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(30, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(-35, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(45, -_rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(-40, -_rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(-50, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
            _spawnPoint = new Point(50, _rand.NextDouble() * 50);
            _pm.SpawnParticle(_spawnPoint, 0, 5.0, Colors.Green, _rand.NextDouble(), 40 * _rand.NextDouble());
        }