跟着cherno手搓游戏引擎【28】第一个游戏!源码解读!逐行注释!

源码解读:

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;}}// RenderYOTO::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();//根据不同的状态渲染不同的UIswitch (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-50CreatePillar(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变换成RGBglm::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 pillarsfor (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()
{// Smokem_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;// Flamesm_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(向下),动力*2if (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=atm_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;//索引减一后取模,保证大于0m_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;//获取lifefloat 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! 

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

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

相关文章

云原生架构技术揭秘:探索容器技术的奥秘

云原生的概念和演进都是围绕云计算的核心价值展开的&#xff0c;比如弹性、自动化、韧性&#xff0c;所以云原生所涵盖的技术领域非常丰富。 随着云计算技术的不断发展&#xff0c;云原生架构已经成为了新一代软件开发的重要趋势。本文将为您介绍云原生架构的相关技术&#xf…

java BIO深入学习

一、BIO的工作原理 传统Io(BIO)的本质就是面向字节流来进行数据传输的 ①:当两个进程之间进行相互通信&#xff0c;我们需要建立一个用于传输数据的管道(输入流、输出流)&#xff0c;原来我们传输数据面对的直接就是管道里面一个个字节数据的流动&#xff08;我们弄了一个 by…

综合实战(volume and Compose)

"让我&#xff0c;重获新生~" MySQL 灾难恢复 熟练掌握挂载卷的使用&#xff0c;将Mysql的业务数据存储在 外部。 实战思想: 使用 MySQL 5.7 的镜像创建容器并创建一个普通数据卷 "mysql-data"用来保存容器中产生的数据。我们需要容器连接到Mysql服务&a…

java008 - Java方法

1、方法概述 1.1 概念 将独立功能的代码块组织成为一个整体&#xff0c;使其具有特殊功能的代码集。 1.2 注意事项 方法必须先创建才能使用&#xff0c;该过程称为方法的定义方法创建好不能直接运行&#xff0c;需要手动使用才执行&#xff0c;该过程称为方法的调用 2、方…

观察者模式与发布订阅模式

观察者模式 定义&#xff1a; 观察者模式是一种行为型设计模式&#xff0c;定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 结构图&#xff1a; ES6简易代码实现&#xff1a; //ts环境下…

ai作画在线生成!这8个AI生图工具一定要知道。

过去的2023年被称作AI元年&#xff0c;随之而来的2024&#xff0c;被业内人士称之为AI应用元年&#xff0c;即随着大模型和各类AI应用的涌现速度放缓&#xff0c;人们关注的焦点也从产品层面&#xff08;有哪些好用的AI应用&#xff09;&#xff0c;转移到AI如何更好地赋能实际…

【Godot4自学手册】第十七节主人公的攻击和敌人的受伤

本节主要学习主人公是如何向敌人发起进攻的&#xff0c;敌人是如何受伤的&#xff0c;受伤时候动画显示&#xff0c;击退效果等。其原理和上一节内容相同&#xff0c;不过有许多细节需要关注和完善。 一、修改Bug 在本节学习之前&#xff0c;我将要对上一节的代码进行完善&am…

androidframework开发面试,阿里P8成长路线

字节跳动Android面经 一面问的 Java 和 Android 基础 1、Jvm虚拟机 2、messageQueue会不会阻塞ui线程 3、对象锁和类锁 4、之字形打印树 5、还有其他的 《Android学习笔记总结最新移动架构视频大厂安卓面试真题项目实战源码讲义》 **完整开源项目&#xff1a;docs.qq.com/doc…

【leetcode】破解闯关密码 模板字符串

/*** param {number[]} password* return {string}*/ var crackPassword function(password) {return minNumspassword.sort((a,b)>{if(${a}${b}-${b}${a}>0){return 1;}else{return -1;}}).join(""); };巧用模板字符串对数组进行排序

二、TensorFlow结构分析(2)

目录 1、会话 1.1 __init__(target,graphNone,configNone) 1.2 会话的run() 1.3 feed操作 TF数据流图图与TensorBoard会话张量变量OP高级API 1、会话 1.1 __init__(target,graphNone,configNone) def session_demo():# 会话的演示# Tensorflow实现加法运算a_t tf.constan…

Vue+SpringBoot打造音乐偏好度推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1.2 我的喜好模块2.1.3 每日推荐模块2.1.4 通知公告模块 2.2 用例图设计2.3 实体类设计2.4 数据库设计 三、系统展示3.1 登录注册3.2 音乐档案模块3.3 音乐每日推荐模块3.4 通知公告模…

继承-学习2

this关键字&#xff1a;指向调用该方法的对象&#xff0c;一般我们是在当前类中使用this关键字&#xff0c;所以我们常说代表本类对象的引用 super关键字&#xff1a;代表父类存储空间的标识(可看作父类对象的引用) 父类&#xff1a; package ven;public class Fu {//父类成员…