跟着cherno手搓游戏引擎【14】封装opengl

本节先把代码粘上,后续会慢慢把注释都给加上,先看代码了解个大概(待更新)

前置:

RendererAPI.h:

cpp 复制代码
#pragma once
namespace YOTO {
	enum class RendererAPI {
		None = 0,
		OpenGL=1
	};
	class Renderer {
	public:
		inline static RendererAPI GetAPI() {
			return s_RendererAPI;
		}
		static RendererAPI s_RendererAPI;
	};

}

RendererAPI.cpp:

cpp 复制代码
#include"ytpch.h"
#include"Renderer.h"
namespace YOTO {
	RendererAPI Renderer::s_RendererAPI = RendererAPI::OpenGL;
}

抽象:

buffer.h:

cpp 复制代码
#pragma once
#include <YOTO/Log.h>
namespace YOTO {
	enum class ShaderDataType{
	None=0,
	Float,Float2,Float3,Float4,
	Mat3,Mat4,
	Int,Int2,Int3,Int4,
	Bool,
	};
	static uint32_t  ShaderDataTypeSize(ShaderDataType type) {
		switch (type)
		{
		case YOTO::ShaderDataType::Float:
			return 4;
			break;
		case YOTO::ShaderDataType::Float2:
			return 4*2;
			break;
		case YOTO::ShaderDataType::Float3:
			return 4*3;
			break;
		case YOTO::ShaderDataType::Float4:
			return 4*4;
			break;
		case YOTO::ShaderDataType::Mat3:
			return 4*3*3;
			break;
		case YOTO::ShaderDataType::Mat4:
			return 4*4*4;
			break;
		case YOTO::ShaderDataType::Int:
			return 4;
			break;
		case YOTO::ShaderDataType::Int2:
			return 4*2;
			break;
		case YOTO::ShaderDataType::Int3:
			return 4*3;
			break;
		case YOTO::ShaderDataType::Int4:
			return 4*4;
			break;
		case YOTO::ShaderDataType::Bool:
			return 1;
			break;
		}
		YT_CORE_ASSERT(false, "未知的ShaderDataType!");
		return 0;
	}
	struct BufferElement {
		std::string Name;
		ShaderDataType Type;
		uint32_t Size;
		uint32_t Offset;
		bool Normalized;
		BufferElement(){}
		BufferElement(ShaderDataType type, const std::string& name,bool normalized=false)
			:Name(name), Type(type), Size(ShaderDataTypeSize(type)), Offset(0), Normalized(normalized){}
		uint32_t GetComponentCount() const{
			switch (Type)
			{
			case YOTO::ShaderDataType::Float:
				return 1;
				break;
			case YOTO::ShaderDataType::Float2:
				return 2;
				break;
			case YOTO::ShaderDataType::Float3:
				return 3;
				break;
			case YOTO::ShaderDataType::Float4:
				return 4;
				break;
			case YOTO::ShaderDataType::Mat3:
				return 3*3;
				break;
			case YOTO::ShaderDataType::Mat4:
				return 4*4;
				break;
			case YOTO::ShaderDataType::Int:
				return 1;
				break;
			case YOTO::ShaderDataType::Int2:
				return 2;
				break;
			case YOTO::ShaderDataType::Int3:
				return 3;
				break;
			case YOTO::ShaderDataType::Int4:
				return 4;
				break;
			case YOTO::ShaderDataType::Bool:
				return 1;
				break;
			default:
				break;
			}
			YT_CORE_ASSERT(false, "未知的ShaderDataType!");
			return 0;
		}
	};
	class BufferLayout {
	public:
		BufferLayout(){}
		BufferLayout(const std::initializer_list<BufferElement>elements)
			:m_Elements(elements) 
		{
			CalculateOffsetAndStride();
		} 
		inline uint32_t GetStride()const { return m_Stride; }
		inline const std::vector<BufferElement>& GetElements()const {
			return m_Elements;
		}
		std::vector<BufferElement>::iterator begin() { return m_Elements.begin(); }
		std::vector<BufferElement>::iterator end() { return m_Elements.end(); }
		std::vector<BufferElement>::const_iterator begin() const { return m_Elements.begin(); }
		std::vector<BufferElement>::const_iterator end() const { return m_Elements.end(); }
	private:
		void CalculateOffsetAndStride() {
			uint32_t offset = 0;
			m_Stride = 0;
			for (auto& element : m_Elements) {
				element.Offset = offset;
				offset += element.Size;
				m_Stride += element.Size;
			}
		}
	private:
		std::vector<BufferElement> m_Elements;
		uint32_t m_Stride = 0;
	};
	class VertexBuffer {
	public:
		virtual~VertexBuffer() {}

		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
		virtual void SetLayout(const BufferLayout& layout) = 0;
		virtual const BufferLayout& GetLayout()const = 0;
		static  VertexBuffer* Create(float* vertices, uint32_t size);
	};
	class IndexBuffer {
	public:
		virtual~IndexBuffer(){}
		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
		virtual uint32_t GetCount() const = 0;
		static  IndexBuffer* Create(uint32_t* indices, uint32_t size);
	};
}

