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

源码解读:

GameLayer层级:在构造函数中:创建窗口和相机,对随机数种子初始化;

在OnAttach方法中:初始化关卡:加载资源初始化地图信息;设置字体;

在OnUpdate方法中:判断游戏状态;设置相机跟随;计算/刷新关卡,角色,粒子的变换、碰撞、颜色、生命周期等信息;接着根据刷新的信息,渲染关卡、角色、粒子

在OnImGuiRender方法中:判断状态渲染UI;

在OnEvent方法中:拦截窗口大小改变和鼠标点击事件,改变相机大小\根据状态判断是否重置游戏;

代码:

SandboxApp.cpp:

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:

cpp 复制代码
#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:

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:

cpp 复制代码
#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:

cpp 复制代码
#include "Random.h"

std::mt19937 Random::s_RandomEngine;
std::uniform_int_distribution<std::mt19937::result_type> Random::s_Distribution;

Level.h:

cpp 复制代码
#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:

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:

cpp 复制代码
#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:

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:

cpp 复制代码
#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:

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!

相关推荐
向宇it1 天前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
虾球xz1 天前
游戏引擎学习第55天
学习·游戏引擎
虾球xz1 天前
游戏引擎学习第58天
学习·游戏引擎
ue星空1 天前
虚幻引擎结构之UWorld
游戏引擎·虚幻
ue星空1 天前
虚幻引擎结构之ULevel
游戏引擎·虚幻
向宇it1 天前
【从零开始入门unity游戏开发之——unity篇01】unity6基础入门开篇——游戏引擎是什么、主流的游戏引擎、为什么选择Unity
开发语言·unity·c#·游戏引擎
神洛华1 天前
Y3地图制作1:水果缤纷乐、密室逃脱
编辑器·游戏引擎·游戏程序
向宇it1 天前
【从零开始入门unity游戏开发之——C#篇26】C#面向对象动态多态——接口(Interface)、接口里氏替换原则、密封方法(`sealed` )
java·开发语言·unity·c#·游戏引擎·里氏替换原则
神码编程2 天前
【Unity功能集】TextureShop纹理工坊(五)选区
unity·游戏引擎·shader·ps选区
benben0442 天前
Unity3D仿星露谷物语开发7之事件创建动画
unity·游戏引擎