基于 C++ 11 和 EasyX库 实现。
主要在 Krissi 大佬 代码基础上,修改和添加一些功能。
功能:
- 能模拟一些简单的粒子行为。
- 添加新的行为容易。
不足:
- 功能过于简陋。
- 公式没有参考严谨的物理与数学。
本人代码萌新,虽然有注释,但是存在不明白或错误之处,欢迎大家一起讨论和交流。
二. 截图
菜单中文翻译:
状态
- 平均帧数
- 帧时间
- 粒子数 / 粒子总数
鼠标
- 移动:搅动粒子
- 左击:聚集粒子
- 右击:吹散粒子
- 滚轮:缩放粒子
键盘
- Esc:退出系统
- Tab:隐藏菜单
- Space:暂停系统
- Enter:重新模拟
特效(开启或者关闭)
- A:显示鼠标落点
- B:反弹墙壁
- C:速度和大小关联
- D:吹散
- E:聚集
- F:搅动
- G:摩擦力
- H:漩涡
- I:随机移动
- J:缩放
- K:轨迹消失
- L:重力
- M:生命周期
C++ 源码有两个文件,ParticleSystem.h 和 ParticleSystem.cpp
我先介绍如何使用
Main.cpp
#include "ParticleSystem.h"
int main()
{
// step1:初始化粒子系统
FParticleSystem ParticleSystem(1000, 600);
// step2:生成自定义的发射器
FEmitter EmitterOne;
FParticleSystem::EmitterManager.BindEmitter(EmitterOne) // 绑定要处理的发射器
.SetNum(333) // 进行一系列参数设置
.SetStartTime(0.0f)
.SetColor(RED)
.SetRadiusMin(1.0f)
.SetRadiusMax(1.5f)
.SetLifeTimeMin(5.0f)
.SetLifeTimeMax(10.0f)
.GenerateParticles(); // 最后完成粒子的生成
FEmitter EmitterTwo;
FParticleSystem::EmitterManager.BindEmitter(EmitterTwo)
.SetStartTime(1.0f)
.SetNum(333)
.SetColor(RAND)
.SetRadiusMin(2.0f)
.SetRadiusMax(3.0f)
.SetLifeTimeMin(0.1f)
.SetLifeTimeMax(5.0f)
.GenerateParticles();
FEmitter EmitterThree;
FParticleSystem::EmitterManager.BindEmitter(EmitterThree)
.SetStartTime(1.2f)
.SetNum(333)
.SetColor(RAND)
.SetRadiusMin(2.0f)
.SetRadiusMax(3.0f)
.SetLifeTimeMin(0.1f)
.SetLifeTimeMax(5.0f)
.GenerateParticles();
// step3:将处理好的发射器加入粒子系统
ParticleSystem.AddEmitter(EmitterOne);
ParticleSystem.AddEmitter(EmitterTwo);
ParticleSystem.AddEmitter(EmitterThree);
// step4:粒子系统运行所有的发射器
ParticleSystem.RunEmitters();
return 0;
}
可以看到,只需要设置好发射器的相关参数,就可以轻松的使用粒子系统。
当然上面的例子只是演示,没有做什么效果,萌新初学乍到——_+,期待大佬们给出更好的样例。
而且呃,很多好玩且有用的参数和功能并未实现,不过之后会一直更新的√。
下面是源码!!!
ParticleSystem.h
#pragma once #include#include #include #include #define RAND RGB(255, 255, 255)+1 struct FVector2D { float X, Y; FVector2D operator+(const FVector2D& Other) const { return {X + Other.X, Y + Other.Y}; } FVector2D operator-(const FVector2D& Other) const { return {X - Other.X, Y - Other.Y}; } }; struct FVortex { FVector2D Position; float Radius; float RotateSpeed; }; class Utlis { public: static float GenerateRandomNumber(const float Min, const float Max, const float FractionalNum) { const float PowerNumber = powf(10, FractionalNum); const int MinInt = static_cast (Min * PowerNumber); const int MaxInt = static_cast (Max * PowerNumber); return static_cast (MinInt + GenerateRandomNumber() % (MaxInt - MinInt + 1)) / PowerNumber; } static int GenerateRandomNumber(const int Min, const int Max) { return Min + GenerateRandomNumber() % (Max - Min + 1); } static int GenerateRandomNumber(const int Max) { return GenerateRandomNumber() % (Max + 1); } static float GenerateRandomNumber(const float Max, const float FractionalNum) { const float PowerNumber = powf(10, FractionalNum); const int MaxInt = static_cast (Max * PowerNumber); return static_cast (GenerateRandomNumber() % (MaxInt + 1)) / PowerNumber; } static int GenerateRandomNumber() { return rand(); } template static ElementType GenerateRandomSign() { return static_cast (pow(-1, GenerateRandomNumber() % 2)); } template static ElementType Clamp(ElementType Number, ElementType Min, ElementType Max) { return Number >= Min ? Number <= Max ? Number : Max : Min; } static FVector2D Normalize(const FVector2D& Vector2D) { const float Length = GetLength(Vector2D); return {Vector2D.X / Length, Vector2D.Y / Length}; } static float GetLength(const FVector2D& Vector2D) { return sqrt(Vector2D.X * Vector2D.X + Vector2D.Y * Vector2D.Y); } }; class FWindowManager { private: FWindowManager(const FWindowManager&) = default; FWindowManager(FWindowManager&&) = default; FWindowManager() = default; FWindowManager& operator=(const FWindowManager&) = default; FWindowManager& operator=(FWindowManager&&) = default; public: FWindowManager(float InWidth, float InHeight) { // 创建绘图窗口 initgraph(static_cast (InWidth), static_cast (InHeight)); // 启用批绘图模式 BeginBatchDraw(); } ~FWindowManager() { // 关闭批绘图模式 EndBatchDraw(); // 关闭绘图窗口 closegraph(); } void Flush() const { // 显示缓存的绘制内容 FlushBatchDraw(); } }; struct FParticle { public: COLORREF Color = RED; // 颜色 float PosX = 0.0f; // 坐标 float PosY = 0.0f; float VelX = 0.0f; // 速度 float VelY = 0.0f; float Friction = 0.96f; // 摩擦力 float Radius = 0.4f; // 半径 float LifeTime = 0.5f; // 生命周期 public: float NextX, NextY; // 下一次坐标 bool IsPendingKilled; // 是否进入死亡 FParticle(COLORREF InColor = RED, float InPosX = 0.0f, float InPosY = 0.0f, float InVelX = 0.0f, float InVelY = 0.0f, float InFriction = 0.96f, float InRadius = 0.4f, float InLifeTime = 0.0f) : Color(InColor) , PosX(InPosX) , PosY(InPosY) , VelX(InVelX) , VelY(InVelY) , Friction(InFriction) , Radius(InRadius) , LifeTime(InLifeTime) { NextX = 0.0f; NextY = 0.0f; IsPendingKilled = false; } }; struct FEmitter { public: unsigned int Num = 666; // 粒子总数 float StartTime = 0.0f; // 发射开始时间 COLORREF Color = RAND; // 颜色(默认随机) float StartPosX = 0.0f; // 起始坐标 float StartPosY = 0.0f; float StartVelX = 0.0f; // 起始速度 float StartVelY = 0.0f; float VelocityMin = 0.01f; // 速度最小值 float VelocityMax = 50.0f; // 速度最大值 float FrictionMin = 0.93f; // 摩擦力最小值 float FrictionMax = 0.96f; // 摩擦力最大值 float RadiusMin = 0.40f; // 半径最小值 float RadiusMax = 1.66f; // 半径最大值 float LifeTimeMin = 2.0f; // 生命周期最小值 float LifeTimeMax = 100.0f; // 生命周期最大值 public: std::vector Particles; // 粒子数组 bool IsRender = false; // 是否已经渲染 }; class FEmitterManager { private: FEmitterManager() { Emitter = new FEmitter(); } ~FEmitterManager() { delete Emitter; } FEmitterManager(const FEmitterManager&) = default; FEmitterManager(FEmitterManager&&) = default; FEmitterManager& operator=(const FEmitterManager&) = default; FEmitterManager& operator=(FEmitterManager&&) = default; public: void AvoidWarning() const { Emitter->Num = Emitter->Num + 0; } static FEmitterManager& GetInstance() { static FEmitterManager ParticleManager; return ParticleManager; } FEmitterManager& BindEmitter(FEmitter& InEmitter) { Emitter = &InEmitter; return GetInstance(); } void GenerateParticles() const { std::vector & Particles = Emitter->Particles; Particles = {}; Particles.reserve(Emitter->Num); for (size_t i = 0; i < Emitter->Num; ++i) { Particles.emplace_back( Emitter->Color == RAND ? RGB(Utlis::GenerateRandomNumber(255), Utlis::GenerateRandomNumber(255), Utlis::GenerateRandomNumber(255)) : Emitter->Color, 1000 * 0.5f, 600 * 0.5f, cos(static_cast (i)) * Utlis::GenerateRandomNumber(33.0f, 1.0f), sin(static_cast (i)) * Utlis::GenerateRandomNumber(33.0f, 1.0f), Utlis::GenerateRandomNumber(Emitter->FrictionMin, Emitter->FrictionMax, 2), Utlis::GenerateRandomNumber(Emitter->RadiusMin, Emitter->RadiusMax, 2), Utlis::GenerateRandomNumber(Emitter->LifeTimeMin, Emitter->LifeTimeMax, 2) ); } } std::vector & GetParticles() const { return Emitter->Particles; } public: FEmitterManager& SetIsRender(const bool InIsRender) const { Emitter->IsRender = InIsRender; return GetInstance(); } bool GetIsRender() const { return Emitter->IsRender; } FEmitterManager& SetStartTime(const float InStartTime) const { Emitter->StartTime = Utlis::Clamp(InStartTime, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetStartTime() const { return Emitter->StartTime; } FEmitterManager& SetNum(const unsigned int InNum) const { Emitter->Num = static_cast ( Utlis::Clamp(static_cast (InNum), ValuePositiveMin, ValuePositiveMax)); return GetInstance(); } unsigned int GetNum() const { return Emitter->Num; } FEmitterManager& SetColor(const COLORREF InColor) const { Emitter->Color = Utlis::Clamp(InColor, RGB(0, 0, 0), RAND); return GetInstance(); } COLORREF GetColor() const { return Emitter->Color; } FEmitterManager& SetStartPosX(const float InStartPosX) const { Emitter->StartPosX = Utlis::Clamp(InStartPosX, -ValuePositiveMax, ValuePositiveMax); return GetInstance(); } float GetStartPosX() const { return Emitter->StartPosX; } FEmitterManager& SetStartPosY(const float InStartPosY) const { Emitter->StartPosY = Utlis::Clamp(InStartPosY, -ValuePositiveMax, ValuePositiveMax); return GetInstance(); } float GetStartPosY() const { return Emitter->StartPosY; } FEmitterManager& SetStartVelX(const float InStartVelX) const { Emitter->StartVelX = Utlis::Clamp(InStartVelX, -ValuePositiveMax, ValuePositiveMax); return GetInstance(); } float GetStartVelX() const { return Emitter->StartVelX; } FEmitterManager& SetStartVelY(const float InStartVelY) const { Emitter->StartVelY = Utlis::Clamp(InStartVelY, -ValuePositiveMax, ValuePositiveMax); return GetInstance(); } float GetStartVelY() const { return Emitter->StartVelY; } FEmitterManager& SetVelocityMin(const float InVelocityMin) const { Emitter->VelocityMin = Utlis::Clamp(InVelocityMin, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetVelocityMin() const { return Emitter->VelocityMin; } FEmitterManager& SetVelocityMax(const float InVelocityMax) const { Emitter->VelocityMax = Utlis::Clamp(InVelocityMax, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetVelocityMax() const { return Emitter->VelocityMax; } FEmitterManager& SetFrictionMin(const float InFrictionMin) const { Emitter->FrictionMin = Utlis::Clamp(InFrictionMin, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetFrictionMin() const { return Emitter->FrictionMin; } FEmitterManager& SetFrictionMax(const float InFrictionMax) const { Emitter->FrictionMax = Utlis::Clamp(InFrictionMax, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetFrictionMax() const { return Emitter->FrictionMax; } FEmitterManager& SetRadiusMin(const float InRadiusMin) const { Emitter->RadiusMin = Utlis::Clamp(InRadiusMin, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetRadiusMin() const { return Emitter->RadiusMin; } FEmitterManager& SetRadiusMax(const float InRadiusMax) const { Emitter->RadiusMax = Utlis::Clamp(InRadiusMax, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetRadiusMax() const { return Emitter->RadiusMax; } FEmitterManager& SetLifeTimeMin(const float InLifeTimeMin) const { Emitter->LifeTimeMin = Utlis::Clamp(InLifeTimeMin, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetLifeTimeMin() const { return Emitter->LifeTimeMin; } FEmitterManager& SetLifeTimeMax(const float InLifeTimeMax) const { Emitter->LifeTimeMax = Utlis::Clamp(InLifeTimeMax, ValuePositiveMin, ValuePositiveMax); return GetInstance(); } float GetLifeTimeMax() const { return Emitter->LifeTimeMax; } private: FEmitter* Emitter; public: static const float ValuePercentMax; static const float ValuePercentMin; static const float ValuePositiveMax; static const float ValuePositiveMin; }; class FParticleSystem { public: FParticleSystem(float InWidth = 1540.0f, float InHeight = 840.0f); void AddEmitter(FEmitter& InEmitter); void RunEmitters(); public: void SetDeltaTime(const float InDeltaTime) { CustomDeltaTime = InDeltaTime; } float GetDeltaTime() const { return CustomDeltaTime / 1000.0f; } private: void HandleInput(); void ShowHelp() const; void Render(const float InDeltaTime); float CalculateFps(const float InElapsedTime); float CalculateDeltaTime(const clock_t InCurTime); static void Delay(const clock_t Ms); float LimitFps(const float InElapsedTime) const; private: void OnEffectMouseField(const float InDeltaTime) const; void OnEffectDisappearTrack(const float InDeltaTime) const; void OnEffectReboundWall(const float InDeltaTime, FParticle& InParticle) const; void OnEffectRadiusAboutSpeed(const float InDeltaTime, FParticle& InParticle) const; void OnEffectBlow(const float InDeltaTime, FParticle& InParticle) const; void OnEffectGather(const float InDeltaTime, FParticle& InParticle) const; void OnEffectStir(const float InDeltaTime, FParticle& InParticle) const; void OnEffectFriction(const float InDeltaTime, FParticle& InParticle) const; void OnEffectVortex(const float InDeltaTime, FParticle& InParticle) const; void OnEffectRandomMove(const float InDeltaTime, FParticle& InParticle) const; void OnEffectScaleWithWheel(const float InDeltaTime, FParticle& InParticle) const; void OnEffectGravity(const float InDeltaTime, FParticle& InParticle) const; void OnEffectDeath(const float InDeltaTime, FParticle& InParticle) const; private: const float Width; // 屏幕宽度 const float Height; // 屏幕高度 size_t ParticleTotalNum = 0; // 粒子总数 size_t ParticleCurNum = 0; // 粒子当前数 std::vector Emitters; // 发射器数组 float CurMousePosX = 0.0f; // 当前鼠标坐标 float CurMousePosY = 0.0f; float PrevMousePosX = 0.0f; // 上次鼠标坐标 float PrevMousePosY = 0.0f; float MouseVelX = 0.0f; // 鼠标速度 float MouseVelY = 0.0f; bool IsLeftMouseDown = false; // 是否按下鼠标左键 bool IsRightMouseDown = false; // 是否按下鼠标右键 DWORD* GBuffer = nullptr; // 显示缓冲区指针 float CustomHighFps = 90.0f; // 限制高帧率 float CustomLowFps = 10.0f; // 限制低帧率 float CustomDeltaTime = 1000.0f / 90.0f; // 增量时间(ms) float GraphicsFps = 90.0f; // 当前帧数 float GraphicsTime = 1000.0f / 90.0f; // 当前帧时间 float SimulateTotalTime = 0.0f; // 模拟总时间 FWindowManager WindowManager; // 窗口管理器 public: static FEmitterManager& EmitterManager; // 粒子管理器 private: float RandomDis; // 进入随机距离 float BlowDis; // 进入扩散距离 float GatherDis; // 进入聚集距离 float StirDis; // 进入搅拌距离 private: float RandomSpeed; // 随机速度 float BlowSpeed; // 扩散速度 float GatherSpeed; // 聚集速度 float StirSpeed; // 搅拌速度 private: bool IsShowHelp = true; // 是否显示帮助 bool IsContinueGraphics = true; // 是否继续系统 bool IsContinueSimulate = true; // 是否继续模拟 bool IsRestartSimulate = false; // 是否重新模拟 mutable float ScaleSize = 1.0f; // 滚轮缩放大小 private: // 特效控制,顾名思义 bool IsEffectMouseField = false; bool IsEffectReboundWall = true; bool IsEffectRadiusAboutSpeed = false; bool IsEffectBlow = true; bool IsEffectGather = true; bool IsEffectStir = true; bool IsEffectFriction = true; bool IsEffectVortex = true; bool IsEffectRandomMove = true; bool IsEffectScaleWithWheel = true; bool IsEffectDisappearTrack = true; bool IsEffectGravity = false; bool IsEffectDeath = false; };
ParticleSystem.cpp
#include "ParticleSystem.h" #include#include #include FEmitterManager& FParticleSystem::EmitterManager = FEmitterManager::GetInstance(); const float FEmitterManager::ValuePositiveMax = 10000.0f; const float FEmitterManager::ValuePositiveMin = 0.0001f; const float FEmitterManager::ValuePercentMax = 99.9999f; const float FEmitterManager::ValuePercentMin = 0.0001f; FParticleSystem::FParticleSystem(float InWidth, float InHeight) : Width(InWidth), Height(InHeight), WindowManager(Width, Height) { // 设置随机种子 srand(static_cast (time(nullptr) + 5201314)); // 获取显示缓冲区指针 GBuffer = GetImageBuffer(nullptr); // 初始化鼠标变量 CurMousePosX = PrevMousePosX = InWidth / 2; CurMousePosY = PrevMousePosY = InWidth / 2; // 特效参数 RandomDis = InWidth; BlowDis = InWidth * 0.5f; GatherDis = InWidth * 0.86f; StirDis = InWidth * 0.125f; RandomSpeed = 20.0f; BlowSpeed = 5.0f; GatherSpeed = 26.0f; StirSpeed = 30.0f; // 其他 CustomDeltaTime = 1000.0f / CustomHighFps; } void FParticleSystem::AddEmitter(FEmitter& InEmitter) { Emitters.push_back(InEmitter); EmitterManager.BindEmitter(InEmitter); ParticleCurNum += EmitterManager.GetNum(); ParticleTotalNum += EmitterManager.GetNum(); } void FParticleSystem::RunEmitters() { static clock_t OldTime; static clock_t NewTime; while (IsContinueGraphics) { OldTime = clock(); // 计算增量时间 CalculateDeltaTime(clock()); // 处理输入消息 HandleInput(); // 绘制所需图形 Render(GetDeltaTime()); // 显示缓存绘制 WindowManager.Flush(); NewTime = clock(); // 限制并计算帧数 CalculateFps(LimitFps(static_cast (NewTime - OldTime))); // 计算总时间 SimulateTotalTime += (IsContinueSimulate == true ? static_cast (clock() - OldTime) / 1000.0f : 0.0f); } } void FParticleSystem::HandleInput() { ExMessage Input{}; while (peekmessage(&Input, EM_MOUSE | EM_KEY)) { switch (Input.message) { case WM_MOUSEMOVE: { CurMousePosX = static_cast (Input.x); CurMousePosY = static_cast (Input.y); break; } case WM_MOUSEWHEEL: { ScaleSize = min(max(ScaleSize + Input.wheel / 500.0f, 0.5f), 5.0f); break; } case WM_LBUTTONDOWN: { IsLeftMouseDown = true; break; } case WM_LBUTTONUP: { IsLeftMouseDown = false; break; } case WM_RBUTTONDOWN: { IsRightMouseDown = true; break; } case WM_RBUTTONUP: { IsRightMouseDown = false; break; } case WM_KEYDOWN: { switch (Input.vkcode) { case VK_RETURN: IsRestartSimulate = true; break; case VK_ESCAPE: IsContinueGraphics = false; break; case VK_SPACE: IsContinueSimulate = !IsContinueSimulate; break; case VK_TAB: IsShowHelp = !IsShowHelp; break; case 'A': case 'a': IsEffectMouseField = !IsEffectMouseField; break; case 'B': case 'b': IsEffectReboundWall = !IsEffectReboundWall; break; case 'C': case 'c': IsEffectRadiusAboutSpeed = !IsEffectRadiusAboutSpeed; break; case 'D': case 'd': IsEffectBlow = !IsEffectBlow; break; case 'E': case 'e': IsEffectGather = !IsEffectGather; break; case 'F': case 'f': IsEffectStir = !IsEffectStir; break; case 'G': case 'g': IsEffectFriction = !IsEffectFriction; break; case 'H': case 'h': IsEffectVortex = !IsEffectVortex; break; case 'I': case 'i': IsEffectRandomMove = !IsEffectRandomMove; break; case 'J': case 'j': IsEffectScaleWithWheel = !IsEffectScaleWithWheel; break; case 'K': case 'k': IsEffectDisappearTrack = !IsEffectDisappearTrack; break; case 'L': case 'l': IsEffectGravity = !IsEffectGravity; break; case 'M': case 'm': IsEffectDeath = !IsEffectDeath; break; default: break; } break; } default: break; } } } void FParticleSystem::ShowHelp() const { if (IsShowHelp) { // 不用返回值会报 warning,暂时解决 static int AvoidWarning = 0; static wchar_t Message[255]; static constexpr int TextHeight = 16; int Lines = -1 + AvoidWarning * 0; AvoidWarning = swprintf_s(Message, _T("ttfps: %.1lf"), static_cast (GraphicsFps)); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tttime: %.1lfms"), static_cast (GraphicsTime)); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("ttparticles: %u / %u"), static_cast (ParticleCurNum), static_cast (ParticleTotalNum)); outtextxy(0, TextHeight * (++Lines), Message); ++Lines; outtextxy(0, TextHeight * (++Lines), _T("ttMouse")); outtextxy(0, TextHeight * (++Lines), _T("ttMove: Stir")); outtextxy(0, TextHeight * (++Lines), _T("ttLeft: Gather")); outtextxy(0, TextHeight * (++Lines), _T("ttRight: Blow")); outtextxy(0, TextHeight * (++Lines), _T("ttWheel: Scale")); ++Lines; outtextxy(0, TextHeight * (++Lines), _T("ttKeyboard")); outtextxy(0, TextHeight * (++Lines), _T("tt[Esc]: Exit System")); outtextxy(0, TextHeight * (++Lines), _T("tt[Tab]: Hide Status")); outtextxy(0, TextHeight * (++Lines), _T("tt[Space]: Pause System")); outtextxy(0, TextHeight * (++Lines), _T("tt[Enter]: Restart Simulate")); ++Lines; outtextxy(0, TextHeight * (++Lines), _T("ttSpecial")); AvoidWarning = swprintf_s(Message, _T("tt[A]: Effect - MouseField %s"), IsEffectMouseField == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[B]: Effect - ReboundWall %s"), IsEffectReboundWall == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[C]: Effect - RadiusAboutSpeed %s"), IsEffectRadiusAboutSpeed == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[D]: Effect - Blow %s"), IsEffectBlow == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[E]: Effect - Gather %s"), IsEffectGather == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[F]: Effect - Stir %s"), IsEffectStir == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[G]: Effect - Friction %s"), IsEffectFriction == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[H]: Effect - Vortex %s"), IsEffectVortex == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[I]: Effect - RandomMove %s"), IsEffectRandomMove == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[J]: Effect - Scale %s"), IsEffectScaleWithWheel == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[K]: Effect - DisappearTrack %s"), IsEffectDisappearTrack == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[L]: Effect - Gravity %s"), IsEffectGravity == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); AvoidWarning = swprintf_s(Message, _T("tt[M]: Effect - Death %s"), IsEffectDeath == true ? _T("ON") : _T("OFF")); outtextxy(0, TextHeight * (++Lines), Message); } } void FParticleSystem::Render(const float InDeltaTime) { if (IsContinueSimulate) { if (IsRestartSimulate) { IsRestartSimulate = false; SimulateTotalTime = 0; ParticleCurNum = 0; ParticleTotalNum = 0; for (auto& Emitter : Emitters) { EmitterManager.BindEmitter(Emitter) .GenerateParticles(); ParticleTotalNum += EmitterManager.GetNum(); } } else { ShowHelp(); OnEffectMouseField(InDeltaTime); OnEffectDisappearTrack(InDeltaTime); MouseVelX = CurMousePosX - PrevMousePosX; MouseVelY = CurMousePosY - PrevMousePosY; PrevMousePosX = CurMousePosX; PrevMousePosY = CurMousePosY; ParticleCurNum = 0; for (auto& Emitter : Emitters) { std::vector & Particles = EmitterManager .BindEmitter(Emitter) .GetParticles(); if (EmitterManager.GetStartTime() <= SimulateTotalTime) { EmitterManager.SetIsRender(true).AvoidWarning(); for (size_t i = 0; i < Particles.size(); ++i) { if (Particles[i].IsPendingKilled == true) { std::swap(Particles[i], Particles[Particles.size() - 1]); Particles.pop_back(); } else { FParticle Temp = Particles[i]; OnEffectDeath(InDeltaTime, Temp); OnEffectRandomMove(InDeltaTime, Temp); OnEffectBlow(InDeltaTime, Temp); OnEffectGather(InDeltaTime, Temp); OnEffectStir(InDeltaTime, Temp); OnEffectFriction(InDeltaTime, Temp); OnEffectRadiusAboutSpeed(InDeltaTime, Temp); OnEffectVortex(InDeltaTime, Temp); OnEffectGravity(InDeltaTime, Temp); OnEffectScaleWithWheel(InDeltaTime, Temp); Temp.NextX = Temp.PosX + Temp.VelX; Temp.NextY = Temp.PosY + Temp.VelY; // 位置计算完成 OnEffectReboundWall(InDeltaTime, Temp); Particles[i] = Temp; // 画粒子 setfillcolor(Particles[i].Color); solidcircle(static_cast (Particles[i].NextX), static_cast (Particles[i].NextY), static_cast (floorf(Particles[i].Radius * ScaleSize))); Particles[i].PosX = Particles[i].NextX; Particles[i].PosY = Particles[i].NextY; } } ParticleCurNum += Particles.size(); } } } } } float FParticleSystem::CalculateFps(const float InElapsedTime) { static constexpr int FpsCount = 8; // 每 8 次计算一次帧数 static int Count = 0; static float TotalElapsedTime = 0; ++Count; GraphicsTime = InElapsedTime; TotalElapsedTime += InElapsedTime; if (Count >= FpsCount) { GraphicsFps = static_cast (Count) / (TotalElapsedTime / 1000.0f); Count = 0; TotalElapsedTime = 0; } return GraphicsFps; } float FParticleSystem::CalculateDeltaTime(const clock_t InCurTime) { static clock_t LastTime = clock(); static const float LowLimit = 1000.0f / CustomLowFps; static const float HighLimit = 1000.0f / CustomHighFps; auto CurDeltaTime = static_cast (InCurTime - LastTime); if (CurDeltaTime > LowLimit) { CurDeltaTime = LowLimit; } if (CurDeltaTime < HighLimit) { CurDeltaTime = HighLimit; } SetDeltaTime(CurDeltaTime); LastTime = InCurTime; return CurDeltaTime; } void FParticleSystem::Delay(const clock_t Ms) { static clock_t OldTime = clock(); OldTime = clock(); while ((clock() - OldTime) < Ms) { Sleep(1); } } float FParticleSystem::LimitFps(const float InElapsedTime) const { float OffsetTime = (1000.0f / CustomHighFps) - InElapsedTime; if (OffsetTime >= 0.01f) { Delay(static_cast (OffsetTime + 1)); } else { OffsetTime = 0.0f; } return InElapsedTime + OffsetTime; } void FParticleSystem::OnEffectMouseField(const float InDeltaTime) const { if (IsEffectMouseField) { static float Radius = 2.0f; static float Times = 1.5f; static float MouseFieldSpeed = 0.5f; static COLORREF Color = RGB(187, 34, 128); if (IsLeftMouseDown) { if (Times == 0.0f) { Times = 1.5f; } else { Times += 1.5f; } Times -= MouseFieldSpeed * InDeltaTime; Radius += MouseFieldSpeed * InDeltaTime; setfillcolor(Color); setlinecolor(Color); fillcircle(static_cast (CurMousePosX + 0.01f * MouseVelX), static_cast (CurMousePosY + 0.01f * MouseVelY), static_cast (max(Radius, 5.0f))); } else { if (Times > 0.0f) { Times -= MouseFieldSpeed * InDeltaTime; Radius -= MouseFieldSpeed * InDeltaTime; setfillcolor(Color); setlinecolor(Color); fillcircle(static_cast (CurMousePosX), static_cast (CurMousePosY), static_cast (max(Radius, 2.0f))); } else { Times = 0.0f; Color = RGB(Utlis::GenerateRandomNumber(255), Utlis::GenerateRandomNumber(255), Utlis::GenerateRandomNumber(255)); } } } } void FParticleSystem::OnEffectDisappearTrack(const float InDeltaTime) const { if (IsEffectDisappearTrack) { for (int i = static_cast (Width * Height) - 1; i >= 0; --i) { if (GBuffer[i] != 0) { GBuffer[i] = RGB(GetRValue(GBuffer[i]) >> 1, GetGValue(GBuffer[i]) >> 1, GetBValue(GBuffer[i]) >> 1); } } } } void FParticleSystem::OnEffectReboundWall(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectReboundWall) { static float ReboundLoss = 0.85f; if (InParticle.NextX > Width) { InParticle.NextX = Width; InParticle.VelX *= -1 * ReboundLoss; } else if (InParticle.NextX < 0) { InParticle.NextX = 0; InParticle.VelX *= -1 * ReboundLoss; } if (InParticle.NextY > Height) { InParticle.NextY = Height; InParticle.VelY *= -1 * ReboundLoss; } else if (InParticle.NextY < 0) { InParticle.NextY = 0; InParticle.VelY *= -1 * ReboundLoss; } } } void FParticleSystem::OnEffectRadiusAboutSpeed(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectRadiusAboutSpeed) { const float AvgVx = fabs(InParticle.VelX); const float AvgVy = fabs(InParticle.VelY); const float AvgV = (AvgVx + AvgVy) * 0.5f; InParticle.Radius = AvgV * 0.45f + 0.5f; InParticle.Radius = max(min(InParticle.Radius, 1.66f), 0.4f); } } void FParticleSystem::OnEffectBlow(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectBlow) { float DisX = InParticle.PosX - CurMousePosX; float DisY = InParticle.PosY - CurMousePosY; const float Radius = sqrt(DisX * DisX + DisY * DisY); DisX = Radius > 0.01f ? DisX / Radius : 0; DisY = Radius > 0.01f ? DisY / Radius : 0; if (IsRightMouseDown && Radius < BlowDis) { const float BlowAcc = (1 - (Radius / BlowDis)) * 14; InParticle.VelX += DisX * BlowAcc * BlowSpeed * InDeltaTime + 0.5f - static_cast ( Utlis::GenerateRandomNumber()) / RAND_MAX; InParticle.VelY += DisY * BlowAcc * BlowSpeed * InDeltaTime + 0.5f - static_cast ( Utlis::GenerateRandomNumber()) / RAND_MAX; } } } void FParticleSystem::OnEffectGather(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectGather) { float DisX = InParticle.PosX - CurMousePosX; float DisY = InParticle.PosY - CurMousePosY; const float Radius = sqrt(DisX * DisX + DisY * DisY); DisX = Radius > 0.01f ? DisX / Radius : 0; DisY = Radius > 0.01f ? DisY / Radius : 0; if (IsLeftMouseDown && Radius < GatherDis) { const float GatherAcc = (1 - (Radius / GatherDis)) * Width * 0.0014f; InParticle.VelX -= DisX * GatherAcc * GatherSpeed * InDeltaTime; InParticle.VelY -= DisY * GatherAcc * GatherSpeed * InDeltaTime; } } } void FParticleSystem::OnEffectStir(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectStir) { const float DisX = InParticle.PosX - CurMousePosX; const float DisY = InParticle.PosY - CurMousePosY; const float Radius = sqrt(DisX * DisX + DisY * DisY); if (!IsLeftMouseDown && !IsRightMouseDown && Radius < StirDis) { const float StirAcc = (1 - (Radius / StirDis)) * Width * 0.00026f; InParticle.VelX += MouseVelX * StirAcc * StirSpeed * InDeltaTime; InParticle.VelY += MouseVelY * StirAcc * StirSpeed * InDeltaTime; } } } void FParticleSystem::OnEffectFriction(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectFriction) { InParticle.VelX *= InParticle.Friction; InParticle.VelY *= InParticle.Friction; } } void FParticleSystem::OnEffectVortex(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectVortex) { static std::vector Vortexes; static constexpr int VortexesNum = 3; if (Vortexes.empty()) { for (int i = 0; i < VortexesNum; ++i) { float PosX = 10.0f + Utlis::GenerateRandomNumber(Width - 1, 1.0f); float PosY = 10.0f + Utlis::GenerateRandomNumber(Height - 1, 1.0f); float Radius = max(30.0f, 30.0f + Utlis::GenerateRandomNumber(29.0f, 1.0f)); const float Speed = 20.0f + Utlis::GenerateRandomNumber(50.0f, 1.0f); if ((Width - PosX) <= 10) { PosX = Width - 20; Radius = 15.0f; } if ((Height - PosY) <= 10) { PosY = Height - 20; Radius = 15.0f; } Vortexes.push_back({{PosX, PosY}, Radius, Speed}); } } else { for (int i = 0; i < VortexesNum; ++i) { const float Vx = InParticle.PosX - Vortexes[i].Position.X - 1; const float Vy = InParticle.PosY - Vortexes[i].Position.Y - 1; const float Distance = sqrt(Vx * Vx + Vy * Vy); static float RotationSpeed = 35.0f + Utlis::GenerateRandomNumber(10.0f, 1.0f); if (Distance <= Vortexes[i].Radius) { // 不学习数学是这样的 // InParticle.PosX = Vortexes[i].Position.X + Distance * cos(acos(Vx / Distance)*114.6 + Rotate); // InParticle.PosY = Vortexes[i].Position.Y + Distance * sin(asin(Vy / Distance)*114.6 + Rotate); InParticle.PosX = Vortexes[i].Position.X + Vx * cos(RotationSpeed * InDeltaTime) + Vy * sin( Vortexes[i].RotateSpeed * InDeltaTime); InParticle.PosY = Vortexes[i].Position.Y + Vy * cos(RotationSpeed * InDeltaTime) - Vx * sin( Vortexes[i].RotateSpeed * InDeltaTime); if (Utlis::GenerateRandomNumber(10) > 7) { InParticle.PosX += Utlis::GenerateRandomSign () * Utlis::GenerateRandomNumber(5.0f, 1.0f); InParticle.PosY += Utlis::GenerateRandomSign () * Utlis::GenerateRandomNumber(5.0f, 1.0f); } } } } } } void FParticleSystem::OnEffectRandomMove(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectRandomMove) { float DisX = InParticle.PosX - CurMousePosX; float DisY = InParticle.PosY - CurMousePosY; const float Radius = sqrt(DisX * DisX + DisY * DisY); DisX = Radius > 0.01f ? DisX / Radius : 0; DisY = Radius > 0.01f ? DisY / Radius : 0; const float RandomAcc = Utlis::GenerateRandomSign () * Radius / RandomDis * 0.5f; InParticle.VelX += DisX * RandomAcc * RandomSpeed * InDeltaTime + 0.5f - static_cast ( Utlis::GenerateRandomNumber()) / RAND_MAX; InParticle.VelY += DisY * RandomAcc * RandomSpeed * InDeltaTime + 0.5f - static_cast ( Utlis::GenerateRandomNumber()) / RAND_MAX; const float AvgVx = fabs(InParticle.VelX); const float AvgVy = fabs(InParticle.VelY); if (AvgVx < 0.1f) InParticle.VelX *= static_cast (Utlis::GenerateRandomNumber()) / RAND_MAX * 2; if (AvgVy < 0.1f) InParticle.VelY *= static_cast (Utlis::GenerateRandomNumber()) / RAND_MAX * 2; } } void FParticleSystem::OnEffectScaleWithWheel(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectScaleWithWheel) { // nothing to do } else { ScaleSize = 1.0f; } } void FParticleSystem::OnEffectGravity(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectGravity) { if ((Height - InParticle.PosY) >= 5) { static float GravitySpeed = 1.0f; static float Gravity = 10.0f; InParticle.VelY += Gravity * GravitySpeed * InDeltaTime; } } } void FParticleSystem::OnEffectDeath(const float InDeltaTime, FParticle& InParticle) const { if (IsEffectDeath) { static float GoDieSpeed = 1.0f; InParticle.LifeTime -= GoDieSpeed * InDeltaTime; if (InParticle.LifeTime <= 0) { InParticle.IsPendingKilled = true; } } }
感谢看完!!!



