粒子系统 (Particle System)
2022-8-2 ~ 2022-8-8
(2)
一、作品说明
基于 **C++ 11**
和 **EasyX
**实现。
参考资料
主要功能
- 内置一些简单的粒子行为。可以控制或修改相关属性,生成更加复杂和精彩的效果。
- 特效
- ID
- 功能
- 绑定按键
- 菜单文本
- 是否显示菜单
- 是否启用
- 粒子
- ID
- 名称
- 说明
- 颜色
- 形状 / 贴图
- 初始位置(相对发射器的坐标)
- 初始速率
- 缩放
- 阻力
- 半径
- 生命周期
- 启用特效
- 发射器
- ID
- 名称
- 说明
- 发射数量
- 发射时间
- 发射速率
- 发射角度(以 y 正半轴为 0° 计算)
- 发射位置
- 是否循环
- 特效
其他效果尚未实现或尚不可控。
二、运行截图
菜单中文翻译
状态
- 平均帧数
- 帧时间
- 粒子数 / 粒子总数
- 模拟总时间
鼠标
- 移动:搅动粒子
- 左击:聚集粒子
- 右击:吹散粒子
- 滚轮:缩放粒子
键盘
- 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()
{
constexpr float Width = 1024.0f;
constexpr float Height = 576.0f;
// step1:初始化【粒子系统】
FParticleSystem& ParticleSystem = FParticleSystem::GetInstance()
.SetName(_T("Particle System"))
.SetVersion(2, 8, 1)
.SetScreenSize({Width, Height})
.InitInstance();
// step2: 新建【粒子特效】或者跳过
ParticleSystem.CreateParticleEffect(14)
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
// do everything you want
InParticleInstance.Velocity =
{
FTool::GenerateRandomNumber(0, 10, 2.0f),
-3.0f + FTool::GenerateRandomNumber(-5, 5, 2.0f)
};
})
.SetKeyboard('N')
.SetHelpContent(_T("Diy"))
.SetIsShowHelp(true)
.SetIsEnable(false)
.Finish();
// step3: 新建或复用【粒子模板】
ParticleSystem.CreateParticleTemplate(1)
.SetName(_T("样例粒子"))
.SetDescription(_T("样例"))
.SetShapeType(EShapeType::SolidCircle)
// .SetShapeType(EShapeType::Image)
// .SetImage(FTool::LoadImageA(_T("star.png"), 10))
.SetColor({RGB(125, 125, 0), RGB(255, 255, 255), EGenerateType::RandColor})
.SetStartLocation({0.0f, 0.0f})
.SetStartSpeed({0.0f, 25.0f})
.SetRadius({1.0f, 4.4f})
.SetFriction({0.93f, 0.95f})
.SetLifeTime({0.4f, 0.8f})
.SetEffects(FVarNum(1), 14)
.Finish();
ParticleSystem.CreateParticleTemplate(2, 1)
.SetShapeType(EShapeType::SolidCircle)
.SetColor({RGB(125, 0, 125), RGB(255, 255, 255), EGenerateType::RandColor});
ParticleSystem.CreateParticleTemplate(3, 1)
.SetShapeType(EShapeType::SolidSquare)
.SetColor({RGB(0, 125, 125), RGB(255, 255, 255), EGenerateType::RandColor});
// step4: 新建或复用【发射器模板】
ParticleSystem.CreateEmitterTemplate(1)
.SetName(_T("样例发射器"))
.SetDescription(_T("样例"))
.SetSum(6666)
.SetTime(0.0f)
.SetLocation({Width * 0.5f, Height * 0.5f})
.SetAngle({-90.0f, 90.0f})
.SetSpeed(1)
.SetIsLoop(true)
.Finish();
ParticleSystem.CreateEmitterTemplate(2, 1)
.SetLocation({Width * 0.5f, Height * 0.5f}).Finish();
ParticleSystem.CreateEmitterTemplate(3, 1)
.SetLocation({Width * 0.5f, Height * 0.5f}).Finish();
// step5: 生成【带多个粒子实例的发射器实例】
ParticleSystem.CreateEmitterInstance(1).CreateParticleInstance(FVarNum(1), 1).Finish();
ParticleSystem.CreateEmitterInstance(2).CreateParticleInstance(FVarNum(1), 2).Finish();
ParticleSystem.CreateEmitterInstance(3).CreateParticleInstance(FVarNum(1), 3).Finish();
// step6:【粒子系统】运行所有的【发射器实例】
ParticleSystem.RunEmitterInstances();
return 0;
}
``
可以看到,只需要设置好相关参数,就可以轻松使用粒子系统。
当然上面的例子只是演示,没有做什么效果(完结撒花),萌新初学乍到——_+,期待大佬们给出更好的样例。
而且呃,很多好玩且有用的参数和功能并未实现,不过之后会一直更新的√。(08 - 01 开始,08 - 08 截至)
下面是源码!!!
``
ParticleSystem.h
/**
* 粒子系统(Particle System)
* @platform Windows10,Rider 2022 | Visual Studio 2022,EasyX for VisualStudio2022
* @origin "https://codebus.cn/zhaoh/liquid-particles"
* @author Moota <3334754085@qq.com>
* @date 2022 - 08 - 08
*/
/**
* 注意事项
* @note 代码是在 Krissi 大神的基础上修改!!!
*
* 特效添加步骤
* @step 添加特效函数,在:FParticleSystem。
* 如: OnEffectGravity(float InDeltaTime, FParticle& InParticle)
*
* @step 添加控制信号,在:FParticleSystem。
* 如: bool IsEffectGravity;
*
* @step 添加控制按键,在:FParticleSystem::HandleInput。
* 如: case 'P': IsEffectGravity =! IsEffectGravity;
*
* @step 添加提示信息。在:FParticleSystem::ShowHelp。
* 如: "[P]: Effect - Gravity ON";
*
* 其他
* @note 虽然不能全屏,当你拖动窗口到略微超过屏幕,系统会自动帮你上下对齐窗口,就相当于全屏啦。
* @note 为了简单起见,很多设计限于两个文件没有实现,不过还好啦。
* @note 萌新初学,欢迎一起讨论和交流!!!
*/
/**
* 不算 Bug 的 Bug:
* @bug 鼠标以按下状态超过窗口范围,再以松开状态返回窗口时,鼠标原作用会保持,比如会一直集聚粒子。
* @reason 原因是超过窗口范围的鼠标信息未被利用,再次点击就会恢复正常。
* @note 不过这样也很好看,某种意义上还很方便呃。
*/
#pragma once
#include <map>
#include <set>
#include <ctime>
#include <vector>
#include <iostream>
#include <graphics.h>
/**
* 内置特效类型
*/
enum class EffectType: unsigned
{
MouseField = 1,
ReboundWall = 2,
RadiusAboutSpeed = 3,
Blow = 4,
Gather = 5,
Stir = 6,
Friction = 7,
Vortex = 8,
RandomMove = 9,
ScaleWithWheel = 10,
DisappearTrack = 11,
Gravity = 12,
Death = 13,
};
/**
* 数值的生成类型
*/
enum class EGenerateType: unsigned
{
None = 0,
Normal = 1, // 直接使用 Default 值作为 GetValue()返回值
Rand = 2, // 从 [Min, Max] 随机生成 Default 值作为 GetValue()返回值
RandColor = 3, // 注意!!!是对 RGB 值分别随机
};
/**
* 数值的使用类型
*/
enum class EUsedType: unsigned
{
None = 0,
Float = 1, // 浮点
PositiveFloat = 2, // 非负浮点
NegativeFloat = 3, // 非正浮点
PositivePercent = 4, // 正百分比 (1.0%, 100.0%)
PositivePercentZeroToOne = 5, // 正百分比/100 (0.0, 1.0)
Degree = 6, // 角度 (-360, 360)
Radian = 7, // 弧度 (-2pi, 2pi)
Color = 8, // 颜色 (0-0-0, 255-255-255)
};
/**
* 形状类型
*/
enum class EShapeType: unsigned
{
None = 0,
Image = 1,
SolidCircle = 2,
SolidSquare = 3,
};
/**
* 二维数据
*/
struct FVector2D
{
float X, Y;
FVector2D(const float InX = 0.0f, const float InY = 0.0f): X(InX), Y(InY)
{
}
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};
}
FVector2D operator*(const float Other) const
{
return {X * Other, Y * Other};
}
FVector2D& operator+=(const FVector2D& Other)
{
*this = operator+(Other);
return *this;
}
FVector2D& operator-=(const FVector2D& Other)
{
*this = operator-(Other);
return *this;
}
FVector2D& operator*=(const float Other)
{
*this = operator*(Other);
return *this;
}
};
/**
* 颜色信息
*/
struct FColor
{
int R, G, B;
COLORREF sRGB;
FColor(const int InR = 255, const int InG = 255, const int InB = 255): R(InR), G(InG), B(InB), sRGB(RGB(InR, InG, InB))
{
}
FColor(const COLORREF InColor): R(GetRValue(InColor)), G(GetGValue(InColor)), B(GetBValue(InColor)), sRGB(InColor)
{
}
/**
* FColor 转 RGB
*/
static COLORREF FColorToRGB(const FColor& InColor)
{
return RGB(InColor.R, InColor.G, InColor.B);
}
/**
* RGB 转 FColor
*/
static FColor RGBToFColor(const COLORREF InColor)
{
return {GetRValue(InColor), GetGValue(InColor), GetBValue(InColor)};
}
};
/**
* 特效漩涡信息
*/
struct FVortex
{
FVector2D Position;
float Radius;
float RotateSpeed;
};
/**
* 可变参数数量
*/
struct FVarNum
{
public:
explicit FVarNum(const size_t InValue): Value(InValue)
{
}
FVarNum() = default;
public:
size_t Value = 0;
};
/**
* 通用工具
*/
class FTool
{
public:
/**
* 产生随机浮点数,范围在 [Min,Max],精度为 FractionalNum 位小数
*/
static float GenerateRandomNumber(const float Min, const float Max, const float FractionalNum)
{
const float PowerNumber = powf(10, FractionalNum);
const int MinInt = static_cast<int>(Min * PowerNumber);
const int MaxInt = static_cast<int>(Max * PowerNumber);
return static_cast<float>(MinInt + GenerateRandomNumber() % (MaxInt - MinInt + 1)) / PowerNumber;
}
/**
* 产生随机整数,范围在 [Min,Max]
*/
static int GenerateRandomNumber(const int Min, const int Max)
{
return Min + GenerateRandomNumber() % (Max - Min + 1);
}
/**
* 产生随机从 0 开始的整数,范围在 [0,Max]
*/
static int GenerateRandomNumber(const int Max)
{
return GenerateRandomNumber() % (Max + 1);
}
/**
* 产生随机从 0.0f 开始的浮点数,范围在 [0.0f,Max],精度为 FractionalNum 位小数
*/
static float GenerateRandomNumber(const float Max, const float FractionalNum)
{
const float PowerNumber = powf(10, FractionalNum);
const int MaxInt = static_cast<int>(Max * PowerNumber);
return static_cast<float>(GenerateRandomNumber() % (MaxInt + 1)) / PowerNumber;
}
/**
* 标准随机数
*/
static int GenerateRandomNumber()
{
return rand();
}
/**
* 产生随机颜色
*/
static COLORREF GenerateRandomColor(const FColor& InMin, const FColor& InMax)
{
return RGB(GenerateRandomNumber(InMin.R, InMax.R),
GenerateRandomNumber(InMin.G, InMax.G),
GenerateRandomNumber(InMin.B, InMax.B));
}
/**
* 产生随机符号,范围在 {-1,1}
*/
template <typename ElementType>
static ElementType GenerateRandomSign()
{
return static_cast<ElementType>(pow(-1, GenerateRandomNumber() % 2));
}
/**
* 将数据限制在 [Min,Max] 区间
*/
template <typename ElementType>
static ElementType Clamp(ElementType Number, ElementType Min, ElementType Max)
{
return Number >= Min ? Number <= Max ? Number : Max : Min;
}
/**
* 选择数据,当 Condition 为真,选择 OptionTrue,否则选择 OptionFalse
*/
template <typename ElementType>
static ElementType Select(const bool Condition, const ElementType& OptionTrue, const ElementType& OptionFalse)
{
return Condition == true ? OptionTrue : OptionFalse;
}
/**
* 求单位向量
*/
static FVector2D Normalize(const FVector2D& Vector2D)
{
const float Length = CalculateLength(Vector2D);
return {Vector2D.X / Length, Vector2D.Y / Length};
}
static FVector2D Normalize(FVector2D&& Vector2D)
{
const float Length = CalculateLength(Vector2D);
return {Vector2D.X / Length, Vector2D.Y / Length};
}
/**
* 求向量长度
*/
static float CalculateLength(const FVector2D& Vector2D)
{
return sqrt(Vector2D.X * Vector2D.X + Vector2D.Y * Vector2D.Y);
}
static float CalculateLength(FVector2D&& Vector2D)
{
return sqrt(Vector2D.X * Vector2D.X + Vector2D.Y * Vector2D.Y);
}
/**
* 角度转弧度
*/
static float DegreeToRadian(const float Degree)
{
static constexpr float Piece = (3.1415926f * 2) / 360.0f;
return Degree * Piece;
}
/**
* 弧度转角度
*/
static float RadianToDegree(const float Radian)
{
static constexpr float Piece = 360.0f / (3.1415926f * 2);
return Radian * Piece;
}
// bool 转 _T()
static const wchar_t* BoolToSwitch(const bool InValue)
{
return InValue == true ? _T("ON") : _T("OFF");
}
// 绘制图形
static void Draw(const EShapeType ShapeType, COLORREF InColor, const FVector2D& InLocation, const float InSize)
{
switch (ShapeType)
{
case EShapeType::Image:
{
break;
}
case EShapeType::SolidCircle:
{
setfillcolor(InColor);
solidcircle(
static_cast<int>(InLocation.X),
static_cast<int>(InLocation.Y),
static_cast<int>(InSize)
);
break;
}
case EShapeType::SolidSquare:
{
setfillcolor(InColor);
solidrectangle(
static_cast<int>(InLocation.X),
static_cast<int>(InLocation.Y),
static_cast<int>(InLocation.X + InSize),
static_cast<int>(InLocation.Y + InSize)
);
break;
}
case EShapeType::None:
{
break;
}
}
}
static IMAGE* LoadImageA(const wchar_t* InFilePath, const int InSize)
{
Images.emplace_back();
loadimage(&Images[Images.size() - 1], InFilePath, InSize, InSize);
return &Images[Images.size() - 1];
}
#pragma comment( lib, "MSIMG32.LIB")
static void PutImageA(IMAGE* InImage, const FVector2D& InLocation)
{
const HDC DstDC = GetImageHDC(nullptr);
const HDC SrcDC = GetImageHDC(InImage);
const int Width = InImage->getwidth();
const int Height = InImage->getheight();
constexpr BLENDFUNCTION Bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
AlphaBlend(DstDC, static_cast<int>(InLocation.X), static_cast<int>(InLocation.Y), Width, Height, SrcDC, 0, 0, Width, Height, Bf);
}
public:
static std::vector<IMAGE> Images;
};
/**
* 唯一标识符生成器 暂时不全局
*/
struct FGuid
{
public:
unsigned int NewGuid(unsigned int InNumber)
{
if (GuidMaps.find(InNumber) != GuidMaps.end())
{
throw std::logic_error("the number has been registered");
}
unsigned int Guid = 0;
const size_t Size = Guids.size();
while (Guids.size() == Size)
{
Guid = FTool::GenerateRandomNumber(RAND_MAX);
Guids.insert(Guid);
}
GuidMaps[InNumber] = Guid;
return Guid;
}
unsigned int GetGuid(unsigned int InNumber)
{
if (GuidMaps.find(InNumber) == GuidMaps.end())
{
throw std::logic_error("the number is not registered");
}
return GuidMaps[InNumber];
}
public:
std::set<unsigned int> Guids;
std::map<unsigned int, unsigned int> GuidMaps;
};
/**
* 数值生成器
*/
struct FYielder
{
public:
// 参见 EUsedType
static const float PositiveFloatMin; // 非负浮点最小值
static const float PositiveFloatMax; // 非负浮点最大值
static const float PositivePercentMin; // 非负百分比最小值
static const float PositivePercentMax; // 非负百分比最大值
static const float PositivePercentZeroToOneMin; // 非负百分比/100 最小值
static const float PositivePercentZeroToOneMax; // 非负百分比/100 最大值
static const float PositiveDegreeMin; // 非负角度最小值
static const float PositiveDegreeMax; // 非负角度最大值
static const float PositiveRadianMin; // 非负弧度最小值
static const float PositiveRadianMax; // 非负弧度最大值
static const COLORREF ColorMin; // 颜色最小值
static const COLORREF ColorMax; // 颜色最大值
static int AvoidWarning; // 无用之有用
public:
FYielder(const float InMin, const float InMax, const EGenerateType InGenerateType = EGenerateType::Rand,
const float InDefault = PositiveFloatMin): GenerateType(InGenerateType),
Min(InMin),
Max(InMax),
Default(InDefault),
Value(InDefault)
{
IsGenerated = false;
CheckYielder(*this);
}
/**
* 约束数值
*/
static void AdjustValue(FYielder& InYielder)
{
InYielder.Value = InYielder.Value >= InYielder.Min ? InYielder.Value <= InYielder.Max ? InYielder.Value : InYielder.Max : InYielder.Min;
}
static void CheckRange(const float InValue, const float InMin, const float InMax)
{
if (InValue < (InMin - 0.01f) || (InMax + 0.01f) < InValue) // 误差 0.01f
{
throw std::logic_error("out of specify range");
}
}
/**
* 检查合法性
*/
static void CheckYielder(const FYielder& InYielder, EUsedType InUsedType = EUsedType::Float)
{
if (InYielder.Min > InYielder.Max)
{
throw std::logic_error("your range is wrong");
}
switch (InUsedType)
{
case EUsedType::Float:
{
CheckRange(InYielder.Min, -PositiveFloatMax, PositiveFloatMax);
CheckRange(InYielder.Max, -PositiveFloatMax, PositiveFloatMax);
break;
}
case EUsedType::PositiveFloat:
{
CheckRange(InYielder.Min, PositiveFloatMin, PositiveFloatMax);
CheckRange(InYielder.Max, PositiveFloatMin, PositiveFloatMax);
break;
}
case EUsedType::NegativeFloat:
{
CheckRange(InYielder.Min, -PositiveFloatMax, PositiveFloatMin);
CheckRange(InYielder.Max, -PositiveFloatMax, PositiveFloatMin);
break;
}
case EUsedType::PositivePercent:
{
CheckRange(InYielder.Min, PositivePercentMin, PositivePercentMax);
CheckRange(InYielder.Max, PositivePercentMin, PositivePercentMax);
break;
}
case EUsedType::PositivePercentZeroToOne:
{
CheckRange(InYielder.Min, PositivePercentZeroToOneMin, PositivePercentZeroToOneMax);
CheckRange(InYielder.Max, PositivePercentZeroToOneMin, PositivePercentZeroToOneMax);
break;
}
case EUsedType::Degree:
{
CheckRange(InYielder.Min, -PositiveDegreeMax, PositiveDegreeMax);
CheckRange(InYielder.Max, -PositiveDegreeMax, PositiveDegreeMax);
break;
}
case EUsedType::Radian:
{
CheckRange(InYielder.Min, -PositiveRadianMax, PositiveRadianMax);
CheckRange(InYielder.Max, -PositiveRadianMax, PositiveRadianMax);
break;
}
case EUsedType::Color:
{
CheckRange(InYielder.Min, static_cast<float>(ColorMin), static_cast<float>(ColorMax));
CheckRange(InYielder.Max, static_cast<float>(ColorMin), static_cast<float>(ColorMax));
break;
}
case EUsedType::None:
{
throw std::logic_error("please specify used type");
}
}
}
/**
* 生成 Value
*/
static float GenerateValue(const FYielder& InYielder, const float FractionalNum = 1.0f)
{
float Result = 0.0f;
switch (InYielder.GenerateType)
{
case EGenerateType::Normal:
{
Result = InYielder.Default;
break;
}
case EGenerateType::Rand:
{
Result = FTool::GenerateRandomNumber(InYielder.Min, InYielder.Max, FractionalNum);
break;
}
case EGenerateType::RandColor:
{
static FColor ColorMin;
static FColor ColorMax;
ColorMin = static_cast<COLORREF>(InYielder.Min);
ColorMax = static_cast<COLORREF>(InYielder.Max);
Result = static_cast<float>(FTool::GenerateRandomColor(ColorMin, ColorMax));
break;
}
case EGenerateType::None:
{
throw std::logic_error("please specify generate type");
}
}
return Result;
}
/**
* 获得 Value
*/
float GetValue(const bool IsRestartGenerate, const float InFractionalNum = 1.0f)
{
if (IsRestartGenerate || IsGenerated == false)
{
IsGenerated = true;
Value = GenerateValue(*this, InFractionalNum);
}
return Value;
}
public:
EGenerateType GenerateType; // 生成方式
float Min; // 最小值
float Max; // 最大值
float Default; // 默认值
private:
float Value; // 数值
bool IsGenerated = false; // 是否已经生成
};
/**
* 窗口管理器
*/
class FWindowManager
{
public:
static FWindowManager& GetInstance()
{
static FWindowManager WindowManager;
return WindowManager;
}
private:
FWindowManager(const FWindowManager&) = default;
FWindowManager(FWindowManager&&) = default;
FWindowManager() = default;
FWindowManager& operator=(const FWindowManager&) = default;
FWindowManager& operator=(FWindowManager&&) = default;
public:
void Initialize(float InWidth, float InHeight, const wchar_t* Title)
{
IsRunning = true;
// 创建绘图窗口
ScreenSize = {InWidth, InHeight};
initgraph(static_cast<int>(InWidth), static_cast<int>(InHeight));
// 修改窗口名称
SetWindowText(GetHWnd(), Title);
// 启用批绘图模式
BeginBatchDraw();
// 初始化鼠标变量
CurMouseLocation = PrevMouseLocation = {ScreenSize.X / 2, ScreenSize.Y / 2};
// 获取显示缓冲区指针
GBuffer = GetImageBuffer(nullptr);
}
~FWindowManager()
{
IsRunning = false;
// 关闭绘图窗口
closegraph();
}
void Flush() const
{
// 显示缓存的绘制内容
FlushBatchDraw();
}
public:
DWORD* GBuffer = nullptr; // 显示缓冲区指针
FVector2D ScreenSize{1540.0f, 840.0f}; // 屏幕尺寸
FVector2D CurMouseLocation; // 当前鼠标坐标
FVector2D PrevMouseLocation; // 上次鼠标坐标
FVector2D MouseVelocity; // 鼠标速度
bool IsLeftMouseDown = false; // 是否按下鼠标左键
bool IsRightMouseDown = false; // 是否按下鼠标右键
float WheelSize = 1.0f; // 滚轮缩放大小
private:
bool IsRunning = false; // 是否在运行
};
struct FParticleEffect;
/**
* 粒子信息
*/
struct FParticleInstance
{
public:
EShapeType ShapeType; // 形状
FColor Color; // 颜色
FVector2D Location; // 坐标
FVector2D Velocity; // 速度
float Friction; // 摩擦力
float Radius; // 半径
float LifeTime; // 生命周期
float Scale = 1.0f; // 缩放
std::vector<FParticleEffect*> Effects; // 粒子特效
IMAGE* Image; // 图像
public:
FVector2D NextLocation; // 下一次坐标
bool IsPendingKilled; // 是否进入死亡
bool IsRendering; // 是否正在渲染
public:
FParticleInstance(EShapeType InShapeType = EShapeType::SolidCircle,
FColor InColor = {255, 255, 255},
FVector2D InLocation = {0.0f, 0.0f},
FVector2D InVelocity = {0.0f, 0.0f},
float InFriction = 0.04f, float InRadius = 0.4f,
float InLifeTime = 0.0f, float InScale = 1.0f,
std::vector<FParticleEffect*> InEffects = {}
, IMAGE* InImage = nullptr): ShapeType(InShapeType)
, Color(InColor)
, Location(InLocation)
, Velocity(InVelocity)
, Friction(InFriction)
, Radius(InRadius)
, LifeTime(InLifeTime)
, Scale(InScale)
, Effects(std::move(InEffects))
, Image(InImage)
{
NextLocation = {0.0f, 0.0f};
IsPendingKilled = false;
IsRendering = false;
}
};
typedef void (*FunctionPointer)(float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 特效信息
*/
struct FParticleEffect
{
public:
unsigned int Id = 0; // 标识
BYTE Keyboard = 'A'; // 控制按键
FunctionPointer Function = nullptr; // 功能函数
wchar_t HelpContent[255] = _T("[A]: xxx"); // 帮助文本
bool IsEnable = true; // 是否启用特效
bool IsShowHelp = true; // 是否显示帮助
};
/**
* 粒子模板信息
*/
struct FParticleTemplate
{
public:
FParticleTemplate(const unsigned int InId): Id(InId)
{
}
FParticleTemplate() = default;
public:
unsigned int Id = 0; // 标识
wchar_t Name[255]{_T("默认粒子")}; // 名称
wchar_t Description[255]{_T("默认描述")}; // 描述
public:
EShapeType ShapeType = EShapeType::None; // 形状
FYielder Color{RGB(0, 0, 0), RGB(255, 255, 255)}; // 颜色(默认随机)
FVector2D StartLocation{0.0f, 0.0f}; // 起始坐标
FYielder StartSpeed{10.0f, 20.0f}; // 起始速率
FYielder LimitedSpeed{100.0f, 200.0f}; // 限制速率
FYielder Friction{0.93f, 0.97f}; // 摩擦力
FYielder Radius{1.0f, 1.5f}; // 半径
FYielder LifeTime{1.0f, 5.0f}; // 生命周期
float Scale = 1.0f; // 缩放
std::vector<FParticleEffect*> Effects; // 粒子特效
IMAGE* Image = nullptr; // 图像
};
/**
* 特效管理器
*/
class FParticleEffectManager
{
private:
FParticleEffectManager(): WindowManager(FWindowManager::GetInstance())
{
}
~FParticleEffectManager() = default;
public:
FParticleEffectManager(const FParticleEffectManager&) = delete;
FParticleEffectManager(FParticleEffectManager&&) = delete;
FParticleEffectManager& operator=(const FParticleEffectManager&) = delete;
FParticleEffectManager& operator=(FParticleEffectManager&&) = delete;
public:
void Finish() const
{
ParticleEffect->Id = ParticleEffect->Id + 0;
}
static FParticleEffectManager& GetInstance()
{
static FParticleEffectManager ParticleEffectManager;
return ParticleEffectManager;
}
void Initialize()
{
CreateParticleEffect(static_cast<unsigned>(EffectType::MouseField))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectMouseField(InDeltaTime, InParticleInstance);
}).SetKeyboard('A').SetHelpContent(_T("MouseField")).SetIsEnable(false).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::ReboundWall))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectReboundWall(InDeltaTime, InParticleInstance);
}).SetKeyboard('B').SetHelpContent(_T("ReboundWall")).SetIsEnable(false).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::RadiusAboutSpeed))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectRadiusAboutSpeed(InDeltaTime, InParticleInstance);
}).SetKeyboard('C').SetHelpContent(_T("RadiusAboutSpeed")).SetIsEnable(false).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Blow))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectBlow(InDeltaTime, InParticleInstance);
}).SetKeyboard('D').SetHelpContent(_T("Blow")).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Gather))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectGather(InDeltaTime, InParticleInstance);
}).SetKeyboard('E').SetHelpContent(_T("Gather")).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Stir))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectStir(InDeltaTime, InParticleInstance);
}).SetKeyboard('F').SetHelpContent(_T("Stir")).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Friction))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectFriction(InDeltaTime, InParticleInstance);
}).SetKeyboard('G').SetHelpContent(_T("Friction")).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Vortex))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectVortex(InDeltaTime, InParticleInstance);
}).SetKeyboard('H').SetHelpContent(_T("Vortex")).SetIsEnable(false).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::RandomMove))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectRandomMove(InDeltaTime, InParticleInstance);
}).SetKeyboard('I').SetHelpContent(_T("RandomMove")).SetIsEnable(false).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::ScaleWithWheel))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectScaleWithWheel(InDeltaTime, InParticleInstance);
}).SetKeyboard('J').SetHelpContent(_T("Scale")).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::DisappearTrack))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectDisappearTrack(InDeltaTime, InParticleInstance);
}).SetKeyboard('K').SetHelpContent(_T("DisappearTrack")).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Gravity))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectGravity(InDeltaTime, InParticleInstance);
}).SetKeyboard('L').SetHelpContent(_T("Gravity")).SetIsEnable(true).Finish();
CreateParticleEffect(static_cast<unsigned>(EffectType::Death))
.SetFunction([](const float InDeltaTime, FParticleInstance& InParticleInstance)
{
GetInstance().OnEffectDeath(InDeltaTime, InParticleInstance);
}).SetKeyboard('M').SetHelpContent(_T("Death")).Finish();
}
FParticleEffectManager& BindParticleEffect(FParticleEffect& InParticleEffect)
{
ParticleEffect = &InParticleEffect;
return GetInstance();
}
FParticleEffectManager& CreateParticleEffect(const unsigned int InId)
{
const unsigned int Guid = ParticleEffectGuid.NewGuid(InId);
ParticleEffects[Guid] = {};
return GetInstance()
.BindParticleEffect(ParticleEffects[Guid])
.SetId(InId);
}
FParticleEffect& GetParticleEffect(const unsigned int InId)
{
const unsigned int Guid = ParticleEffectGuid.GetGuid(InId);
return ParticleEffects[Guid];
}
FParticleEffect* GetParticleEffectByKeyboard(const BYTE InKeyboard)
{
if (ParticleKeyboard.find(InKeyboard) == ParticleKeyboard.end())
{
return nullptr;
}
const auto Guid = ParticleKeyboard[InKeyboard];
return &ParticleEffects[Guid];
}
void ReverseIsEnableByKeyboard(const BYTE InKeyboard)
{
if (ParticleKeyboard.find(InKeyboard) != ParticleKeyboard.end())
{
const auto Guid = ParticleKeyboard[InKeyboard];
ParticleEffects[Guid].IsEnable = !ParticleEffects[Guid].IsEnable;
}
}
public:
FParticleEffectManager& SetFunction(FunctionPointer InFunction) const
{
ParticleEffect->Function = InFunction;
return GetInstance();
}
FunctionPointer GetFunction() const
{
return ParticleEffect->Function;
}
FParticleEffectManager& SetHelpContent(const wchar_t* InHelpContent) const
{
FYielder::AvoidWarning = swprintf_s(ParticleEffect->HelpContent, _T("%s"), InHelpContent);
return GetInstance();
}
const wchar_t* GetHelpContent() const
{
return ParticleEffect->HelpContent;
}
FParticleEffectManager& SetIsEnable(const bool InIsEnable) const
{
ParticleEffect->IsEnable = InIsEnable;
return GetInstance();
}
bool GetIsEnable() const
{
return ParticleEffect->IsEnable;
}
FParticleEffectManager& SetIsShowHelp(const bool InIsShowHelp) const
{
ParticleEffect->IsShowHelp = InIsShowHelp;
return GetInstance();
}
bool GetIsShowHelp() const
{
return ParticleEffect->IsShowHelp;
}
FParticleEffectManager& SetId(const unsigned int InId) const
{
ParticleEffect->Id = InId;
return GetInstance();
}
unsigned int GetId() const
{
return ParticleEffect->Id;
}
FParticleEffectManager& SetKeyboard(const BYTE InKeyboard)
{
BYTE Keyboard = InKeyboard;
if (Keyboard >= 'a' && Keyboard <= 'z')
{
Keyboard = Keyboard - 32;
}
if (ParticleKeyboard.find(Keyboard) != ParticleKeyboard.end())
{
throw std::logic_error("the key is binding");
}
ParticleEffect->Keyboard = Keyboard;
ParticleKeyboard[Keyboard] = ParticleEffectGuid.GetGuid(GetId());
return GetInstance();
}
BYTE GetKeyboard() const
{
return ParticleEffect->Keyboard;
}
public:
/**
* 鼠标范围特效:点击会出现小圆表示落点
*/
void OnEffectMouseField(float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子轨迹消失特效:开启将不会记录粒子移动轨迹
*/
void OnEffectDisappearTrack(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子边界特效:碰到边界反弹
*/
void OnEffectReboundWall(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子大小特效:速度越大,粒子半径越大
*/
void OnEffectRadiusAboutSpeed(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子吹散特效:鼠标右击时,以落点吹散粒子
*/
void OnEffectBlow(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子聚集特效:鼠标左击时,以落点聚集粒子
*/
void OnEffectGather(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子搅动特效:鼠标移动时,以落点搅动粒子
*/
void OnEffectStir(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子摩擦力特效:粒子移动受摩擦力
*/
void OnEffectFriction(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子漩涡特效:空间存在漩涡,作用于粒子
*/
void OnEffectVortex(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子随机微动特效:不受作用的情况下,粒子会随机移动
*/
void OnEffectRandomMove(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子随滚轮缩放特效:鼠标滚轮移动控制粒子显示大小
*/
void OnEffectScaleWithWheel(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子重力特效:粒子受重力影响
*/
void OnEffectGravity(const float InDeltaTime, FParticleInstance& InParticleInstance);
/**
* 粒子死亡特效:粒子也会消逝啊
*/
void OnEffectDeath(const float InDeltaTime, FParticleInstance& InParticleInstance);
public:
FParticleEffect* ParticleEffect = nullptr;
std::map<unsigned int, FParticleEffect> ParticleEffects; // 粒子特效数组
std::map<BYTE, unsigned int> ParticleKeyboard; // 粒子特效按键映射
FGuid ParticleEffectGuid; // 粒子唯一标识
FWindowManager& WindowManager; // 屏幕管理器
};
/**
* 粒子实例管理器
*/
class FParticleInstanceManager
{
private:
FParticleInstanceManager() = default;
~FParticleInstanceManager() = default;
FParticleInstanceManager(const FParticleInstanceManager&) = default;
FParticleInstanceManager(FParticleInstanceManager&&) = default;
FParticleInstanceManager& operator=(const FParticleInstanceManager&) = default;
FParticleInstanceManager& operator=(FParticleInstanceManager&&) = default;
public:
void Finish() const
{
ParticleInstance->LifeTime = ParticleInstance->LifeTime + 0;
}
static FParticleInstanceManager& GetInstance()
{
static FParticleInstanceManager ParticleInstanceManager;
return ParticleInstanceManager;
}
private:
FParticleInstance* ParticleInstance = nullptr;
};
/**
* 粒子模板管理器
*/
class FParticleTemplateManager
{
private:
FParticleTemplateManager(): ParticleEffectManager(FParticleEffectManager::GetInstance())
{
}
~FParticleTemplateManager() = default;
public:
FParticleTemplateManager(const FParticleTemplateManager&) = delete;
FParticleTemplateManager(FParticleTemplateManager&&) = delete;
FParticleTemplateManager& operator=(const FParticleTemplateManager&) = delete;
FParticleTemplateManager& operator=(FParticleTemplateManager&&) = delete;
public:
void Finish() const
{
ParticleTemplate->Id = ParticleTemplate->Id + 0;
}
static FParticleTemplateManager& GetInstance()
{
static FParticleTemplateManager ParticleManager;
return ParticleManager;
}
FParticleTemplateManager& BindParticleTemplate(FParticleTemplate& InParticleTemplate)
{
ParticleTemplate = &InParticleTemplate;
return GetInstance();
}
public:
FParticleTemplateManager& SetEffects(const FVarNum Count, ...) const
{
ParticleTemplate->Effects = {};
va_list List;
va_start(List, Count);
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Blow)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Gather)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Stir)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::RandomMove)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Vortex)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Gravity)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Friction)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::RadiusAboutSpeed)));
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(static_cast<unsigned>(EffectType::Death)));
for (size_t i = 0; i < Count.Value; ++i)
{
ParticleTemplate->Effects.push_back(&ParticleEffectManager.GetParticleEffect(va_arg(List, unsigned int)));
}
return GetInstance();
}
std::vector<FParticleEffect*>& GetEffects() const
{
return ParticleTemplate->Effects;
}
FParticleTemplateManager& SetLimitedSpeed(const FYielder& InLimitedSpeed) const
{
FYielder::CheckYielder(InLimitedSpeed, EUsedType::PositiveFloat);
ParticleTemplate->LimitedSpeed = InLimitedSpeed;
return GetInstance();
}
FYielder GetLimitedSpeed() const
{
return ParticleTemplate->LimitedSpeed;
}
FParticleTemplateManager& SetFriction(const FYielder& InFriction) const
{
FYielder::CheckYielder(InFriction, EUsedType::PositivePercentZeroToOne);
ParticleTemplate->Friction = InFriction;
return GetInstance();
}
FYielder GetFriction() const
{
return ParticleTemplate->Friction;
}
FParticleTemplateManager& SetRadius(const FYielder& InRadius) const
{
FYielder::CheckYielder(InRadius, EUsedType::PositiveFloat);
ParticleTemplate->Radius = InRadius;
return GetInstance();
}
FYielder GetRadius() const
{
return ParticleTemplate->Radius;
}
FParticleTemplateManager& SetLifeTime(const FYielder& InLifeTime) const
{
FYielder::CheckYielder(InLifeTime, EUsedType::PositiveFloat);
ParticleTemplate->LifeTime = InLifeTime;
return GetInstance();
}
FYielder GetLifeTime() const
{
return ParticleTemplate->LifeTime;
}
FParticleTemplateManager& SetColor(const FYielder& InColor) const
{
FYielder::CheckYielder(InColor, EUsedType::Color);
ParticleTemplate->Color = InColor;
return GetInstance();
}
FYielder GetColor() const
{
return ParticleTemplate->Color;
}
FParticleTemplateManager& SetStartLocation(const FVector2D& InStartLocation) const
{
ParticleTemplate->StartLocation = InStartLocation;
return GetInstance();
}
FVector2D GetStartLocation() const
{
return ParticleTemplate->StartLocation;
}
FParticleTemplateManager& SetStartSpeed(const FYielder& InStartSpeed) const
{
FYielder::CheckYielder(InStartSpeed, EUsedType::PositiveFloat);
ParticleTemplate->StartSpeed = InStartSpeed;
return GetInstance();
}
FYielder GetStartSpeed() const
{
return ParticleTemplate->StartSpeed;
}
FParticleTemplateManager& SetScale(const float InScale) const
{
ParticleTemplate->Scale = InScale;
return GetInstance();
}
float GetScale() const
{
return ParticleTemplate->Scale;
}
FParticleTemplateManager& SetShapeType(const EShapeType InShapeType) const
{
ParticleTemplate->ShapeType = InShapeType;
return GetInstance();
}
EShapeType GetShapeType() const
{
return ParticleTemplate->ShapeType;
}
FParticleTemplateManager& SetImage(IMAGE* InImage) const
{
ParticleTemplate->Image = InImage;
return GetInstance();
}
IMAGE* GetImage() const
{
return ParticleTemplate->Image;
}
public:
FParticleTemplateManager& SetId(const unsigned int InId) const
{
ParticleTemplate->Id = InId;
return GetInstance();
}
unsigned int GetId() const
{
return ParticleTemplate->Id;
}
FParticleTemplateManager& SetName(const wchar_t* InName) const
{
FYielder::AvoidWarning = swprintf_s(ParticleTemplate->Name, _T("%s"), InName);
return GetInstance();
}
const wchar_t* GetName() const
{
return ParticleTemplate->Name;
}
FParticleTemplateManager& SetDescription(const wchar_t* InDescription) const
{
FYielder::AvoidWarning = swprintf_s(ParticleTemplate->Description, _T("%s"), InDescription);
return GetInstance();
}
const wchar_t* GetDescription() const
{
return ParticleTemplate->Description;
}
private:
FParticleTemplate* ParticleTemplate = nullptr;
FParticleEffectManager& ParticleEffectManager;
};
/**
* 发射器实例信息
*/
struct FEmitterInstance
{
public:
float Time = 0.0f; // 发射时间
size_t Speed = 111; // 发射速率
FYielder Angle{0.0f, 360.0f}; // 发射方向
FVector2D Location = {10.0f, 10.0f}; // 发射位置
size_t Sum = 666; // 发射总数
bool IsLoop = false; // 是否循环发射
public:
std::vector<FParticleInstance> ParticleInstances; // 粒子数组
std::vector<FParticleTemplate> ParticleTemplates; // 粒子模板数组
bool IsRender = false; // 是否已经加入渲染
size_t RenderedNum = 0; // 已经被渲染的粒子数
size_t RenderingNum = 0; // 正在渲染的粒子数
size_t DeadNum = 0; // 已经消逝的粒子数
};
/**
* 发射器模板信息
*/
struct FEmitterTemplate
{
public:
float Time = 0.0f; // 发射时间
size_t Speed = 111; // 发射速率
FYielder Angle{0.0f, 360.0f}; // 发射方向
FVector2D Location = {10.0f, 10.0f}; // 发射位置
size_t Sum = 666; // 发射总数
bool IsLoop = false; // 是否循环发射
public:
unsigned int Id = 0; // 标识
wchar_t Name[255]{_T("默认粒子")}; // 名称
wchar_t Description[255]{_T("默认描述")}; // 描述
public:
std::vector<FParticleTemplate> ParticleTemplates; // 粒子模板数组
};
/**
* 发射器实例管理器
*/
class FEmitterInstanceManager
{
private:
FEmitterInstanceManager() = default;
~FEmitterInstanceManager() = default;
FEmitterInstanceManager(const FEmitterInstanceManager&) = default;
FEmitterInstanceManager(FEmitterInstanceManager&&) = default;
FEmitterInstanceManager& operator=(const FEmitterInstanceManager&) = default;
FEmitterInstanceManager& operator=(FEmitterInstanceManager&&) = default;
public:
void Finish() const
{
EmitterInstance->Time = EmitterInstance->Time + 0;
}
static FEmitterInstanceManager& GetInstance()
{
static FEmitterInstanceManager EmitterInstanceManager;
return EmitterInstanceManager;
}
FEmitterInstanceManager& BindEmitterInstance(FEmitterInstance& InEmitterInstance)
{
EmitterInstance = &InEmitterInstance;
return GetInstance();
}
/**
* 生成粒子实例
*/
FEmitterInstanceManager& CreateParticleInstance(FParticleTemplate InParticleTemplate) const
{
EmitterInstance->ParticleTemplates.push_back(InParticleTemplate);
auto& ParticleInstances = EmitterInstance->ParticleInstances;
ParticleInstances.reserve(ParticleInstances.size() + EmitterInstance->Sum);
for (size_t i = 0; i < EmitterInstance->Sum; ++i)
{
const float StartAngleRand = EmitterInstance->Angle.GetValue(true, 2.0f);
const float StartSpeedRand = InParticleTemplate.StartSpeed.GetValue(true, 2.0f);
ParticleInstances.emplace_back(
InParticleTemplate.ShapeType,
static_cast<COLORREF>(InParticleTemplate.Color.GetValue(true, 0.0f)),
InParticleTemplate.StartLocation + EmitterInstance->Location,
FVector2D
{
cos(StartAngleRand) * StartSpeedRand,
sin(StartAngleRand) * StartSpeedRand
},
InParticleTemplate.Friction.GetValue(true, 2.0f),
InParticleTemplate.Radius.GetValue(true, 2.0f),
InParticleTemplate.LifeTime.GetValue(true, 2.0f),
1.0f,
InParticleTemplate.Effects
, InParticleTemplate.Image
);
}
return GetInstance();
}
/**
* 重新生成粒子实例
*/
FEmitterInstanceManager& RegenerateParticleInstance() const
{
SetIsRender(false)
.SetRenderingNum(0)
.SetRenderedNum(0)
.SetDeadNum(0)
.Finish();
EmitterInstance->ParticleInstances = {};
const auto ParticleTemplates = EmitterInstance->ParticleTemplates;
EmitterInstance->ParticleTemplates = {};
for (const auto& ParticleTemplate : ParticleTemplates)
{
CreateParticleInstance(ParticleTemplate).Finish();
}
return GetInstance();
}
/**
* 获得粒子实例
*/
std::vector<FParticleInstance>& GetParticleInstances() const
{
return EmitterInstance->ParticleInstances;
}
public:
FEmitterInstanceManager& SetParticleTemplates(const std::vector<FParticleTemplate>& InParticleTemplates) const
{
EmitterInstance->ParticleTemplates = InParticleTemplates;
return GetInstance();
}
std::vector<FParticleTemplate>& GetParticleTemplates() const
{
return EmitterInstance->ParticleTemplates;
}
size_t GetTotalNum() const
{
return EmitterInstance->Sum * EmitterInstance->ParticleTemplates.size();
}
FEmitterInstanceManager& SetDeadNum(const size_t InDeadNum) const
{
EmitterInstance->DeadNum = InDeadNum;
return GetInstance();
}
size_t GetDeadNum() const
{
return EmitterInstance->DeadNum;
}
FEmitterInstanceManager& SetRenderedNum(const size_t InRenderedNum) const
{
EmitterInstance->RenderedNum = InRenderedNum;
return GetInstance();
}
size_t GetRenderedNum() const
{
return EmitterInstance->RenderedNum;
}
FEmitterInstanceManager& SetRenderingNum(const size_t InRenderingNum) const
{
EmitterInstance->RenderingNum = InRenderingNum;
return GetInstance();
}
size_t GetRenderingNum() const
{
return EmitterInstance->RenderingNum;
}
FEmitterInstanceManager& SetIsLoop(const bool InIsLoop) const
{
EmitterInstance->IsLoop = InIsLoop;
return GetInstance();
}
bool GetIsLoop() const
{
return EmitterInstance->IsLoop;
}
FEmitterInstanceManager& SetIsRender(const bool InIsRender) const
{
EmitterInstance->IsRender = InIsRender;
return GetInstance();
}
bool GetIsRender() const
{
return EmitterInstance->IsRender;
}
FEmitterInstanceManager& SetTime(const float InTime) const
{
EmitterInstance->Time = InTime;
return GetInstance();
}
float GetTime() const
{
return EmitterInstance->Time;
}
FEmitterInstanceManager& SetSpeed(const size_t InSpeed) const
{
EmitterInstance->Speed = InSpeed;
return GetInstance();
}
size_t GetSpeed() const
{
return EmitterInstance->Speed;
}
FEmitterInstanceManager& SetSum(const unsigned int InSum) const
{
EmitterInstance->Sum = InSum;
return GetInstance();
}
size_t GetSum() const
{
return EmitterInstance->Sum;
}
FEmitterInstanceManager& SetAngle(const FYielder& InAngle) const
{
EmitterInstance->Angle = InAngle;
EmitterInstance->Angle.Min = FTool::DegreeToRadian(InAngle.Min);
EmitterInstance->Angle.Max = FTool::DegreeToRadian(InAngle.Max);
FYielder::CheckYielder(EmitterInstance->Angle, EUsedType::Radian);
return GetInstance();
}
FYielder GetAngle() const
{
return EmitterInstance->Angle;
}
FEmitterInstanceManager& SetLocation(const FVector2D& InLocation) const
{
EmitterInstance->Location = InLocation;
return GetInstance();
}
FVector2D GetLocation() const
{
return EmitterInstance->Location;
}
private:
FEmitterInstance* EmitterInstance = nullptr;
};
/**
* 发射器模板管理器
*/
class FEmitterTemplateManager
{
private:
FEmitterTemplateManager() = default;
~FEmitterTemplateManager() = default;
FEmitterTemplateManager(const FEmitterTemplateManager&) = default;
FEmitterTemplateManager(FEmitterTemplateManager&&) = default;
FEmitterTemplateManager& operator=(const FEmitterTemplateManager&) = default;
FEmitterTemplateManager& operator=(FEmitterTemplateManager&&) = default;
public:
void Finish() const
{
EmitterTemplate->Time = EmitterTemplate->Time + 0;
}
static FEmitterTemplateManager& GetInstance()
{
static FEmitterTemplateManager ParticleManager;
return ParticleManager;
}
FEmitterTemplateManager& BindEmitterTemplate(FEmitterTemplate& InEmitterTemplate)
{
EmitterTemplate = &InEmitterTemplate;
return GetInstance();
}
FEmitterInstance CreateEmitterInstance(const FEmitterTemplate& InEmitterTemplate) const
{
FEmitterInstance EmitterInstance;
EmitterInstance.Sum = InEmitterTemplate.Sum;
EmitterInstance.Speed = InEmitterTemplate.Speed;
EmitterInstance.Angle = InEmitterTemplate.Angle;
EmitterInstance.Location = InEmitterTemplate.Location;
EmitterInstance.Time = InEmitterTemplate.Time;
EmitterInstance.IsLoop = InEmitterTemplate.IsLoop;
EmitterInstance.ParticleTemplates = InEmitterTemplate.ParticleTemplates;
Finish();
return EmitterInstance;
}
public:
FEmitterTemplateManager& SetParticleTemplates(const std::vector<FParticleTemplate>& InParticleTemplates) const
{
EmitterTemplate->ParticleTemplates = InParticleTemplates;
return GetInstance();
}
std::vector<FParticleTemplate>& GetParticleTemplates() const
{
return EmitterTemplate->ParticleTemplates;
}
FEmitterTemplateManager& SetIsLoop(const bool InIsLoop) const
{
EmitterTemplate->IsLoop = InIsLoop;
return GetInstance();
}
bool GetIsLoop() const
{
return EmitterTemplate->IsLoop;
}
FEmitterTemplateManager& SetTime(const float InTime) const
{
EmitterTemplate->Time = InTime;
return GetInstance();
}
float GetTime() const
{
return EmitterTemplate->Time;
}
FEmitterTemplateManager& SetSpeed(const size_t InSpeed) const
{
EmitterTemplate->Speed = InSpeed;
return GetInstance();
}
size_t GetSpeed() const
{
return EmitterTemplate->Speed;
}
FEmitterTemplateManager& SetSum(const unsigned int InSum) const
{
EmitterTemplate->Sum = InSum;
return GetInstance();
}
size_t GetSum() const
{
return EmitterTemplate->Sum;
}
FEmitterTemplateManager& SetAngle(const FYielder& InAngle) const
{
FYielder::CheckYielder(EmitterTemplate->Angle, EUsedType::Degree);
EmitterTemplate->Angle = InAngle;
EmitterTemplate->Angle.Min = FTool::DegreeToRadian(InAngle.Min - 90.0f);
EmitterTemplate->Angle.Max = FTool::DegreeToRadian(InAngle.Max - 90.0f);
return GetInstance();
}
FYielder GetAngle() const
{
return EmitterTemplate->Angle;
}
FEmitterTemplateManager& SetLocation(const FVector2D& InLocation) const
{
EmitterTemplate->Location = InLocation;
return GetInstance();
}
FVector2D GetLocation() const
{
return EmitterTemplate->Location;
}
public:
FEmitterTemplateManager& SetId(const unsigned int InId) const
{
EmitterTemplate->Id = InId;
return GetInstance();
}
unsigned int GetId() const
{
return EmitterTemplate->Id;
}
FEmitterTemplateManager& SetName(const wchar_t* InName) const
{
FYielder::AvoidWarning = swprintf_s(EmitterTemplate->Name, _T("%s"), InName);
return GetInstance();
}
const wchar_t* GetName() const
{
return EmitterTemplate->Name;
}
FEmitterTemplateManager& SetDescription(const wchar_t* InDescription) const
{
FYielder::AvoidWarning = swprintf_s(EmitterTemplate->Description, _T("%s"), InDescription);
return GetInstance();
}
const wchar_t* GetDescription() const
{
return EmitterTemplate->Description;
}
private:
FEmitterTemplate* EmitterTemplate = nullptr;
};
/**
* 粒子系统
*/
class FParticleSystem
{
public:
static FParticleSystem& GetInstance()
{
static FParticleSystem ParticleSystem;
return ParticleSystem;
}
private:
FParticleSystem(): WindowManager(FWindowManager::GetInstance()),
ParticleEffectManager(FParticleEffectManager::GetInstance()),
ParticleTemplateManager(FParticleTemplateManager::GetInstance()),
ParticleInstanceManager(FParticleInstanceManager::GetInstance()),
EmitterTemplateManager(FEmitterTemplateManager::GetInstance()),
EmitterInstanceManager(FEmitterInstanceManager::GetInstance())
{
}
~FParticleSystem() = default;
public:
FParticleSystem(const FParticleSystem&) = delete;
FParticleSystem(FParticleSystem&&) = delete;
FParticleSystem& operator=(const FParticleSystem&) = delete;
FParticleSystem& operator=(FParticleSystem&&) = delete;
public:
void Finish()
{
SimulateTotalTime = SimulateTotalTime + 0.0f;
}
/**
* 初始化粒子系统
*/
FParticleSystem& InitInstance();
/**
* 创造粒子特效
*/
FParticleEffectManager& CreateParticleEffect(const unsigned int InId) const
{
return ParticleEffectManager.CreateParticleEffect(InId);
}
/**
* 创造粒子模板
*/
FParticleTemplateManager& CreateParticleTemplate(const unsigned int InId)
{
const unsigned int Guid = ParticleTemplateGuid.NewGuid(InId);
ParticleTemplates[Guid] = {};
return ParticleTemplateManager
.BindParticleTemplate(ParticleTemplates[Guid])
.SetId(InId);
}
/**
* 参考已有粒子模板创造粒子模板
*/
FParticleTemplateManager& CreateParticleTemplate(const unsigned int InId, const unsigned int InCopyId)
{
const unsigned int Guid = ParticleTemplateGuid.NewGuid(InId);
const unsigned int ReferGuid = ParticleTemplateGuid.GetGuid(InCopyId);
ParticleTemplates[Guid] = ParticleTemplates[ReferGuid];
return ParticleTemplateManager
.BindParticleTemplate(ParticleTemplates[Guid])
.SetId(InId);
}
/**
* 生成粒子实例
*/
FParticleSystem& CreateParticleInstance(const FVarNum Count, ...)
{
va_list List;
va_start(List, Count);
for (size_t i = 0; i < Count.Value; ++i)
{
EmitterInstanceManager.CreateParticleInstance(ParticleTemplates[ParticleTemplateGuid.GetGuid(va_arg(List, unsigned int))]);
ParticleTotalNum += EmitterInstanceManager.GetSum();
}
va_end(List);
return GetInstance();
}
/**
* 创造发射器模板
*/
FEmitterTemplateManager& CreateEmitterTemplate(const unsigned int InId)
{
const unsigned int Guid = EmitterTemplateGuid.NewGuid(InId);
EmitterTemplates[Guid] = {};
return EmitterTemplateManager
.BindEmitterTemplate(EmitterTemplates[Guid])
.SetId(InId);
}
/**
* 生成发射器实例
*/
FParticleSystem& CreateEmitterInstance(const unsigned int InId)
{
const FEmitterInstance EmitterInstance = EmitterTemplateManager.CreateEmitterInstance(EmitterTemplates[EmitterTemplateGuid.GetGuid(InId)]);
EmitterInstances.push_back(EmitterInstance);
EmitterInstanceManager.BindEmitterInstance(EmitterInstances[EmitterInstances.size() - 1]);
return GetInstance();
}
/**
* 生成发射器实例
*/
FEmitterTemplateManager& CreateEmitterTemplate(const unsigned int InId, const unsigned int InCopyId)
{
const unsigned int Guid = EmitterTemplateGuid.NewGuid(InId);
const unsigned int ReferGuid = EmitterTemplateGuid.GetGuid(InCopyId);
EmitterTemplates[Guid] = EmitterTemplates[ReferGuid];
return EmitterTemplateManager
.BindEmitterTemplate(EmitterTemplates[Guid])
.SetId(InId);
}
/**
* 启动所有发射器实例
*/
void RunEmitterInstances();
public:
/**
* 设置增量时间
*/
void SetDeltaTime(const float InDeltaTime)
{
CustomDeltaTime = InDeltaTime;
}
/**
* 获得增量时间 (s)
*/
float GetDeltaTime() const
{
return CustomDeltaTime / 1000.0f;
}
/**
* 设置版本信息
* @param InX 系统重大重构时 + 1
* @param InY 系统出现新的功能时 + 1
* @param InZ 系统零碎修改时 + 1
*/
FParticleSystem& SetVersion(const unsigned InX, const unsigned InY, const unsigned InZ)
{
FYielder::AvoidWarning = swprintf_s(Version, _T("%u.%u.%u"), InX, InY, InZ);
return GetInstance();
}
/**
* 获得版本信息 X.Y.Z
*/
const wchar_t* GetVersion()
{
return Version;
}
FParticleSystem& SetName(const wchar_t* InName)
{
FYielder::AvoidWarning = swprintf_s(Name, _T("%s"), InName);
return GetInstance();
}
const wchar_t* GetName()
{
return Name;
}
FParticleSystem& SetScreenSize(const FVector2D& InScreenSize) const
{
WindowManager.ScreenSize = InScreenSize;
return GetInstance();
}
FVector2D GetScreenSize() const
{
return WindowManager.ScreenSize;
}
private:
/**
* 处理输入
*/
void HandleInput();
/**
* 显示帮助信息
*/
void ShowHelp() const;
/**
* 绘制动画(一帧)
*/
void Render(const float InDeltaTime);
/**
* 计算 FPS
*/
float CalculateFps(const float InElapsedTime);
/**
* 计算增量时间
*/
float CalculateDeltaTime(const clock_t InCurTime);
/**
* 绝对延时
*/
static void Delay(const clock_t Ms);
/**
* 帧率限制
*/
float LimitFps(const float InElapsedTime) const;
public:
size_t ParticleTotalNum = 0; // 粒子总数
size_t ParticleCurNum = 0; // 粒子当前数
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; // 模拟总时间
public:
FWindowManager& WindowManager; // 窗口管理器
FParticleEffectManager& ParticleEffectManager; // 粒子特效管理器
FParticleTemplateManager& ParticleTemplateManager; // 粒子模板管理器
FParticleInstanceManager& ParticleInstanceManager; // 粒子实例管理器
FEmitterTemplateManager& EmitterTemplateManager; // 发射器模板管理器
FEmitterInstanceManager& EmitterInstanceManager; // 发射器实例管理器
public:
std::map<unsigned int, FParticleTemplate> ParticleTemplates; // 粒子模板数组
std::map<unsigned int, FEmitterTemplate> EmitterTemplates; // 发射器模板数组
std::vector<FEmitterInstance> EmitterInstances; // 发射器实例数组
FGuid ParticleTemplateGuid; // 粒子唯一标识
FGuid EmitterTemplateGuid; // 发射器唯一标识
public:
bool IsShowHelp = true; // 是否显示帮助
bool IsContinueGraphics = true; // 是否继续系统
bool IsContinueSimulate = true; // 是否继续模拟
bool IsRestartSimulate = false; // 是否重新模拟
public:
wchar_t Name[255] = _T("Particle System"); // 粒子系统名称
wchar_t Version[255] = _T("0.0.0"); // 粒子系统版本
};
ParticleSystem.cpp
#include "ParticleSystem.h"
std::vector<IMAGE> FTool::Images = {};
const float FYielder::PositiveFloatMin = 0.0f;
const float FYielder::PositiveFloatMax = 100000000.0f;
const float FYielder::PositivePercentMin = 0.0f;
const float FYielder::PositivePercentMax = 100.0f;
const float FYielder::PositivePercentZeroToOneMin = 0.0f;
const float FYielder::PositivePercentZeroToOneMax = 1.0f;
const float FYielder::PositiveDegreeMin = 0.0f;
const float FYielder::PositiveDegreeMax = 360.0f;
const float FYielder::PositiveRadianMin = 0.0f;
const float FYielder::PositiveRadianMax = 3.1415926f * 2.0f;
const COLORREF FYielder::ColorMin = RGB(0, 0, 0);
const COLORREF FYielder::ColorMax = RGB(255, 255, 255);
int FYielder::AvoidWarning = 0;
FParticleSystem& FParticleSystem::InitInstance()
{
// 初始化窗口管理器
wchar_t Message[510];
FYielder::AvoidWarning = swprintf_s(Message, _T("%s %s"), Name, GetVersion());
WindowManager.Initialize(WindowManager.ScreenSize.X, WindowManager.ScreenSize.Y, Message);
ParticleEffectManager.Initialize();
// 设置随机种子
srand(static_cast<unsigned>(time(nullptr) + 5201314));
// 其他
CustomDeltaTime = 1000.0f / CustomHighFps;
return GetInstance();
}
void FParticleSystem::RunEmitterInstances()
{
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<float>(NewTime - OldTime)));
// 计算总时间
SimulateTotalTime += (IsContinueSimulate == true ? static_cast<float>(clock() - OldTime) / 1000.0f : 0.0f);
}
}
void FParticleSystem::HandleInput()
{
ExMessage Input{};
while (peekmessage(&Input, EM_MOUSE | EM_KEY))
{
switch (Input.message)
{
case WM_MOUSEMOVE:
{
WindowManager.CurMouseLocation = {static_cast<float>(Input.x), static_cast<float>(Input.y)};
WindowManager.MouseVelocity = WindowManager.CurMouseLocation - WindowManager.PrevMouseLocation;
WindowManager.PrevMouseLocation = WindowManager.CurMouseLocation;
break;
}
case WM_MOUSEWHEEL:
{
WindowManager.WheelSize = min(max(WindowManager.WheelSize + Input.wheel / 500.0f, 0.5f), 5.0f);
break;
}
case WM_LBUTTONDOWN:
{
WindowManager.IsLeftMouseDown = true;
break;
}
case WM_LBUTTONUP:
{
WindowManager.IsLeftMouseDown = false;
break;
}
case WM_RBUTTONDOWN:
{
WindowManager.IsRightMouseDown = true;
break;
}
case WM_RBUTTONUP:
{
WindowManager.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;
default: ParticleEffectManager.ReverseIsEnableByKeyboard(Input.vkcode);
break;
}
break;
}
default: break;
}
}
}
void FParticleSystem::ShowHelp() const
{
if (IsShowHelp)
{
static wchar_t Message[255];
static constexpr int TextHeight = 16;
int Lines = -1;
FYielder::AvoidWarning = swprintf_s(Message, _T("\t\tfps: %.1f"), static_cast<double>(GraphicsFps));
outtextxy(0, TextHeight * (++Lines), Message);
FYielder::AvoidWarning = swprintf_s(Message, _T("\t\ttime: %.1fms"), static_cast<double>(GraphicsTime));
outtextxy(0, TextHeight * (++Lines), Message);
FYielder::AvoidWarning = swprintf_s(Message,
_T("\t\tparticles: %u / %u"),
static_cast<unsigned int>(ParticleCurNum),
static_cast<unsigned int>(ParticleTotalNum));
outtextxy(0, TextHeight * (++Lines), Message);
FYielder::AvoidWarning = swprintf_s(Message,
_T("\t\ttotal: %.2fs"),
static_cast<double>(SimulateTotalTime));
outtextxy(0, TextHeight * (++Lines), Message);
++Lines;
outtextxy(0, TextHeight * (++Lines), _T("\t\tMouse"));
outtextxy(0, TextHeight * (++Lines), _T("\t\tMove: Stir"));
outtextxy(0, TextHeight * (++Lines), _T("\t\tLeft: Gather"));
outtextxy(0, TextHeight * (++Lines), _T("\t\tRight: Blow"));
outtextxy(0, TextHeight * (++Lines), _T("\t\tWheel: Scale"));
++Lines;
outtextxy(0, TextHeight * (++Lines), _T("\t\tKeyboard"));
outtextxy(0, TextHeight * (++Lines), _T("\t\t[Esc]: Exit System"));
outtextxy(0, TextHeight * (++Lines), _T("\t\t[Tab]: Hide Menu"));
outtextxy(0, TextHeight * (++Lines), _T("\t\t[Space]: Pause System"));
outtextxy(0, TextHeight * (++Lines), _T("\t\t[Enter]: Restart Simulate"));
++Lines;
outtextxy(0, TextHeight * (++Lines), _T("\t\tSpecial"));
for (BYTE i = 'A'; i <= 'Z'; ++i)
{
const auto ParticleEffect = ParticleEffectManager.GetParticleEffectByKeyboard(i);
if (ParticleEffect)
{
FYielder::AvoidWarning = swprintf_s(Message, _T("\t\t[%c]: Effect - %s %s"),
ParticleEffect->Keyboard,
ParticleEffect->HelpContent,
FTool::BoolToSwitch(ParticleEffect->IsEnable));
outtextxy(0, TextHeight * (++Lines), Message);
}
}
}
}
void FParticleSystem::Render(const float InDeltaTime)
{
if (IsContinueSimulate)
{
if (IsRestartSimulate)
{
IsRestartSimulate = false;
SimulateTotalTime = 0.0f;
ParticleCurNum = 0;
ParticleTotalNum = 0;
for (auto& EmitterInstance : EmitterInstances)
{
EmitterInstanceManager.BindEmitterInstance(EmitterInstance)
.SetIsRender(false)
.SetRenderingNum(0)
.SetRenderedNum(0)
.SetDeadNum(0)
.RegenerateParticleInstance()
.Finish();
ParticleTotalNum += EmitterInstanceManager.GetTotalNum();
}
}
else
{
ShowHelp();
static FParticleInstance Instance;
ParticleEffectManager.OnEffectMouseField(InDeltaTime, Instance);
ParticleEffectManager.OnEffectDisappearTrack(InDeltaTime, Instance);
ParticleCurNum = 0;
for (auto& EmitterInstance : EmitterInstances)
{
std::vector<FParticleInstance>& Particles = EmitterInstanceManager
.BindEmitterInstance(EmitterInstance)
.GetParticleInstances();
if (EmitterInstanceManager.GetTime() <= SimulateTotalTime)
{
EmitterInstanceManager.SetIsRender(true).Finish();
static size_t NewRenderNum;
static size_t RenderingNum;
static size_t RenderedNum;
static size_t DeadNum;
static size_t RemainNum;
NewRenderNum = 0;
RenderingNum = EmitterInstanceManager.GetRenderingNum();
RenderedNum = EmitterInstanceManager.GetRenderedNum();
DeadNum = EmitterInstanceManager.GetDeadNum();
RemainNum = EmitterInstanceManager.GetTotalNum() - EmitterInstanceManager.GetRenderedNum();
if (RemainNum > 0)
{
NewRenderNum = FTool::Clamp(EmitterInstanceManager.GetSpeed(), static_cast<size_t>(0), RemainNum);
EmitterInstanceManager.SetRenderedNum(RenderedNum + NewRenderNum).Finish();
EmitterInstanceManager.SetRenderingNum(RenderingNum + NewRenderNum).Finish();
}
for (size_t i = 0; i < EmitterInstanceManager.GetRenderingNum(); ++i)
{
Particles[i].IsRendering = true;
ParticleEffectManager.OnEffectScaleWithWheel(InDeltaTime, Particles[i]);
if (Particles[i].IsPendingKilled == true)
{
std::swap(Particles[i], Particles[Particles.size() - 1]);
Particles.pop_back();
EmitterInstanceManager.SetRenderingNum(EmitterInstanceManager.GetRenderingNum() - 1).Finish();
EmitterInstanceManager.SetDeadNum(EmitterInstanceManager.GetDeadNum() + 1).Finish();
}
else
{
if (!Particles[i].Effects.empty())
{
for (const auto& Effect : Particles[i].Effects)
{
if (Effect->IsEnable)
{
Effect->Function(InDeltaTime, Particles[i]);
}
}
}
Particles[i].NextLocation = Particles[i].Location + Particles[i].Velocity;
if (ParticleEffectManager.GetParticleEffect(static_cast<unsigned>
(EffectType::ReboundWall)).IsEnable)
{
ParticleEffectManager.OnEffectReboundWall(InDeltaTime, Instance);
}
Particles[i].Location = Particles[i].NextLocation;
if (Particles[i].ShapeType == EShapeType::Image)
{
FTool::PutImageA(Particles[i].Image, Particles[i].Location);
}
else
{
FTool::Draw(Particles[i].ShapeType
, Particles[i].Color.sRGB
, Particles[i].Location
, floorf(Particles[i].Radius *
Particles[i].Scale *
WindowManager.WheelSize));
}
}
}
ParticleCurNum += EmitterInstanceManager.GetRenderingNum();
if (RemainNum <= 0)
{
if (EmitterInstanceManager.GetIsLoop())
{
EmitterInstanceManager.BindEmitterInstance(EmitterInstance)
.RegenerateParticleInstance()
.Finish();
}
}
}
}
}
}
}
float FParticleSystem::CalculateFps(const float InElapsedTime)
{
static constexpr int FpsCount = 8;
static int Count = 0;
static float TotalElapsedTime = 0;
++Count;
GraphicsTime = InElapsedTime;
TotalElapsedTime += InElapsedTime;
if (Count >= FpsCount)
{
GraphicsFps = static_cast<float>(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<float>(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<clock_t>(OffsetTime + 1));
}
else
{
OffsetTime = 0.0f;
}
return InElapsedTime + OffsetTime;
}
void FParticleEffectManager::OnEffectMouseField(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::MouseField)).IsEnable)
{
static float Radius = 2.0f;
static float Times = 1.5f;
static float MouseFieldSpeed = 0.5f;
static COLORREF Color = RGB(187, 34, 128);
if (WindowManager.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<int>(WindowManager.CurMouseLocation.X + 0.01f * WindowManager.MouseVelocity.X),
static_cast<int>(WindowManager.CurMouseLocation.Y + 0.01f * WindowManager.MouseVelocity.Y),
static_cast<int>(max(Radius, 5.0f)));
}
else
{
if (Times > 0.0f)
{
Times -= MouseFieldSpeed * InDeltaTime;
Radius -= MouseFieldSpeed * InDeltaTime;
setfillcolor(Color);
setlinecolor(Color);
fillcircle(static_cast<int>(WindowManager.CurMouseLocation.X),
static_cast<int>(WindowManager.CurMouseLocation.Y),
static_cast<int>(max(Radius, 2.0f)));
}
else
{
Times = 0.0f;
Color = RGB(FTool::GenerateRandomNumber(255), FTool::GenerateRandomNumber(255),
FTool::GenerateRandomNumber(255));
}
}
}
}
void FParticleEffectManager::OnEffectDisappearTrack(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::DisappearTrack)).IsEnable)
{
for (int i = static_cast<int>(WindowManager.ScreenSize.X * WindowManager.ScreenSize.Y) - 1; i >= 0; --i)
{
if (WindowManager.GBuffer[i] != 0)
{
WindowManager.GBuffer[i] = RGB(GetRValue(WindowManager.GBuffer[i]) *0.66,
GetGValue(WindowManager.GBuffer[i]) *0.66,
GetBValue(WindowManager.GBuffer[i]) *0.66);
/*WindowManager.GBuffer[i] = RGB(GetRValue(WindowManager.GBuffer[i]) >> 1,
GetGValue(WindowManager.GBuffer[i]) >> 1,
GetBValue(WindowManager.GBuffer[i]) >> 1);*/
}
}
}
}
void FParticleEffectManager::OnEffectReboundWall(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::ReboundWall)).IsEnable && InParticleInstance.IsRendering)
{
static float ReboundLoss = 0.85f;
if (InParticleInstance.NextLocation.X > WindowManager.ScreenSize.X)
{
InParticleInstance.NextLocation.X = WindowManager.ScreenSize.X;
InParticleInstance.Velocity.X *= -1 * ReboundLoss;
}
else if (InParticleInstance.NextLocation.X < 0)
{
InParticleInstance.NextLocation.X = 0;
InParticleInstance.Velocity.X *= -1 * ReboundLoss;
}
if (InParticleInstance.NextLocation.Y > WindowManager.ScreenSize.Y)
{
InParticleInstance.NextLocation.Y = WindowManager.ScreenSize.Y;
InParticleInstance.Velocity.Y *= -1 * ReboundLoss;
}
else if (InParticleInstance.NextLocation.Y < 0)
{
InParticleInstance.NextLocation.Y = 0;
InParticleInstance.Velocity.Y *= -1 * ReboundLoss;
}
}
}
void FParticleEffectManager::OnEffectRadiusAboutSpeed(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::RadiusAboutSpeed)).IsEnable && InParticleInstance.IsRendering)
{
const float AvgVx = fabs(InParticleInstance.Velocity.X);
const float AvgVy = fabs(InParticleInstance.Velocity.Y);
const float AvgV = (AvgVx + AvgVy) * 0.5f;
InParticleInstance.Radius = AvgV * 0.45f + 0.5f;
InParticleInstance.Radius = FTool::Clamp(InParticleInstance.Radius, 0.5f, 1.5f);
}
}
void FParticleEffectManager::OnEffectBlow(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Blow)).IsEnable && InParticleInstance.IsRendering)
{
static float BlowDis = WindowManager.ScreenSize.X * 0.5f;
float DisX = InParticleInstance.Location.X - WindowManager.CurMouseLocation.X;
float DisY = InParticleInstance.Location.Y - WindowManager.CurMouseLocation.Y;
const float Radius = sqrt(DisX * DisX + DisY * DisY);
DisX = Radius > 0.01f ? DisX / Radius : 0;
DisY = Radius > 0.01f ? DisY / Radius : 0;
if (WindowManager.IsRightMouseDown && Radius < BlowDis)
{
constexpr float BlowSpeed = 5.0f;
const float BlowAcc = (1 - (Radius / BlowDis)) * 14;
InParticleInstance.Velocity.X += DisX * BlowAcc * BlowSpeed * InDeltaTime + 0.5f - static_cast<float>(
FTool::GenerateRandomNumber()) / RAND_MAX;
InParticleInstance.Velocity.Y += DisY * BlowAcc * BlowSpeed * InDeltaTime + 0.5f - static_cast<float>(
FTool::GenerateRandomNumber()) / RAND_MAX;
}
}
}
void FParticleEffectManager::OnEffectGather(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Gather)).IsEnable && InParticleInstance.IsRendering)
{
static float GatherDis = WindowManager.ScreenSize.X * 0.86f;
float DisX = InParticleInstance.Location.X - WindowManager.CurMouseLocation.X;
float DisY = InParticleInstance.Location.Y - WindowManager.CurMouseLocation.Y;
const float Radius = FTool::CalculateLength({DisX, DisY});
DisX = Radius > 0.01f ? DisX / Radius : 0;
DisY = Radius > 0.01f ? DisY / Radius : 0;
if (WindowManager.IsLeftMouseDown && Radius < GatherDis)
{
constexpr float GatherSpeed = 26.0f;
const float GatherAcc = (1 - (Radius / GatherDis)) * WindowManager.ScreenSize.X * 0.0014f;
InParticleInstance.Velocity.X -= DisX * GatherAcc * GatherSpeed * InDeltaTime;
InParticleInstance.Velocity.Y -= DisY * GatherAcc * GatherSpeed * InDeltaTime;
}
}
}
void FParticleEffectManager::OnEffectStir(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Stir)).IsEnable && InParticleInstance.IsRendering)
{
static float StirDis = WindowManager.ScreenSize.X * 0.125f;
const float DisX = InParticleInstance.Location.X - WindowManager.CurMouseLocation.X;
const float DisY = InParticleInstance.Location.Y - WindowManager.CurMouseLocation.Y;
const float Radius = FTool::CalculateLength({DisX, DisY});
if (!WindowManager.IsLeftMouseDown && !WindowManager.IsRightMouseDown && Radius < StirDis)
{
constexpr float StirSpeed = 35.0f;
const float StirAcc = (1 - (Radius / StirDis)) * WindowManager.ScreenSize.X * 0.00026f;
InParticleInstance.Velocity +=
{
WindowManager.MouseVelocity.X * StirAcc * StirSpeed * InDeltaTime,
WindowManager.MouseVelocity.Y * StirAcc * StirSpeed * InDeltaTime
};
}
}
}
void FParticleEffectManager::OnEffectFriction(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Friction)).IsEnable && InParticleInstance.IsRendering)
{
InParticleInstance.Velocity *= InParticleInstance.Friction;
}
}
void FParticleEffectManager::OnEffectVortex(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Vortex)).IsEnable && InParticleInstance.IsRendering)
{
static std::vector<FVortex> Vortexes;
static constexpr int VortexesNum = 3;
if (Vortexes.empty())
{
for (int i = 0; i < VortexesNum; ++i)
{
float PosX = 10.0f + FTool::GenerateRandomNumber(WindowManager.ScreenSize.X - 1, 1.0f);
float PosY = 10.0f + FTool::GenerateRandomNumber(WindowManager.ScreenSize.Y - 1, 1.0f);
float Radius = max(30.0f, 30.0f + FTool::GenerateRandomNumber(29.0f, 1.0f));
const float Speed = 20.0f + FTool::GenerateRandomNumber(50.0f, 1.0f);
if ((WindowManager.ScreenSize.X - PosX) <= 10)
{
PosX = WindowManager.ScreenSize.X - 20;
Radius = 15.0f;
}
if ((WindowManager.ScreenSize.Y - PosY) <= 10)
{
PosY = WindowManager.ScreenSize.Y - 20;
Radius = 15.0f;
}
Vortexes.push_back({{PosX, PosY}, Radius, Speed});
}
}
else
{
for (int i = 0; i < VortexesNum; ++i)
{
const float Vx = InParticleInstance.Location.X - Vortexes[i].Position.X - 1;
const float Vy = InParticleInstance.Location.Y - Vortexes[i].Position.Y - 1;
const float Distance = FTool::CalculateLength({Vx, Vy});
static float RotationSpeed = 35.0f + FTool::GenerateRandomNumber(10.0f, 1.0f);
if (Distance <= Vortexes[i].Radius)
{
InParticleInstance.Location =
{
Vortexes[i].Position.X + Vx * cos(RotationSpeed * InDeltaTime) + Vy * sin(
Vortexes[i].RotateSpeed * InDeltaTime),
Vortexes[i].Position.Y + Vy * cos(RotationSpeed * InDeltaTime) - Vx * sin(
Vortexes[i].RotateSpeed * InDeltaTime)
};
if (FTool::GenerateRandomNumber(10) > 7)
{
InParticleInstance.Location +=
{
FTool::GenerateRandomSign<float>() * FTool::GenerateRandomNumber(
5.0f, 1.0f),
FTool::GenerateRandomSign<float>() * FTool::GenerateRandomNumber(
5.0f, 1.0f)
};
}
}
}
}
}
}
void FParticleEffectManager::OnEffectRandomMove(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::RandomMove)).IsEnable && InParticleInstance.IsRendering)
{
static float RandomDis = WindowManager.ScreenSize.X;
float DisX = InParticleInstance.Location.X - WindowManager.CurMouseLocation.X;
float DisY = InParticleInstance.Location.Y - WindowManager.CurMouseLocation.Y;
const float Radius = FTool::CalculateLength({DisX, DisY});
DisX = Radius > 0.01f ? DisX / Radius : 0;
DisY = Radius > 0.01f ? DisY / Radius : 0;
if (Radius < RandomDis)
{
constexpr float RandomSpeed = 20.0f;
const float RandomAcc = FTool::GenerateRandomSign<float>() * Radius / RandomDis * 0.5f;
InParticleInstance.Velocity +=
{
DisX * RandomAcc * RandomSpeed * InDeltaTime + 0.5f - static_cast<float>(
FTool::GenerateRandomNumber()) / RAND_MAX,
DisY * RandomAcc * RandomSpeed * InDeltaTime + 0.5f - static_cast<float>(
FTool::GenerateRandomNumber()) / RAND_MAX
};
const float AvgVx = fabs(InParticleInstance.Velocity.X);
const float AvgVy = fabs(InParticleInstance.Velocity.Y);
if (AvgVx < 0.1f) InParticleInstance.Velocity.X *= static_cast<float>(FTool::GenerateRandomNumber()) / RAND_MAX * 2;
if (AvgVy < 0.1f) InParticleInstance.Velocity.Y *= static_cast<float>(FTool::GenerateRandomNumber()) / RAND_MAX * 2;
}
}
}
void FParticleEffectManager::OnEffectScaleWithWheel(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::ScaleWithWheel)).IsEnable)
{
// nothing to do
}
else
{
WindowManager.WheelSize = 1.0f;
}
}
void FParticleEffectManager::OnEffectGravity(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Gravity)).IsEnable && InParticleInstance.IsRendering)
{
if ((WindowManager.ScreenSize.Y - InParticleInstance.Location.Y) >= 5)
{
static float GravitySpeed = 1.0f;
static float Gravity = 10.0f;
InParticleInstance.Velocity.Y += Gravity * GravitySpeed * InDeltaTime;
}
}
}
void FParticleEffectManager::OnEffectDeath(const float InDeltaTime, FParticleInstance& InParticleInstance)
{
if (GetParticleEffect(static_cast<unsigned>(EffectType::Death)).IsEnable && InParticleInstance.IsRendering)
{
static float DeathSpeed = 1.0f;
InParticleInstance.LifeTime -= DeathSpeed * InDeltaTime;
if (InParticleInstance.LifeTime <= 0)
{
InParticleInstance.LifeTime = 0;
InParticleInstance.IsPendingKilled = true;
}
}
}
虽然代码有注释,但是如果存在不明白或错误之处,欢迎留言。
四、项目记录
## 一、概念介绍
粒子系统的一个普遍公认的定义是:粒子系统由具有相同属性规则的微小个体组成,
大量微小个体可以随机显示不同特征的粒子的集合体。
## 二、编码规范
命名法
- PascalCase
前缀
- F 默认
- E 枚举
- Is 表询问的布尔值
- In 形参
其他
- 函数声明提供:文档注释
- 变量声明提供:行注释
- 不同作用的成员隔开:类权限修饰符
- 尽量加 const 修饰符
- 尽量传 const& 参数
- 使用:Setter / Getter 提供访问控制
- 使用 Tab 对齐注释
- 能使用 static 函数不使用 non - static
- 能在 编译期 报错就绝不在 运行期 报错
- 能使用 变量 就绝不写 固定数值
- 不要忽视任何一个 warning
## 三、可控属性
发射器
- 标识符:unsigned int >0 无重复,用于寻找
- 名称:wchar_t 0 - 255 个字符
- 说明:wchar_t 0 - 255 个字符
- 发射数量:unsigned int > 0 总共发射多少粒子
- 发射时间:float > 0 粒子系统运行多久后发射
- 发射速率:size_t > 0 每帧发射多少粒子
- 发射角度:float 以 y 轴正半轴顺时针转 0 - 360
- 发射位置:FVector2D
- 是否循环:bool
粒子
- 标识符:unsigned int > 0 无重复,用于寻找
- 名称:wchar_t 0 - 255 个字符
- 说明:wchar_t 0 - 255 个字符
- 颜色:COLORREF RGB() 随机请使用 EGenerateType::RandColor
- 形状:enum
- 初始位置:FVector2D 相对发射器的坐标
- 初始速率:float > 0
- 阻力:float 0.0 - 1.0
- 半径:float > 0
- 生命周期:float > 0
- 缩放:float
- 启用特效:vector
特效
- 功能指定
- 按键绑定
- 提示文本
- 是否菜单显示
- 是否启用
## 四、Bug
- DeltaTime 未完全利用
- IsRender 属性应该归粒子所有,这样才好计算生命周期 (已解决)
- 粒子渲染错误,奇怪的数组越界(已解决)
- 阻力公式错误
- 吐血,弧度转角度计算错误(已解决)
- 特效应该分渲染时段,有些只需要对粒子整体操作,而有些需要对每一个粒子操作
- 缩放控制失败,应该还是特效分时段的问题(已解决)
## 五、更新日志
采用 x.y.z 模式
- x 系统重大重构时 + 1
- y 系统出现新的功能时 + 1
- z 系统零碎修改时 + 1
2020 - 08 - 01 版本 1.0.0
- x 研究 Krissi 大佬的粒子系统代码
- x 研究 EasyX 平台其他作品的代码
2020 - 08 - 02 版本 1.6.0
- y 提供 ParticlesSystem 类,Particle 类
- y 添加基础属性/特效函数
- y 添加更多按键
- y 添加提示信息
- y 计算 fps,deltaTime,particles 等数据
2020 - 08 - 03 版本 1.11.3
- y 提供 Emitter 类,EmitterManager 类,Utlis 类
- z 解决 fps 计算和限制问题
- z 解决部分 deltaTime 计算和使用错误问题
- y 规范语法,大幅度减少编译器警告
- y 现在可以使用链式调用设置 Emitter 类
- y 新增开始时间属性
- y 新增位置属性
- z 完善单例模式
2020 - 08 - 04 版本 1.15.6
- y 提供 FParticleManager 类
- z 实装 FVector2D 类
- y 提供 Float 类,作用是数值
- z 实装 Float 类
- z Float 类改名为 FYielder 类,作用是生成数值
- y 数值异常处理
- y 提供总时间提示
2020 - 08 - 05 版本 2.0.0
- y 新增发射时间和生命周期关联
- y 新增发射速率
- z 继续规范命名
- y 现在可以设置系统的名称和版本
- x 重新设计粒子系统的执行流程,大幅度修改代码
2020 - 08 - 06 版本 2.4.1
- z 优化命名
- y 提供 FGuid 类,提供唯一标识符
- y 实装 FGuid 类
- y 提供参考已有模板创建新模板的功能
- y 提供可变参数创建发射器实例
2020 - 08 - 07 版本 2.6.1
- y 分离特效控制,提供自定义特效接口
- y 完成特效分离
2020 - 08 - 08 版本 2.8.1
- y 提供粒子形状属性
- y 提供自定义贴图
感谢看完!!!
``