buffer.cpp:

cpp 复制代码
#include"ytpch.h"
#include"Buffer.h"
#include "Renderer.h"

#include "Platform/OpenGL/OpenGLBuffer.h"

namespace YOTO {
	VertexBuffer* VertexBuffer::Create(float* vertices, uint32_t size)
	{
		switch (Renderer::GetAPI())
		{
		case RendererAPI::None:
			YT_CORE_ASSERT(false,"Buffer:API为None不支持");
			return nullptr;
		case RendererAPI::OpenGL:
			return new OpenGLVertexBuffer(vertices,size);
		}
		YT_CORE_ASSERT(false,"Buffer:未知API");
		return nullptr;
	}
	IndexBuffer* IndexBuffer::Create(uint32_t* indices, uint32_t size)
	{
		switch (Renderer::GetAPI())
		{
		case RendererAPI::None:
			YT_CORE_ASSERT(false, "Buffer:API为None不支持");
			return nullptr;
		case RendererAPI::OpenGL:
			return new OpenGLIndexBuffer(indices, size);
		}
		YT_CORE_ASSERT(false, "Buffer:未知API");
		return nullptr;
	}
}

VertexArray.h:

cpp 复制代码
#pragma once
#include"YOTO/Renderer/Buffer.h"
namespace YOTO {
	
	class VertexArray {
	public:
		virtual~VertexArray() {}

		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
		virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>&vertexBuffer) = 0;
		virtual void AddIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer) = 0;
		virtual const  std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers()const = 0;
		virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer()const = 0;
		static  VertexArray* Create();
	};
}

VertexArray.cpp:

cpp 复制代码
#include "ytpch.h"
#include "VertexArray.h"
#include"Renderer.h"
#include "Platform/OpenGL/OpenGLVertexArray.h"
namespace YOTO {
	VertexArray* VertexArray::Create()
	{
		switch (Renderer::GetAPI())
		{
		case RendererAPI::None:
			YT_CORE_ASSERT(false, "Buffer:API为None不支持");
			return nullptr;
		case RendererAPI::OpenGL:
			return new OpenGLVertexArray();
		}
		YT_CORE_ASSERT(false, "Buffer:未知API");
		return nullptr;
	}
}

实现:

OpenGLBuffer.h:

cpp 复制代码
#pragma once
#include "YOTO/Renderer/Buffer.h"
namespace YOTO {
	class OpenGLVertexBuffer : public VertexBuffer {
	public:
		OpenGLVertexBuffer(float* vertices, uint32_t size);
		virtual~OpenGLVertexBuffer();
		virtual void Bind()const override;
		virtual void UnBind()const override;

		virtual void SetLayout(const BufferLayout& layout)  override {
			m_Layout = layout;
		}
		virtual const BufferLayout& GetLayout()const override {
			return m_Layout;
		}
	private: 
		uint32_t m_RendererID;
		BufferLayout m_Layout;
	};

	class OpenGLIndexBuffer : public IndexBuffer {
	public:
		OpenGLIndexBuffer(uint32_t* indices, uint32_t count);
		virtual~OpenGLIndexBuffer();
		virtual void Bind()const;
		virtual void UnBind()const;
		virtual uint32_t GetCount() const {
			return m_Count;
		};
	private:
		uint32_t m_RendererID;
		uint32_t m_Count;
	};
}

OpenGLBuffer.cpp:

cpp 复制代码
#include"ytpch.h"
#include"OpenGLBuffer.h"
#include <glad/glad.h>
namespace YOTO {

// VertexBuffer 

