MoOtA

粒子系统 (Particle System) 银牌收录

一、作品说明

基于 **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 提供自定义贴图

感谢看完!!!

``

评论 (2) -

添加评论