源码解读:
GameLayer层级:在构造函数中:创建窗口和相机,对随机数种子初始化;
在OnAttach方法中:初始化关卡:加载资源初始化地图信息;设置字体;
在OnUpdate方法中:判断游戏状态;设置相机跟随;计算/刷新关卡,角色,粒子的变换、碰撞、颜色、生命周期等信息;接着根据刷新的信息,渲染关卡、角色、粒子
在OnImGuiRender方法中:判断状态渲染UI;
在OnEvent方法中:拦截窗口大小改变和鼠标点击事件,改变相机大小\根据状态判断是否重置游戏;
代码:
SandboxApp.cpp:
#include<YOTO.h>
//入口点
#include"YOTO/Core/EntryPoint.h"
#include "imgui/imgui.h"
#include<stdio.h>
#include <glm/gtc/matrix_transform.hpp>
#include <Platform/OpenGL/OpenGLShader.h>
#include <glm/gtc/type_ptr.hpp>
#include "Sandbox2D.h"
#include"GameLayer.h"
class Sandbox:public YOTO::Application
{
public:
Sandbox(){
//加入层级
PushLayer(new GameLayer());
//PushLayer(new Sandbox2D());
}
~Sandbox() {
}
private:
};
/// <summary>
/// 创建App
/// </summary>
/// <returns></returns>
YOTO::Application* YOTO::CreateApplication() {
YT_PROFILE_FUNCTION();
return new Sandbox();
}
GameLayer.h:
#pragma once
#include "YOTO.h"
#include "Level.h"
#include <imgui/imgui.h>
class GameLayer : public YOTO::Layer
{
public:
GameLayer();
virtual ~GameLayer() = default;
virtual void OnAttach() override;
virtual void OnDetach() override;
void OnUpdate(YOTO::Timestep ts) override;
virtual void OnImGuiRender() override;
void OnEvent(YOTO::Event& e) override;
bool OnMouseButtonPressed(YOTO::MouseButtonPressedEvent& e);
bool OnWindowResize(YOTO::WindowResizeEvent& e);
private:
void CreateCamera(uint32_t width, uint32_t height);
private:
YOTO::Scope<YOTO::OrthographicCamera> m_Camera;
Level m_Level;
ImFont* m_Font;
float m_Time = 0.0f;
bool m_Blink = false;
enum class GameState
{
Play = 0, MainMenu = 1, GameOver = 2
};
GameState m_State = GameState::MainMenu;
};
GameLayer.cpp:
#include "GameLayer.h"
#include <imgui/imgui.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
GameLayer::GameLayer()
: Layer("GameLayer")
{ //创建窗口
auto& window = YOTO::Application::Get().GetWindow();
//创建相机m_Camera赋值
CreateCamera(window.GetWidth(), window.GetHeight());
//初始化随机数系统,确定种子
Random::Init();
}
void GameLayer::OnAttach()
{
//初始化关卡,加载资源、初始化关卡
m_Level.Init();
//设置UI字体
ImGuiIO io = ImGui::GetIO();
m_Font = io.Fonts->AddFontFromFileTTF("assets/OpenSans-Regular.ttf", 120.0f);
}
void GameLayer::OnDetach()
{
}
/// <summary>
/// Update
/// </summary>
/// <param name="ts"></param>
void GameLayer::OnUpdate(YOTO::Timestep ts)
{
//记录游戏开始总时间
m_Time += ts;
//控制闪烁
if ((int)(m_Time * 10.0f) % 8 > 4)
m_Blink = !m_Blink;
//判断是否游戏结束
if (m_Level.IsGameOver())
//切换游戏状态
m_State = GameState::GameOver;
//获取角色位置,相机跟踪角色
const auto& playerPos = m_Level.GetPlayer().GetPosition();
m_Camera->SetPosition({ playerPos.x, playerPos.y, 0.0f });
//如果游戏还在进行中
switch (m_State)
{
case GameState::Play:
{
//刷新关卡,角色,粒子
m_Level.OnUpdate(ts);
break;
}
}
// Render
YOTO::RenderCommand::SetClearColor({ 0.0f, 0.0f, 0.0f, 1 });
YOTO::RenderCommand::Clear();
//设置相机,设置VP矩阵
YOTO::Renderer2D::BeginScene(*m_Camera);
//渲染
m_Level.OnRender();
YOTO::Renderer2D::EndScene();
}
void GameLayer::OnImGuiRender()
{
//ImGui::Begin("Settings");
//m_Level.OnImGuiRender();
//ImGui::End();
//根据不同的状态渲染不同的UI
switch (m_State)
{
case GameState::Play:
{
uint32_t playerScore = m_Level.GetPlayer().GetScore();
std::string scoreStr = std::string("Score: ") + std::to_string(playerScore);
ImGui::GetForegroundDrawList()->AddText(m_Font, 48.0f, ImGui::GetWindowPos(), 0xffffffff, scoreStr.c_str());
break;
}
case GameState::MainMenu:
{
auto pos = ImGui::GetWindowPos();
auto width = YOTO::Application::Get().GetWindow().GetWidth();
auto height = YOTO::Application::Get().GetWindow().GetHeight();
pos.x += width * 0.5f - 300.0f;
pos.y += 50.0f;
if (m_Blink)
ImGui::GetForegroundDrawList()->AddText(m_Font, 120.0f, pos, 0xffffffff, "Click to Play!");
break;
}
case GameState::GameOver:
{
auto pos = ImGui::GetWindowPos();
auto width = YOTO::Application::Get().GetWindow().GetWidth();
auto height = YOTO::Application::Get().GetWindow().GetHeight();
pos.x += width * 0.5f - 300.0f;
pos.y += 50.0f;
if (m_Blink)
ImGui::GetForegroundDrawList()->AddText(m_Font, 120.0f, pos, 0xffffffff, "Click to Play!");
pos.x += 200.0f;
pos.y += 150.0f;
uint32_t playerScore = m_Level.GetPlayer().GetScore();
std::string scoreStr = std::string("Score: ") + std::to_string(playerScore);
ImGui::GetForegroundDrawList()->AddText(m_Font, 48.0f, pos, 0xffffffff, scoreStr.c_str());
break;
}
}
}
void GameLayer::OnEvent(YOTO::Event& e)
{
//拦截窗口大小改变和鼠标点击
YOTO::EventDispatcher dispatcher(e);
dispatcher.Dispatch<YOTO::WindowResizeEvent>(YT_BIND_EVENT_FN(GameLayer::OnWindowResize));
dispatcher.Dispatch<YOTO::MouseButtonPressedEvent>(YT_BIND_EVENT_FN(GameLayer::OnMouseButtonPressed));
}
/// <summary>
/// 鼠标点击
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
bool GameLayer::OnMouseButtonPressed(YOTO::MouseButtonPressedEvent& e)
{
//当游戏结束时候点击重置关卡
if (m_State == GameState::GameOver)
m_Level.Reset();
m_State = GameState::Play;
return false;
}
/// <summary>
/// 窗口大小改变
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
bool GameLayer::OnWindowResize(YOTO::WindowResizeEvent& e)
{
//创建相机,修改相机的宽高
CreateCamera(e.GetWidth(), e.GetHeight());
return false;
}
/// <summary>
/// 创建相机
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
void GameLayer::CreateCamera(uint32_t width, uint32_t height)
{
//宽高比
float aspectRatio = (float)width / (float)height;
//宽度
float camWidth = 8.0f;
float bottom = -camWidth;
float top = camWidth;
float left = bottom * aspectRatio;
float right = top * aspectRatio;
//创建相机
m_Camera = YOTO::CreateScope<YOTO::OrthographicCamera>(left, right, bottom, top);
}
Random.h:
#pragma once
#include<random>
//< random > :提供了随机数生成相关的类和函数。
//std::mt19937:Mersenne Twister 伪随机数生成器,可以生成高质量的随机数序列。
//std::random_device:用于生成真随机数的设备,通常是硬件随机数生成器。
//std::uniform_int_distribution:生成均匀分布的随机整数。
//std::numeric_limits<uint32_t>::max():返回 uint32_t 类型的最大值,用于归一化随机数。
class Random
{
public:
static void Init() {
s_RandomEngine.seed(std::random_device()());
// 使用随机设备生成种子,以当前时间为种子
}
static float Float() {
// 生成一个范围在[0,1]之间的随机浮点数
return (float)s_Distribution(s_RandomEngine) / (float)std::numeric_limits<uint32_t>::max();
// 通过均匀分布生成随机数,将其归一化到[0,1]之间
}
private:
//Mersenne Twister 伪随机数生成器,可以生成高质量的随机数序列。
static std::mt19937 s_RandomEngine;
//生成均匀分布的随机整数。
static std::uniform_int_distribution<std::mt19937::result_type> s_Distribution;
};
Random.cpp:
#include "Random.h"
std::mt19937 Random::s_RandomEngine;
std::uniform_int_distribution<std::mt19937::result_type> Random::s_Distribution;
Level.h:
#pragma once
#include"YOTO.h"
#include"Player.h"
/// <summary>
/// 每个三角刺的位置
/// </summary>
struct Pillar
{
glm::vec3 TopPosition = { 0.0f, 10.0f, 0.0f };
glm::vec2 TopScale = { 15.0f, 20.0f };
glm::vec3 BottomPosition = { 10.0f, 10.0f, 0.0f };
glm::vec2 BottomScale = { 15.0f, 20.0f };
};
class Level
{public:
void Init();
void OnUpdate(YOTO::Timestep ts);
void OnRender();
void OnImGuiRender();
bool IsGameOver()const { return m_GameOver; }
void Reset();
Player& GetPlayer() { return m_Player; }
private:
void CreatePillar(int index, float offset);
bool CollisionTest();
void GameOver();
private:
Player m_Player;
bool m_GameOver;
float m_PillarTarget = 30.0f;
int m_PillarIndex = 0;
glm::vec3 m_PillarHSV = { 0.0f,0.8f,0.8f };
std::vector<Pillar> m_Pillars;
YOTO::Ref<YOTO::Texture2D>m_TriangleTexture;
};
Level.cpp:
#include "Level.h"
#include<YOTO/Renderer/Texture.h>
#include"Random.h"
#include <glm/gtc/matrix_transform.hpp>
/// <summary>
/// 变换
/// </summary>
/// <param name="hsv"></param>
/// <returns></returns>
static glm::vec4 HSVtoRGB(const glm::vec3& hsv) {
int H = (int)(hsv.x * 360.0f);
double S = hsv.y;
double V = hsv.z;
double C = S * V;
double X = C * (1 - abs(fmod(H / 60.0, 2) - 1));
double m = V - C;
double Rs, Gs, Bs;
if (H >= 0 && H < 60) {
Rs = C;
Gs = X;
Bs = 0;
}
else if (H >= 60 && H < 120) {
Rs = X;
Gs = C;
Bs = 0;
}
else if (H >= 120 && H < 180) {
Rs = 0;
Gs = C;
Bs = X;
}
else if (H >= 180 && H < 240) {
Rs = 0;
Gs = X;
Bs = C;
}
else if (H >= 240 && H < 300) {
Rs = X;
Gs = 0;
Bs = C;
}
else {
Rs = C;
Gs = 0;
Bs = X;
}
return { (Rs + m), (Gs + m), (Bs + m), 1.0f };
}
/// <summary>
/// 判断是否在三角形内
/// </summary>
/// <param name="p">角色点</param>
/// <param name="p0">三角形点</param>
/// <param name="p1">三角形点</param>
/// <param name="p2">三角形点</param>
/// <returns></returns>
static bool PointInTri(const glm::vec2& p, glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p2)
{
float s = p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y;
float t = p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y;
if ((s < 0) != (t < 0))
return false;
float A = -p1.y * p2.x + p0.y * (p2.x - p1.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y;
return A < 0 ?
(s <= 0 && s + t >= A) :
(s >= 0 && s + t <= A);
}
/// <summary>
/// 初始化关卡
/// </summary>
void Level::Init()
{
//创建关卡中三角形的纹理
m_TriangleTexture = YOTO::Texture2D::Create("assets/textures/Triangle.png");
//加载角色资源,加载角色纹理
m_Player.LoadAssets();
//初始化三角刺的容器大小为5个
m_Pillars.resize(5);
//生成5个刺
for (int i = 0; i < 5; i++)//索引为0-5,偏移量为0-50
CreatePillar(i, i * 10.0f);
}
/// <summary>
/// Update
/// </summary>
/// <param name="ts"></param>
void Level::OnUpdate(YOTO::Timestep ts)
{
//刷新角色位置,和粒子
m_Player.OnUpdate(ts);
//进行碰撞检测
if (CollisionTest()) {
//碰到就游戏结束
GameOver();
return;
}
//柱子的颜色
m_PillarHSV.x += 0.1f * ts;
if (m_PillarHSV.x > 1.0f) {
m_PillarHSV.x = 0.0f;
}
//如果角色到达柱子位置
if (m_Player.GetPosition().x > m_PillarTarget) {
//生成柱子
CreatePillar(m_PillarIndex, m_PillarTarget + 20.0f);
//索引++且大于最大柱子数
m_PillarIndex = ++m_PillarIndex % m_Pillars.size();
//多了一个柱子所以target+10(每个柱子间隔10)
m_PillarTarget += 10;
}
}
void Level::OnRender()
{
//获取角色位置
const auto& playerPos = m_Player.GetPosition();
//把HSV变换成RGB
glm::vec4 color = HSVtoRGB(m_PillarHSV);
// Background背景
YOTO::Renderer2D::DrawQuad({ playerPos.x, 0.0f, -0.8f }, { 50.0f, 50.0f }, { 0.3f, 0.3f, 0.3f, 1.0f });
//顶部和底部俩横着的
YOTO::Renderer2D::DrawQuad({ playerPos.x, 34.0f }, { 50.0f, 50.0f }, color);
YOTO::Renderer2D::DrawQuad({ playerPos.x, -34.0f }, { 50.0f, 50.0f }, color);
//渲染柱子
for (auto& pillar : m_Pillars)
{ //顶部柱子
YOTO::Renderer2D::DrawRotatedQuad(pillar.TopPosition, pillar.TopScale, glm::radians(180.0f), m_TriangleTexture,1.0f, color);
//底部柱子
YOTO::Renderer2D::DrawRotatedQuad(pillar.BottomPosition, pillar.BottomScale, 0.0f, m_TriangleTexture,1.0f, color);
}
//渲染角色,粒子
m_Player.OnRender();
}
void Level::OnImGuiRender()
{
m_Player.OnImGuiRender();
}
/// <summary>
/// 重置关卡
/// </summary>
void Level::Reset()
{
m_GameOver = false;
//重置角色位置和速度
m_Player.Reset();
//重置柱子目标和索引
m_PillarTarget = 30.0f;
m_PillarIndex = 0;
//重置前五个柱子的位置
for (int i = 0; i < 5; i++)
CreatePillar(i, i * 10.0f);
}
/// <summary>
/// 生成刺的函数
/// </summary>
/// <param name="index">索引</param>
/// <param name="offset">偏移量</param>
void Level::CreatePillar(int index, float offset)
{
//取出索引的柱子
Pillar& pillar = m_Pillars[index];
//设置水平位置
pillar.TopPosition.x = offset;
pillar.BottomPosition.x = offset;
pillar.TopPosition.z = index*0.1-0.5f;
pillar.BottomPosition.z = index * 0.1 - 0.5f+0.05f;
//设置中心
float center = Random::Float() * 35.0f - 17.5f;
//设置缝隙
float gap = 8.0f + Random::Float() * 0.5f;
//设置垂直的位置
pillar.TopPosition.y = 10.0f - ((10.0f - center) * 0.2f) + gap * 0.5f;
pillar.BottomPosition.y = -10.0f - ((-10.0f - center) * 0.2f) - gap * 0.5f;
}
/// <summary>
/// 碰撞检测
/// </summary>
/// <returns></returns>
bool Level::CollisionTest()
{
//如果超过活动范围,直接判定为碰到
if (glm::abs(m_Player.GetPosition().y) > 8.5f)
return true;
//player的四个点的分布
glm::vec4 playerVertices[4] = {
{ -0.5f, -0.5f, 0.0f, 1.0f },
{ 0.5f, -0.5f, 0.0f, 1.0f },
{ 0.5f, 0.5f, 0.0f, 1.0f },
{ -0.5f, 0.5f, 0.0f, 1.0f }
};
//player的位置
const auto& pos = m_Player.GetPosition();
//player的变换矩阵
glm::vec4 playerTransformedVerts[4];
for (int i = 0; i < 4; i++)
{
playerTransformedVerts[i] = glm::translate(glm::mat4(1.0f), { pos.x, pos.y, 0.0f })
* glm::rotate(glm::mat4(1.0f), glm::radians(m_Player.GetRotation()), { 0.0f, 0.0f, 1.0f })
* glm::scale(glm::mat4(1.0f), { 1.0f, 1.3f, 1.0f })
* playerVertices[i];
}
//柱子的点的分布
// To match Triangle.png (each corner is 10% from the texture edge)
glm::vec4 pillarVertices[3] = {
{ -0.5f + 0.1f, -0.5f + 0.1f, 0.0f, 1.0f },
{ 0.5f - 0.1f, -0.5f + 0.1f, 0.0f, 1.0f },
{ 0.0f + 0.0f, 0.5f - 0.1f, 0.0f, 1.0f },
};
//判断每个柱子
for (auto& p : m_Pillars)
{
//每个点的位置
glm::vec2 tri[3];
// Top pillars
for (int i = 0; i < 3; i++)
{
//获取三角形点的位置
tri[i] = glm::translate(glm::mat4(1.0f), { p.TopPosition.x, p.TopPosition.y, 0.0f })
* glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), { 0.0f, 0.0f, 1.0f })
* glm::scale(glm::mat4(1.0f), { p.TopScale.x, p.TopScale.y, 1.0f })
* pillarVertices[i];
}
//判断palyer的位置是否在三角形内
for (auto& vert : playerTransformedVerts)
{
//判断是否在三角形内
if (PointInTri({ vert.x, vert.y }, tri[0], tri[1], tri[2]))
return true;
}
// Bottom pillars下方的三角形
for (int i = 0; i < 3; i++)
{
tri[i] = glm::translate(glm::mat4(1.0f), { p.BottomPosition.x, p.BottomPosition.y, 0.0f })
* glm::scale(glm::mat4(1.0f), { p.BottomScale.x, p.BottomScale.y, 1.0f })
* pillarVertices[i];
}
for (auto& vert : playerTransformedVerts)
{
if (PointInTri({ vert.x, vert.y }, tri[0], tri[1], tri[2]))
return true;
}
}
return false;
}
void Level::GameOver()
{
m_GameOver = true;
}
Player.h:
#pragma once
#include"YOTO.h"
//#include "Color.h"
#include "Random.h"
#include "ParticleSystem.h"
class Player
{
public:
Player();
void LoadAssets();
void OnUpdate(YOTO::Timestep ts);
void OnRender();
void OnImGuiRender();
void Reset();
//根据速度y获取旋转
float GetRotation() { return m_Velocity.y * 4.0f - 90.0f; }
const glm::vec2& GetPosition() const { return m_Position; }
uint32_t GetScore() const { return (uint32_t)(m_Position.x + 10.0f) / 10.0f; }
private:
glm::vec2 m_Position = { -10.0f, 0.0f };
glm::vec2 m_Velocity = { 5.0f, 0.0f };
float m_EnginePower = 0.5f;
float m_Gravity = 0.4f;
float m_Time = 0.0f;
float m_SmokeEmitInterval = 0.4f;
float m_SmokeNextEmitTime = m_SmokeEmitInterval;
ParticleProps m_SmokeParticle, m_EngineParticle;
ParticleSystem m_ParticleSystem;
YOTO::Ref<YOTO::Texture2D> m_ShipTexture;
};
Player.cpp:
#include "Player.h"
#include<YOTO/Renderer/Texture.h>
#include <imgui/imgui.h>
#include <glm/gtc/matrix_transform.hpp>
Player::Player()
{
// Smoke
m_SmokeParticle.Position = { 0.0f, 0.0f };
m_SmokeParticle.Velocity = { -2.0f, 0.0f }, m_SmokeParticle.VelocityVariation = { 4.0f, 2.0f };
m_SmokeParticle.SizeBegin = 0.35f, m_SmokeParticle.SizeEnd = 0.0f, m_SmokeParticle.SizeVariation = 0.15f;
m_SmokeParticle.ColorBegin = { 0.8f, 0.8f, 0.8f, 1.0f };
m_SmokeParticle.ColorEnd = { 0.6f, 0.6f, 0.6f, 1.0f };
m_SmokeParticle.LifeTime = 4.0f;
// Flames
m_EngineParticle.Position = { 0.0f, 0.0f };
m_EngineParticle.Velocity = { -2.0f, 0.0f }, m_EngineParticle.VelocityVariation = { 3.0f, 1.0f };
m_EngineParticle.SizeBegin = 0.5f, m_EngineParticle.SizeEnd = 0.0f, m_EngineParticle.SizeVariation = 0.3f;
m_EngineParticle.ColorBegin = { 254 / 255.0f, 109 / 255.0f, 41 / 255.0f, 1.0f };
m_EngineParticle.ColorEnd = { 254 / 255.0f, 212 / 255.0f, 123 / 255.0f , 1.0f };
m_EngineParticle.LifeTime = 1.0f;
}
void Player::LoadAssets()
{//加载角色的纹理
m_ShipTexture = YOTO::Texture2D::Create("assets/textures/Ship.png");
}
void Player::OnUpdate(YOTO::Timestep ts)
{
m_Time += ts;
//如果按下空格
if (YOTO::Input::IsKeyPressed(YT_KEY_SPACE))
{
//速度的y增加动力
m_Velocity.y += m_EnginePower;
//如果速度小于0(向下),动力*2
if (m_Velocity.y < 0.0f)
m_Velocity.y += m_EnginePower * 2.0f;
// Flames
//排放物的点
glm::vec2 emissionPoint = { 0.0f, -0.6f };
//根据速度y获取旋转角度
float rotation = glm::radians(GetRotation());
//计算旋转后的位置
glm::vec4 rotated = glm::rotate(glm::mat4(1.0f), rotation, { 0.0f, 0.0f, 1.0f }) * glm::vec4(emissionPoint, 0.0f, 1.0f);
//赋值给m_EngineParticle动力粒子系统
m_EngineParticle.Position = m_Position + glm::vec2{ rotated.x, rotated.y };
//赋值给粒子系统速度
m_EngineParticle.Velocity.y = -m_Velocity.y * 0.2f - 0.2f;
//传入粒子系统的例子配置信息
m_ParticleSystem.Emit(m_EngineParticle);
}
else
{
//如果没按,速度就减重力:v=at
m_Velocity.y -= m_Gravity;
}
//速度y限制在-20到20之间
m_Velocity.y = glm::clamp(m_Velocity.y, -20.0f, 20.0f);
//更新位置
m_Position += m_Velocity * (float)ts;
// Particles 粒子,每隔一段时间产生一次白烟
if (m_Time > m_SmokeNextEmitTime)
{
//烟的位置=当前时间
m_SmokeParticle.Position = m_Position;
//配置烟的粒子信息
m_ParticleSystem.Emit(m_SmokeParticle);
//产生间隔
m_SmokeNextEmitTime += m_SmokeEmitInterval;
}
//粒子系统刷新
m_ParticleSystem.OnUpdate(ts);
}
void Player::OnRender()
{
//渲染例子
m_ParticleSystem.OnRender();
//渲染角色
YOTO::Renderer2D::DrawRotatedQuad({ m_Position.x, m_Position.y, 0.5f }, { 1.0f, 1.3f }, glm::radians(GetRotation()), m_ShipTexture);
}
void Player::OnImGuiRender()
{
ImGui::DragFloat("Engine Power", &m_EnginePower, 0.1f);
ImGui::DragFloat("Gravity", &m_Gravity, 0.1f);
}
/// <summary>
/// 重置角色
/// </summary>
void Player::Reset()
{//重置位置和速度
m_Position = { -10.0f, 0.0f };
m_Velocity = { 5.0f, 0.0f };
}
ParticleSystem.h:
#pragma once
#include <YOTO.h>
struct ParticleProps
{
glm::vec2 Position;
glm::vec2 Velocity, VelocityVariation;
glm::vec4 ColorBegin, ColorEnd;
float SizeBegin, SizeEnd, SizeVariation;
float LifeTime = 1.0f;
};
class ParticleSystem
{
public:
ParticleSystem();
void Emit(const ParticleProps& particleProps);
void OnUpdate(YOTO::Timestep ts);
void OnRender();
private:
struct Particle
{
glm::vec2 Position;
glm::vec2 Velocity;
glm::vec4 ColorBegin, ColorEnd;
float Rotation = 0.0f;
float SizeBegin, SizeEnd;
float LifeTime = 1.0f;
float LifeRemaining = 0.0f;
bool Active = false;
};
std::vector<Particle> m_ParticlePool;
uint32_t m_PoolIndex = 999;
};
ParticleSystem.cpp:
#include "ParticleSystem.h"
#include "Random.h"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/compatibility.hpp>
ParticleSystem::ParticleSystem()
{
m_ParticlePool.resize(1000);
}
/// <summary>
///发散粒子
/// </summary>
/// <param name="particleProps"></param>
void ParticleSystem::Emit(const ParticleProps& particleProps)
{
//从粒子池中取出一个
Particle& particle = m_ParticlePool[m_PoolIndex];
//激活
particle.Active = true;
//设置位置
particle.Position = particleProps.Position;
//设置旋转随机数*2*π
particle.Rotation = Random::Float() * 2.0f * glm::pi<float>();
// Velocity 设置速度
particle.Velocity = particleProps.Velocity;
particle.Velocity.x += particleProps.VelocityVariation.x * (Random::Float() - 0.5f);
particle.Velocity.y += particleProps.VelocityVariation.y * (Random::Float() - 0.5f);
// Color 设置颜色
particle.ColorBegin = particleProps.ColorBegin;
particle.ColorEnd = particleProps.ColorEnd;
// Size 设置大小
particle.SizeBegin = particleProps.SizeBegin + particleProps.SizeVariation * (Random::Float() - 0.5f);
particle.SizeEnd = particleProps.SizeEnd;
// Life 设置生命周期
particle.LifeTime = particleProps.LifeTime;
particle.LifeRemaining = particleProps.LifeTime;
//索引减一后取模,保证大于0
m_PoolIndex = --m_PoolIndex % m_ParticlePool.size();
}
void ParticleSystem::OnUpdate(YOTO::Timestep ts)
{
//更新池子的每个元素
for (auto& particle : m_ParticlePool)
{
//如果没激活,直接跳过
if (!particle.Active)
continue;
//如果生命周期到头了,直接设置未激活
if (particle.LifeRemaining <= 0.0f)
{
particle.Active = false;
continue;
}
//每次刷新生命周期减少
particle.LifeRemaining -= ts;
//位置更新
particle.Position += particle.Velocity * (float)ts;
//旋转更新(自动旋转)
particle.Rotation += 0.01f * ts;
}
}
void ParticleSystem::OnRender()
{
//取出粒子
for (auto& particle : m_ParticlePool)
{
//如果没有激活直接不处理
if (!particle.Active)
continue;
//获取life
float life = particle.LifeRemaining / particle.LifeTime;
//根据life过渡Color变换
glm::vec4 color = glm::lerp(particle.ColorEnd, particle.ColorBegin, life);
//根据life过渡透明度
color.a = color.a * life;
//根据life过渡大小
float size = glm::lerp(particle.SizeEnd, particle.SizeBegin, life);
//渲染粒子
YOTO::Renderer2D::DrawRotatedQuad(particle.Position, { size, size }, particle.Rotation, color);
}
}
测试:
cool!