	OpenGLVertexBuffer::OpenGLVertexBuffer(float* vertices, uint32_t size)
	{	 
		
		glCreateBuffers(1, &m_RendererID);
		glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
		glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
	}
	OpenGLVertexBuffer::~OpenGLVertexBuffer()
	{ 
		glDeleteBuffers(1, &m_RendererID);
	}
	void OpenGLVertexBuffer::Bind() const
	{
		glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
	}
	void OpenGLVertexBuffer::UnBind() const
	{
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	
	// IndexBuffer /
	
	OpenGLIndexBuffer::OpenGLIndexBuffer(uint32_t* indices, uint32_t count)
		:m_Count(count)
	{
		glCreateBuffers(1, &m_RendererID);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, count*sizeof(uint32_t), indices, GL_STATIC_DRAW);
	}
	OpenGLIndexBuffer::~OpenGLIndexBuffer()
	{
		glDeleteBuffers(1, &m_RendererID);
	}
	void OpenGLIndexBuffer::Bind() const
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
	}
	void OpenGLIndexBuffer::UnBind() const
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}
}

OpenGLVertexArray.h:

cpp 复制代码
#pragma once
#include"YOTO/Renderer/VertexArray.h"
namespace YOTO {
	class OpenGLVertexArray:public VertexArray
	{
	public :
		OpenGLVertexArray();
		virtual ~OpenGLVertexArray();
		virtual void Bind() const override;
		virtual void UnBind() const override;

		virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer)override;
		virtual void AddIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer)override;

		virtual const std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers()const {
			return m_VertexBuffers;
		}
		virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer()const {
			return m_IndexBuffers;
		}
	private:
		uint32_t m_RendererID;
		std::vector<std::shared_ptr<VertexBuffer>> m_VertexBuffers;
		std::shared_ptr<IndexBuffer> m_IndexBuffers;
	};

}

OpenGLVertexArray.cpp:

cpp 复制代码
#include "ytpch.h"
#include "OpenGLVertexArray.h"
#include <glad/glad.h>
namespace YOTO {
	static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type) {
		switch (type)
		{
		case YOTO::ShaderDataType::Float:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Float2:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Float3:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Float4:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Mat3:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Mat4:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Int:
			return GL_INT;
		case YOTO::ShaderDataType::Int2:
			return GL_INT;
		case YOTO::ShaderDataType::Int3:
			return GL_INT;
		case YOTO::ShaderDataType::Int4:
			return GL_INT;
		case YOTO::ShaderDataType::Bool:
			return GL_BOOL;
		}
		return 0;
	}
	OpenGLVertexArray::OpenGLVertexArray()
	{
		glCreateVertexArrays(1, &m_RendererID);
	}
	OpenGLVertexArray::~OpenGLVertexArray()
	{
		glDeleteVertexArrays(1, &m_RendererID);
	}
	void OpenGLVertexArray::Bind() const
	{
		glBindVertexArray(m_RendererID);
	}
	void OpenGLVertexArray::UnBind() const
	{
		glBindVertexArray(0);
	}
	void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer)
	{
		YT_CORE_ASSERT(vertexBuffer->GetLayout().GetElements().size(), "OpenGLVertexArray:VertexBuffer没有布局(Layout)")
		glBindVertexArray(m_RendererID);
		vertexBuffer->Bind();
		
		uint32_t index = 0;
		const auto& layout = vertexBuffer->GetLayout();
		for (const auto& element : layout) {
			glEnableVertexAttribArray(index);
			//设置缓冲区数据格式:缓冲区序号、顶点属性的大小、什么数据类型、会不会被归一化、
			glVertexAttribPointer(index,
				element.GetComponentCount(),
				ShaderDataTypeToOpenGLBaseType(element.Type),
				element.Normalized ? GL_TRUE : GL_FALSE,
				layout.GetStride(),
				(const void*)element.Offset);
			index++;
		}
		m_VertexBuffers.push_back(vertexBuffer);
	}
	void OpenGLVertexArray::AddIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer)
	{
		glBindVertexArray(m_RendererID);
		indexBuffer->Bind();
		m_IndexBuffers = indexBuffer;
	}
}

调用:

Application.h:

cpp 复制代码
#pragma once
#include"Core.h"
#include"Event/Event.h"
#include"Event/ApplicationEvent.h"
#include "YOTO/Window.h"
#include"YOTO/LayerStack.h"
#include"YOTO/ImGui/ImGuiLayer.h"

#include <YOTO/Renderer/Shader.h>
#include <YOTO/Renderer/Buffer.h>
#include <YOTO/Renderer/VertexArray.h>
namespace YOTO {
	class YOTO_API Application
	{
	public:
		Application();
		virtual ~Application();
		void Run();
		void OnEvent(Event &e);
		void PushLayer(Layer* layer);
		void PushOverlay(Layer* layer);

