前言
在图形编程中,粒子系统被广泛应用于游戏开发、电影特效以及各种交互式应用中。通过模拟大量微小粒子的行为,粒子系统能够创造出诸如火焰、雨滴、雪花等丰富的视觉效果。
尽管有许多高级图形引擎(如Unity、Unreal Engine)可以实现这些效果,但在某些情况下,使用轻量级的C#和GDI+也能达到类似的效果,并且具有更高的灵活性和可控性。
正文
C#语言能不能画一些动画特效呢?闲来无事,特过一把编程瘾。
一共写了6个例子特效动画,界面如下,程序在文末供下载。
拿一个粒子效果“鼓泡泡效果”的类讲解,其他类似:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CSharpGDI
{
/// <summary>
/// 粒子效果2
/// </summary>
internal class Particle2
{
public struct ParticleObj
{
public int px;//圆点坐标x
public int py;//圆点坐标y
public int d;//直径
public int c;//颜色标识 1有颜色,0无色
public int a;//透明度
public Brush b;//画刷
public int vx; //原x
public int vy; //原y
public int vd; //原直径
}
static List<ParticleObj> particles = new List<ParticleObj>();
static int speed =1;
static Bitmap bmp = null;
static Graphics g = null;
/// <summary>
/// 初始化
/// </summary>
/// <param name="w">宽</param>
/// <param name="h">高</param>
/// <param name="span">间距</param>
public static void Init(int w,int h,int size=15,int span=5)
{
particles.Clear();
Random rnd=new Random();
int r0 = Math.Min(w / size, h / size);
for (var x = 0; x < w / r0; x++)
{
for (var y = 0; y < h / r0; y++)
{
ParticleObj p = new ParticleObj()
{
px = x * r0 + (r0 - span)/2,
py = y * r0 + (r0 - span)/2,
d = r0-span,
c = rnd.Next(2),
a = rnd.Next(100, 255)
};
p.vx = p.px;
p.vy = p.py;
p.vd = p.d;
Brush b = new SolidBrush(Color.FromArgb(p.a, 255, 255, 255));
if (p.c == 1)
b = new SolidBrush(Color.FromArgb(p.a,72,209,204));
p.b = b;
particles.Add(p);
}
}
bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb);
g = Graphics.FromImage(bmp);
}
/// <summary>
/// 动画
/// </summary>
/// <param name="mouseX">鼠标x坐标</param>
/// <param name="mouseY"></param>
/// <param name="w">宽</param>
/// <param name="h">高</param>
/// <param name="R">圆的半径</param>
public static Bitmap Start(int mouseX, int mouseY, int w, int h, int R=150)
{
if (particles.Count == 0)
Init(w,h);
g.Clear(Color.Black);
g.SmoothingMode = SmoothingMode.AntiAlias;
Random rnd = new Random();
for (var i = 0; i < particles.Count; i++)
{
var p = particles[i];
if (R * R > (p.px - mouseX) * (p.px - mouseX) + (p.py - mouseY) * (p.py - mouseY))
{
if (p.px > mouseX)
p.px += speed ;
if (p.px < mouseX)
p.px -= speed;
if (p.py > mouseY)
p.py += speed ;
if (p.py < mouseY)
p.py -= speed;
if (rnd.Next(2) == 1)
{
p.d = (p.d+speed*5 >w/2?w/2:p.d+speed*5);
}
else
{
p.d =(p.d- speed*5<=0?10:p.d-speed*5);
}
}
else
{
if (p.px > p.vx)
p.px = p.px - speed < p.vx ? p.vx : p.px - speed;
if (p.px < p.vx)
p.px = p.px + speed > p.vx ? p.vx : p.px + speed;
if (p.py > p.vy)
p.py = p.py - speed < p.vy ? p.vy : p.py - speed;
if (p.py < p.vy)
p.py = p.py + speed > p.vy ? p.vy : p.py + speed;
if (p.d > p.vd)
{
p.d -= speed;
}
if (p.d < p.vd)
{
p.d += speed;
}
}
particles[i] = p;
g.FillEllipse(p.b, p.px - p.d / 2, p.py - p.d / 2, p.d, p.d);
}
return bmp;
}
}
}
效果如下:
思想很简单,Init() 方法根据屏幕大小和相关粒子大小参数产生粒子对象数组particles,然后在Onpait事件中反复调用Start()绘制粒子即可。其中可以根据鼠标位置,改变粒子大小和位置。
当点击"鼓泡泡"按钮事件:其实核心只是调用了pictureBox1.Refresh()方法而已,其他逻辑都不重要。
private void btnParticle2_Click(object sender, EventArgs e)
{
if (btnParticle2.Text.Contains("停止"))
{
btnParticle2.Text = "鼓泡泡效果";
animations.Clear();
}
else
{
animations.Clear();
ResetButtons();
btnParticle2.Text = "停止鼓泡泡效果";
animations.Add("鼓泡泡");
ReStartCalFps();
pictureBox1.Refresh();
}
}
pictureBox1.Refresh()方法会触发pictureBox1的pictureBox1_Paint事件,在该事件中添加逻辑:
//重绘事件
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if(animations.Count==0) return;
if (animations.Contains("抹纱窗"))
{
currbmp=Particle1.Start(mousePoint.X,mousePoint.Y,pictureBox1.Width,pictureBox1.Height,100,V);
pictureBox1.Image = currbmp;
CalFps();
}
if (animations.Contains("鼓泡泡"))
{
currbmp = Particle2.Start(mousePoint.X, mousePoint.Y, pictureBox1.Width, pictureBox1.Height,150);
pictureBox1.Image = currbmp;
CalFps();
}
……
}
其他几个效果展示:
文字粒子效果:
爱心效果:
写完之后感受就是,C#也是可以写出炫酷的粒子效果的,而且不卡顿很丝滑。
其中几个关键点:
1、窗体设置双缓存:
public Form1()
{
DoubleBuffered = true; //设置双缓冲
InitializeComponent();
}
2、在Paint事件中重绘粒子,不要在While(true)之类的循环里无间隔调用。Winform中的Paint事件就相当于JavaScript中的requestAnimationFrame事件。
3、每次重绘调用的方法统一返回一张Bitmap图片,换句话说就是把全部的粒子画到一张Bitmap中,不能直接用pictureBox1.CreateGraphics()的Graphics对象来画粒子。否则会出现卡顿。而且这个Bitmap要用公共变量,不能每次调用都重新创建,否则内存会疯涨。
4、取像素点的数据要用内存拷贝法,不可直接调用img.GetPixel(i ,j).R ,否则性能极差,粒子一多也会出现卡顿。
内存拷贝法取像素点代码如下:注意其中的Data.Stride属性,用LockBits返回的像素数据每一行会有个补全操作,若采用Format24bppRgb格式也就是每个像素占用24位,3个字节分别表示B,G,R,且每行长度为Stride,不足Stride的会补全。
Bitmap bmp=new Bitmap(w,h, PixelFormat.Format24bppRgb);
BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int length = h * data.Stride;
byte[] RGB = new byte[length];
System.IntPtr Scan0 = data.Scan0;
System.Runtime.InteropServices.Marshal.Copy(Scan0, RGB, 0, length);
for (int y = 0; y < h; y++)
{
int index = y * data.Stride;
for (int x = 0; x < w; x++)
{
if (x % span == 0 && y % span == 0)
{
particles.Add(new ParticleObj()
{
x = x,
y = y,
vx = x,
vy = y,
vspeed = 2
});
//改变颜色。
RGB[index + 3 * x] = 255;
RGB[index + 3 * x+1] = 255;
RGB[index + 3 * x+2] = 255;
}
}
}
System.Runtime.InteropServices.Marshal.Copy(RGB, 0, Scan0, length);
bmp.UnlockBits(data);
g.DrawImage(bmp,0,0);
程序下载:files.cnblogs.com/files/tuyil…
需要源码请在评论区留言。
总结
通过本文的学习,我们成功实现了六种不同类型的粒子动画特效,并掌握了如何使用C#和GDI+进行高效的图形绘制和动画处理。
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:小y
出处:cnblogs.com/tuyile006/p/18698404
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!