跟着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.mzph.cn/news/709645.shtml

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

相关文章

从单体服务到微服务:多模式 Web 应用开发记录<三>预初始化属性

相关文章&#xff1a; 多模式 Web 应用开发记录<一>背景&全局变量优化多模式 Web 应用开发记录<二>自己动手写一个 Struts 开头先看一个简单的例子&#xff0c;这是 ftl 文件的一个表单&#xff1a; <form id"validateForm" action"#&quo…

【第十天】C++函数对象/仿函数、谓词、适配器及常见algorithm算法

一、函数对象 重载了函数调用运算符()的类 实例化的对象叫函数对象&#xff0c;也叫仿函数。 如果函数对象 有一个参数 叫&#xff1a;一元函数对象/仿函数如果函数对象 有二个参数 叫&#xff1a;二元函数对象/仿函数如果函数对象 有三个及以上参数 叫&#xff1a;多元函数对…

K8S存储卷与PV,PVC

一、前言 Kubernetes&#xff08;K8s&#xff09;中的存储卷是用于在容器之间共享数据的一种机制。存储卷可以在多个Pod之间共享数据&#xff0c;并且可以保持数据的持久性&#xff0c;即使Pod被重新调度或者删除&#xff0c;数据也不会丢失。 Kubernetes支持多种类型的存储卷…

Three.js-03Vite打包入门

1.安装 说明&#xff1a;创建文件以后&#xff0c;按照提示进行操作。如cd文件夹&#xff0c;npm i ,npm run dev等操作。 npm create vitelatest 2.安装three npm i three.js 4.打开npm官网 说明:搜索three第三方库。按照案例进行操作。 5.修改App.vue文件 <script set…

手撕LRU缓存——LinkedHashMap简易源码

题目链接&#xff1a;https://leetcode.cn/problems/lru-cache/description/?envTypestudy-plan-v2&envIdtop-100-liked 原理非常简单&#xff0c;一个双端链表配上一个hash表。 首先我们要知道什么是LRU就是最小使用淘汰。怎么淘汰&#xff0c;链表尾部就是最不常用的直接…

Google大模型Bard更名Gemini,现在实力如何?(VS gpt系列)

名人说&#xff1a;一花独放不是春&#xff0c;百花齐放花满园。——《增广贤文》 作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、简要介绍1、Gemini是什么&#xff1f;2、主要特点3、Gemini的版本4、应用潜力5、…

zookeeper启动报错

启动zookeeper报错 从报错中可以看到 Invalid config, exiting abnormally 意思是&#xff1a;配置无效&#xff0c;异常退出 在往上看是没有zoo.cof这个配置文件 2024-02-27 14:47:03,285 [myid:] - ERROR [main:o.a.z.s.q.QuorumPeerMain99] - Invalid config, exiting…

基于AMDGPU-ROCm的深度学习环境搭建

在风起云涌的AI江湖&#xff0c;NVIDIA凭借其CUDA生态和优秀的硬件大杀四方&#xff0c;立下赫赫战功&#xff0c;而另一家公司AMD也不甘示弱&#xff0c;带着他的生态解决方案ROCm开始了与不世出的NVIDA的正面硬钢&#xff0c;"ROCm is the answer to CUDA", AMD官网…

Java——数组的定义与使用

目录 一.数组的基本概念 1.什么是数组 2.数组的创建及初始化 3.数组的使用 二.数组是引用类型 1.初始JVM的内存分布 2.基本类型变量与引用类型变量的区别 3.再谈引用变量 4.认识 null 三.数组的应用场景 1.保存数据 2.作为函数的参数 2.1参数传基本数据类型 2.…

学习vue3第二节(使用vite 创建vue3项目)

使用vite 创建vue3项目 node 安装请移步 node官网&#xff1a; https://nodejs.p2hp.com/ node 版本控制 请移步 nvm官网&#xff1a;https://nvm.uihtm.com/ vite 生成vue项目完整版 请移步 vite官网&#xff1a;https://cn.vitejs.dev/ 1、使用 npm 或者 yarn 创建vue3 项目…

NXP实战笔记(十一):32K3xx基于RTD-SDK在S32DS上配置DFLASH、MemAcc、Fee

目录 1、概述 2、RTD-SDK配置之Cache_Ip 3、RTD-SDK配置之Mem_43_InFls 4、RTD-SDK配置之MemAcc 5、RTD-SDK配置之Fee 6、代码示例 1、概述 S32K3目前安装的RTD普遍使用的是R22-11版本的AUTOSAR规范&#xff0c;作为一直使用AUTOSAR4.2.2的程序员来讲&#xff0c;属实迭代…

东方博宜 1511. 数字之和为13的整数

东方博宜 1511. 数字之和为13的整数 #include<iostream> using namespace std; int main() {int n ;cin >> n ;int cnt 0 ;for(int i 1 ; i < n ; i){int sum 0 ;int y ;y i ;while(true){sum y%10;y y / 10 ;if(y0)break; }if(sum 13)cnt 1 ; }cout…

91、在推理流程中加载动态库中的Infer函数

注:建议在 new_version_with_notes 目录下尝试本节内容,有更为丰富的细节输出。 在上一节将生成的代码编译成一个动态链接库之后,接下来需要加载动态链接库中写好的推理入口函数,完成推理。 在生成的代码中,在 codegen 目录下,有一个 codegen.cc 文件,里面是一个 Infe…

二、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…

Appium + mitmProxy 实现APP接口稳定性测试

随着 App 用户量的不断增长&#xff0c;任何小的问题都可能放大成严重的线上事故&#xff0c;为了避免对App造成损害的任何可能性&#xff0c;我们必须从各个方面去思考 App 的稳定性建设&#xff0c;尽可能减少任何潜在的威胁。 1.背景介绍 为了保障 App 的稳定性&#xff0…

算法基础(三)(模拟)

1.模拟算法介绍&#xff1a; 模拟算法通过模拟实际情况来解决问题&#xff0c;一般容易理解但是实现起来比较复杂&#xff0c;有很多需要注意的细节&#xff0c;或者是一些所谓很“麻烦”的东西。模拟题一般不涉及太难的算法&#xff0c;一般就是由较多的简单但是不好处理的部…

redis启动错误

错误&#xff1a; Creating Server TCP listening socket 127.0.0.1:6379: bind: No error redis-server.exe redis.windows.conf redis-cli.exe shutdown auth "yourpassword"

9 easy 28. 找出字符串中第一个匹配项的下标

暴力法&#xff1a; //给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。 //如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 // // // // 示例 1&…

harbor配置目录被误删后救援

我司微服务产品的业务镜像&#xff0c;在客户现场上云安全检测中发现需要打补丁&#xff1b;放在角落、时不常用一次的harbor镜像仓库需要用到了&#xff1a;将加固后的基础镜像上传以备份&#xff0c;方便其他同学拉取使用。 然鹅&#xff0c;启动后harbor-db频繁重启 harbo…

最新 CLion 2023.3.4 下载与安装 + 永久免费

文章目录 Stage 1 : 官网下载Stage 2 : 下载工具Stage 3-1 : windows为例Stage 3-2 : mac为例常见问题部分小伙伴 Mac 系统执行脚本遇到如下错误&#xff1a;解决方法&#xff1a; 执行脚本做了啥&#xff1f;和正版区别&#xff1f; Stage 1 : 官网下载 先去官网下载 我这里下…