		inline static Application& Get() {return * s_Instance;}
		inline Window& GetWindow() { return *m_Window; }
	private:
		bool  OnWindowClosed(WindowCloseEvent& e);
		std::unique_ptr<Window>  m_Window;
		ImGuiLayer *  m_ImGuiLayer;
		bool m_Running = true;
		LayerStack m_LayerStack;
		
		//unsigned int m_VertexArray;
		std::shared_ptr<Shader> m_Shader;
		std::shared_ptr<VertexArray> m_VertexArray;


		std::shared_ptr<Shader> m_BlueShader;
		std::shared_ptr<VertexArray> m_SquareVA;



		static Application* s_Instance;
	};
	//在客户端定义
	Application* CreateApplication();
}

Application.cpp:

cpp 复制代码
#include"ytpch.h"
#include "Application.h"

#include"Log.h"
#include<glad/glad.h>
#include"Input.h"


namespace YOTO {
#define BIND_EVENT_FN(x) std::bind(&x, this, std::placeholders::_1)

	 Application* Application::s_Instance = nullptr;

	Application::Application() {

		YT_CORE_ASSERT(!s_Instance, "Application需要为空!")
		s_Instance = this;
		//智能指针
		m_Window = std::unique_ptr<Window>(Window::Creat());
		//设置回调函数
		m_Window->SetEventCallback(BIND_EVENT_FN(Application::OnEvent));
		//new一个Layer,放在最后层进行渲染
		m_ImGuiLayer = new ImGuiLayer();
		PushOverlay(m_ImGuiLayer);  
		//unsigned int id;
		//glGenBuffers(1, &id);

		uint32_t indices[3] = { 0,1,2 };
		float vertices[3 * 7] = {
			-0.5f,-0.5f,0.0f, 0.8f,0.2f,0.8f,1.0f,
			0.5f,-0.5f,0.0f,  0.2f,0.3f,0.8f,1.0f,
			0.0f,0.5f,0.0f,   0.8f,0.8f,0.2f,1.0f,
		};
		m_VertexArray.reset(VertexArray::Create());


		//顶点数组:
		//glGenVertexArrays(1, &m_VertexArray);
		//glBindVertexArray(m_VertexArray);
		//顶点缓冲区
		//glGenBuffers(1, &m_VertexBuffer);
		//glBindBuffer(GL_ARRAY_BUFFER,m_VertexBuffer);
		//把数据传送给gpu,GL_STATIC_DRAW不断的用新数据刷新数组。告诉opengl这个缓冲区的数据布局
		//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
		std::shared_ptr<VertexBuffer> m_VertexBuffer;
		m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));

	

		//启用数据的索引0
		//glEnableVertexAttribArray(0);
		//设置缓冲区数据格式:缓冲区序号、顶点属性的大小、什么数据类型、会不会被归一化、
		//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),nullptr);
		{
			BufferLayout setlayout = {

	{ShaderDataType::Float3,"a_Position"},
		{ShaderDataType::Float4,"a_Color"}
			};
			m_VertexBuffer->SetLayout(setlayout);

		}

		//uint32_t index = 0;
		//const auto& layout = m_VertexBuffer->GetLayout();
		//for (const auto& element : layout) {
		//	glEnableVertexAttribArray(index);
		//	//设置缓冲区数据格式:缓冲区序号、顶点属性的大小、什么数据类型、会不会被归一化、
		//	glVertexAttribPointer(index,
		//		element.GetComponentCount(), 
		//		ShaderDataTypeToOpenGLBaseType(element.Type), 
		//		element.Normalized?GL_TRUE:GL_FALSE,
		//		layout.GetStride() ,
		//		(const void *)element.Offset);
		//	index++;
		//}
		 
		m_VertexArray->AddVertexBuffer(m_VertexBuffer);
		
		 
		//创建索引缓冲区
		//glGenBuffers(1, &m_IndexBuffer);
		//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer);
		//设置缓冲区格式
		//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
		std::shared_ptr<IndexBuffer>m_IndexBuffer;
		m_IndexBuffer.reset(IndexBuffer::Create(indices, sizeof(indices)/sizeof(uint32_t)));

		m_VertexArray->AddIndexBuffer(m_IndexBuffer);
		//着色器
		//顶点布局
		std::string vertexSource = R"(
		#version 330 core
		layout(location = 0) in vec3 a_Position;
		layout(location = 1) in vec4 a_Color;
		out vec3 v_Position;
		out vec4 v_Color;
		void main(){
		v_Position=a_Position;
		v_Color=a_Color;
		gl_Position =vec4( a_Position+0.5,1.0);
		}
		)";
		//绘制颜色
		std::string fragmentSource = R"(
		#version 330 core
		layout(location = 0) out vec4 color;
		in vec3 v_Position;
		in vec4 v_Color;
		void main(){
		color=vec4(v_Color);
		}
		)";
		m_Shader.reset(new Shader(vertexSource, fragmentSource));

		///测试/

		m_SquareVA.reset(VertexArray::Create());

		float squareVertices[3 * 4] = {
			-0.5f,-0.5f,0.0f,
			0.5f,-0.5f,0.0f, 
			0.5f,0.5f,0.0f,
			-0.5f,0.5f,0.0f
		};
		std::shared_ptr<VertexBuffer> squareVB;
		squareVB.reset(VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
		squareVB->SetLayout({
			{ShaderDataType::Float3,"a_Position"}
		});
		m_SquareVA->AddVertexBuffer(squareVB);
		uint32_t squareIndices[6] = { 0,1,2,2,3,0 };
		std::shared_ptr<IndexBuffer> squareIB; 

		squareIB.reset((IndexBuffer::Create(squareIndices, sizeof(squareIndices) / sizeof(uint32_t))));

		m_SquareVA->AddIndexBuffer(squareIB);
		

		//测试:
		std::string BlueShaderVertexSource = R"(
		#version 330 core
		layout(location = 0) in vec3 a_Position;
		out vec3 v_Position;
		void main(){
		v_Position=a_Position;
		gl_Position =vec4( a_Position,1.0);
		}
		)";
		//绘制颜色
		std::string BlueShaderFragmentSource = R"(
		#version 330 core
		layout(location = 0) out vec4 color;
		in vec3 v_Position;
		void main(){
		color=vec4(0.2,0.3,0.8,1.0);
		}
		)";
		m_BlueShader.reset(new Shader(BlueShaderVertexSource, BlueShaderFragmentSource));
		
		//shader
	}
	Application::~Application() {

	}
	/// <summary>
	/// 所有的Window事件都会在这触发,作为参数e
	/// </summary>
	/// <param name="e"></param>
	void Application::OnEvent(Event& e) {
		//根据事件类型绑定对应事件
		EventDispatcher dispatcher(e);
		dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(Application::OnWindowClosed));
		//输出事件信息
		YT_CORE_INFO("Application:{0}",e);
		for (auto it = m_LayerStack.end(); it != m_LayerStack.begin();) {
			(*--it)->OnEvent(e);
			if (e.m_Handled)
				break;
		}
	}

	bool Application::OnWindowClosed(WindowCloseEvent& e) {
		m_Running = false;
		return true;
	}
	void Application::Run() {
		WindowResizeEvent e(1280, 720);
		if (e.IsInCategory(EventCategoryApplication)) {
			YT_CORE_TRACE(e);
		}
		if (e.IsInCategory(EventCategoryInput)) {
			YT_CORE_ERROR(e);
		}

		while (m_Running)
		{
			glClearColor(0.2f, 0.2f, 0.2f,1);
			glClear(GL_COLOR_BUFFER_BIT);
			
			m_BlueShader->Bind();
			m_SquareVA->Bind();
			glDrawElements(GL_TRIANGLES, m_SquareVA->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
			//glBindVertexArray(m_VertexArray);
			m_Shader->Bind();
			m_VertexArray->Bind();
			glDrawElements(GL_TRIANGLES,m_VertexArray->GetIndexBuffer()->GetCount(),GL_UNSIGNED_INT,nullptr); 

			for (Layer* layer : m_LayerStack) {
				layer->OnUpdate();
			}
			//将ImGui的刷新放到APP中,与Update分开
			m_ImGuiLayer->Begin();
			
			for (Layer* layer : m_LayerStack) {
				layer->OnImGuiRender();
			}
			m_ImGuiLayer->End();
			m_Window->OnUpdate();
		}
	}
	void Application::PushLayer(Layer* layer) {
		m_LayerStack.PushLayer(layer);
		layer->OnAttach();
	}
	void Application::PushOverlay(Layer* layer) {
		m_LayerStack.PushOverlay(layer);
		layer->OnAttach();
	}
}

测试:

shader就不改了,随便一测试画出来个矩形就对了

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