用C# GDI编写粒子效果

news/2025/2/4 19:29:37/文章来源:https://www.cnblogs.com/tuyile006/p/18698404

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;//圆点坐标xpublic int py;//圆点坐标ypublic int d;//直径public int c;//颜色标识 1有颜色,0无色public int a;//透明度public Brush b;//画刷public int vx; //原xpublic int vy; //原ypublic 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()的对象来画粒子。否则会出现卡顿。而且这个Bitmap要用公共变量,不能每次调用都重新创建,否则内存会疯涨。

4.取像素点的数据要用内存拷贝法,不可直接调用img.GetPixel(i ,j).R   ,否则性能极差,也会出现卡顿。

内存拷贝法取像素点代码如下:

 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);

程序下载:Demo

需要源码请在评论区留言。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/878682.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你说得对,但是我怎么没学过后缀科技啊???

后缀数组 (SA) 后缀数组(SA,Suffix Array)最基础的应用是,可以将给定串 \(S\) 的所有后缀串排序。 一点定义:\(sa_i\) 表示第 \(i\) 小的后缀的编号,\(rk_i\) 表示后缀 \([i,n]\) 的排名。显然 \(sa_{rk[i]}=rk_{sa[i]}=i\)。 后缀数组就是 \(sa\) 数组,我们可以利用后缀…

2025.2.4 鲜花

交通网络 题解?hzoi898 交通网络 题解?Underground 是那个纯音乐啦~Ans 注意到:一个只能说真话,一个绝不说假话。这题有四样读法,你知道么?出一个毒瘤 ds 的最好方式就是把序列问题直接出到树上,考察选手树剖能力。正确的题意:给定一棵树,在时刻 \([tl, tr]\) 链加,查…

云手机和模拟器究竟有什么区别?一分钟带你理清楚

云手机和模拟器究竟有什么区别?一分钟带你理清楚概念 云手机:是在云端运行的虚拟手机,基于云计算技术和端云一体虚拟化技术,通过云服务器实现云服务。具备真实的ARM硬件,和真实手机的架构几乎一样,用户通过网络连接,在不同终端设备上远程操作,可轻松安装、管理APP。 模…

函数笔记

想了很久还是来更这个了。 0x00 一次函数 一般式 \(y=kx+b\),特殊的,当 \(b=0\),称作正比例函数。 图象 一次函数的图像是过\(\left(0,b\right)\)、\(\left( -\dfrac{b}{k},0\right)\)的直线。 而正比例函数的图像则是过原点的一条直线。 根据参数的符号,我们可以确定图象…

java基础(中)

java基础 Scanner对象Scanner类是用于获取用户输入的import java.util.Scanner;public class Hello {public static void main(String[] args){// 创建一个scanner对象来接收键盘数据Scanner scanner = new Scanner(System.in);System.out.println("Enter your name: &qu…

java基础1

java基础2 Scanner对象Scanner类是用于获取用户输入的import java.util.Scanner;public class Hello {public static void main(String[] args){// 创建一个scanner对象来接收键盘数据Scanner scanner = new Scanner(System.in);System.out.println("Enter your name: &q…

PCIe扫盲——TLP Header详解(三)

Completions Completions的TLP Header的格式如下图所示:这里来解释一下Completion Status Codes000b (SC) Successful Completion:表示请求(Request)被正确的处理; 001b (UR) Unsupported Request:表示请求是非法的或者不能被Completer所识别的。在PCIe V1.1以及之后的版…

03-requests库和session

接口测试经常会用到抓包工具,用来抓取接口测试中发送的HTTP请求信息和接收的响应信息。然后查看里面的具体内容。 fiddler是一款常用的HTTP抓包工具,抓包原理是代理式抓包。 Filters设置过滤项Inspectors查看请求消息和响应消息,点击Raw查看原始的请求消息和响应消息 首先客…

探索Java动态代理的奥秘:JDK vs CGLIB

动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。一、关于动态代理 1.1 简介 动态代理是一种在 运行时动态生成代理类 的技术,无需手动编写代理类代码。它通过…

Esp32s3(立创实战派)移植LVGL

Esp32s3(立创实战派)移植LVGL 移植: 使用软件EEZ studio 创建工程选择带EEZ Flow的,可以使用该软件更便捷的功能根据屏幕像素调整画布为320*240复制ui文件至工程 将生成的ui文件夹复制到main文件夹同级目录(ui文件夹在工程文件src中) 工程结构:修改声明头文件路径 首先将u…

字节系AI代码编辑器Trae:免费双模大模型+「Builder模式」

字节跳动技术团队近日正式推出AI代码编辑器Trae(官网:https://www.trae.ai/),这款定位为「自适应AI IDE」的开发工具,凭借其创新功能组合在技术圈引发关注。作为面向下一代开发者的智能编程平台,Trae正在重新定义人机协作的编码体验。 一、核心亮点解析双模大模型免费开放…

RocketMQ实战—3.基于RocketMQ升级订单系统架构

大纲 1.基于MQ实现订单系统核心流程的异步化改造 2.基于MQ实现订单系统和第三方系统的解耦 3.基于MQ实现将订单数据同步给大数据团队 4.秒杀系统的技术难点以及秒杀商详页的架构设计 5.基于MQ实现秒杀系统的异步化架构 6.全面引入MQ的订单系统架构的思维导图1.基于MQ实现订单系…