WPF实现三种粒子背景动画效果
程序员文章站
2022-05-29 07:51:37
...
WPF中粒子特效的实现多数是由DispatcherTimer计时器驱动,绑定事件:每1/60s执行一帧动作来实现的,这里主要参考大神的粒子群3D动画转圈示例博文,实现效果如下:
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());
}