Notifications
Clear all
Topic starter 30/08/2025 1:07 am
# C++ 3D Video Game Engine I'll create a comprehensive folder structure and source code for a C++ 3D game engine with key components. ## Folder Structure ``` GameEngine/ ├── src/ │ ├── core/ │ │ ├── engine.cpp │ │ ├── engine.h │ │ ├── window.cpp │ │ ├── window.h │ │ └── input.cpp │ │ └── input.h │ ├── graphics/ │ │ ├── renderer.cpp │ │ ├── renderer.h │ │ ├── shader.cpp │ │ ├── shader.h │ │ ├── texture.cpp │ │ ├── texture.h │ │ ├── mesh.cpp │ │ ├── mesh.h │ │ └── camera.cpp │ │ └── camera.h │ ├── scene/ │ │ ├── scene.cpp │ │ ├── scene.h │ │ ├── gameobject.cpp │ │ ├── gameobject.h │ │ └── transform.cpp │ │ └── transform.h │ ├── components/ │ │ ├── meshrenderer.cpp │ │ ├── meshrenderer.h │ │ ├── camera.cpp │ │ ├── camera.h │ │ └── script.cpp │ │ └── script.h │ └── main.cpp ├── assets/ │ ├── shaders/ │ │ ├── vertex.glsl │ │ └── fragment.glsl │ ├── models/ │ └── textures/ └── CMakeLists.txt ``` ## Source Code Implementation ### src/core/engine.h ```cpp #ifndef ENGINE_H #define ENGINE_H #include <memory> #include "window.h" #include "input.h" #include "../graphics/renderer.h" #include "../scene/scene.h" class Engine { private: std::unique_ptr<Window> window; std::unique_ptr<Renderer> renderer; std::unique_ptr<Input> input; std::unique_ptr<Scene> scene; bool isRunning; public: Engine(); ~Engine(); void Initialize(); void Run(); void Update(float deltaTime); void Render(); void Shutdown(); Window* GetWindow() { return window.get(); } Input* GetInput() { return input.get(); } Scene* GetScene() { return scene.get(); } }; #endif // ENGINE_H ``` ### src/core/engine.cpp ```cpp #include "engine.h" #include <iostream> Engine::Engine() : isRunning(false) { window = std::make_unique<Window>(1024, 768, "3D Game Engine"); renderer = std::make_unique<Renderer>(); input = std::make_unique<Input>(); scene = std::make_unique<Scene>(); } Engine::~Engine() { Shutdown(); } void Engine::Initialize() { if (!window->Initialize()) { std::cerr << "Failed to initialize window!" << std::endl; return; } if (!renderer->Initialize()) { std::cerr << "Failed to initialize renderer!" << std::endl; return; } isRunning = true; std::cout << "Engine initialized successfully!" << std::endl; } void Engine::Run() { float deltaTime = 0.0f; float lastFrame = 0.0f; while (isRunning && !window->ShouldClose()) { float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; input->Update(); Update(deltaTime); Render(); window->SwapBuffers(); glfwPollEvents(); } } void Engine::Update(float deltaTime) { scene->Update(deltaTime); } void Engine::Render() { renderer->Clear(); scene->Render(renderer.get()); } void Engine::Shutdown() { isRunning = false; std::cout << "Engine shutdown complete!" << std::endl; } ``` ### src/core/window.h ```cpp #ifndef WINDOW_H #define WINDOW_H #include <GL/glew.h> #include <GLFW/glfw3.h> #include <string> class Window { private: GLFWwindow* window; int width, height; std::string title; public: Window(int width, int height, const std::string& title); ~Window(); bool Initialize(); void SwapBuffers(); void PollEvents(); bool ShouldClose() const; int GetWidth() const { return width; } int GetHeight() const { return height; } GLFWwindow* GetGLFWWindow() const { return window; } }; #endif // WINDOW_H ``` ### src/core/window.cpp ```cpp #include "window.h" #include <iostream> Window::Window(int width, int height, const std::string& title) : width(width), height(height), title(title) { window = nullptr; } Window::~Window() { if (window) { glfwDestroyWindow(window); } glfwTerminate(); } bool Window::Initialize() { if (!glfwInit()) { std::cerr << "Failed to initialize GLFW!" << std::endl; return false; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); if (!window) { std::cerr << "Failed to create GLFW window!" << std::endl; return false; } glfwMakeContextCurrent(window); // Initialize GLEW if (glewInit() != GLEW_OK) { std::cerr << "Failed to initialize GLEW!" << std::endl; return false; } // Set up viewport glViewport(0, 0, width, height); // Enable depth testing glEnable(GL_DEPTH_TEST); return true; } void Window::SwapBuffers() { glfwSwapBuffers(window); } void Window::PollEvents() { glfwPollEvents(); } bool Window::ShouldClose() const { return glfwWindowShouldClose(window); } ``` ### src/core/input.h ```cpp #ifndef INPUT_H #define INPUT_H #include <GLFW/glfw3.h> #include <unordered_map> class Input { private: std::unordered_map<int, bool> keyStates; std::unordered_map<int, bool> previousKeyStates; public: void Update(); bool IsKeyPressed(int key); bool IsKeyReleased(int key); bool IsKeyDown(int key); static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); }; #endif // INPUT_H ``` ### src/core/input.cpp ```cpp #include "input.h" #include <iostream> void Input::Update() { previousKeyStates = keyStates; } bool Input::IsKeyPressed(int key) { return keyStates[key] && !previousKeyStates[key]; } bool Input::IsKeyReleased(int key) { return !keyStates[key] && previousKeyStates[key]; } bool Input::IsKeyDown(int key) { return keyStates[key]; } void Input::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { static Input* input = nullptr; if (input == nullptr) { // This is a bit hacky but works for now // In a real engine, you'd have a global input manager return; } if (action == GLFW_PRESS) { input->keyStates[key] = true; } else if (action == GLFW_RELEASE) { input->keyStates[key] = false; } } ``` ### src/graphics/renderer.h ```cpp #ifndef RENDERER_H #define RENDERER_H #include <GL/glew.h> #include <vector> #include "../scene/scene.h" #include "shader.h" class Renderer { private: Shader* defaultShader; public: Renderer(); ~Renderer(); bool Initialize(); void Clear(); void SetClearColor(float r, float g, float b, float a); void Render(const Scene& scene); void DrawMesh(const Mesh& mesh, const Shader& shader); }; #endif // RENDERER_H ``` ### src/graphics/renderer.cpp ```cpp #include "renderer.h" #include <iostream> Renderer::Renderer() { defaultShader = nullptr; } Renderer::~Renderer() { if (defaultShader) { delete defaultShader; } } bool Renderer::Initialize() { // Initialize shaders defaultShader = new Shader("assets/shaders/vertex.glsl", "assets/shaders/fragment.glsl"); if (!defaultShader->IsCompiled()) { std::cerr << "Failed to compile default shader!" << std::endl; return false; } return true; } void Renderer::Clear() { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void Renderer::SetClearColor(float r, float g, float b, float a) { glClearColor(r, g, b, a); } void Renderer::Render(const Scene& scene) { // For simplicity, we'll just draw all objects with default shader // In a real engine, this would involve culling, sorting, etc. for (const auto& gameObject : scene.GetGameObjects()) { if (gameObject->GetComponent<MeshRenderer>()) { const auto* meshRenderer = gameObject->GetComponent<MeshRenderer>(); const auto* mesh = meshRenderer->GetMesh(); if (mesh) { // Set up model matrix defaultShader->Use(); defaultShader->SetMat4("model", gameObject->GetTransform()->GetModelMatrix()); DrawMesh(*mesh, *defaultShader); } } } } void Renderer::DrawMesh(const Mesh& mesh, const Shader& shader) { // Bind VAO and draw glBindVertexArray(mesh.GetVAO()); glDrawElements(GL_TRIANGLES, mesh.GetIndexCount(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); } ``` ### src/graphics/shader.h ```cpp #ifndef SHADER_H #define SHADER_H #include <string> #include <GL/glew.h> #include <glm/glm.hpp> class Shader { private: GLuint ID; bool compiled; std::string vertexPath; std::string fragmentPath; public: Shader(const std::string& vertexPath, const std::string& fragmentPath); ~Shader(); bool IsCompiled() const { return compiled; } void Use(); void SetMat4(const std::string& name, const glm::mat4& mat) const; void SetVec3(const std::string& name, const glm::vec3& vec) const; private: void CompileShader(); }; #endif // SHADER_H ``` ### src/graphics/shader.cpp ```cpp #include "shader.h" #include <iostream> #include <fstream> #include <sstream> Shader::Shader(const std::string& vertexPath, const std::string& fragmentPath) : vertexPath(vertexPath), fragmentPath(fragmentPath), compiled(false) { CompileShader(); } Shader::~Shader() { if (ID != 0) { glDeleteProgram(ID); } } void Shader::CompileShader() { // Read shader files std::string vertexCode; std::string fragmentCode; try { std::ifstream vertexFile(vertexPath); std::ifstream fragmentFile(fragmentPath); if (!vertexFile.is_open()) { throw std::runtime_error("Failed to open vertex shader file"); } if (!fragmentFile.is_open()) { throw std::runtime_error("Failed to open fragment shader file"); } std::stringstream vShaderStream, fShaderStream; vShaderStream << vertexFile.rdbuf(); fShaderStream << fragmentFile.rdbuf(); vertexFile.close(); fragmentFile.close(); vertexCode = vShaderStream.str(); fragmentCode = fShaderStream.str(); } catch (const std::exception& e) { std::cerr << "Error reading shader files: " << e.what() << std::endl; return; } const char* vShaderCode = vertexCode.c_str(); const char* fShaderCode = fragmentCode.c_str(); // Compile shaders GLuint vertex, fragment; GLint success; GLchar infoLog[512]; // Vertex Shader vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &vShaderCode, NULL); glCompileShader(vertex); glGetShaderiv(vertex, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertex, 512, NULL, infoLog); std::cerr << "Vertex shader compilation failed:\n" << infoLog << std::endl; return; } // Fragment Shader fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &fShaderCode, NULL); glCompileShader(fragment); glGetShaderiv(fragment, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragment, 512, NULL, infoLog); std::cerr << "Fragment shader compilation failed:\n" << infoLog << std::endl; return; } // Shader Program ID = glCreateProgram(); glAttachShader(ID, vertex); glAttachShader(ID, fragment); glLinkProgram(ID); glGetProgramiv(ID, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(ID, 512, NULL, infoLog); std::cerr << "Shader program linking failed:\n" << infoLog << std::endl; return; } // Clean up glDeleteShader(vertex); glDeleteShader(fragment); compiled = true; } void Shader::Use() { if (compiled) { glUseProgram(ID); } } void Shader::SetMat4(const std::string& name, const glm::mat4& mat) const { if (!compiled) return; glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); } void Shader::SetVec3(const std::string& name, const glm::vec3& vec) const { if (!compiled) return; glUniform3f(glGetUniformLocation(ID, name.c_str()), vec.x, vec.y, vec.z); } ``` ### src/graphics/mesh.h ```cpp #ifndef MESH_H #define MESH_H #include <vector> #include <GL/glew.h> #include <glm/glm.hpp> struct Vertex { glm::vec3 Position; glm::vec3 Normal; glm::vec2 TexCoords; }; class Mesh { private: std::vector<Vertex> vertices; std::vector<unsigned int> indices; GLuint VAO, VBO, EBO; public: Mesh(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices); ~Mesh(); void SetupMesh(); void Draw() const; GLuint GetVAO() const { return VAO; } unsigned int GetIndexCount() const { return static_cast<unsigned int>(indices.size()); } }; #endif // MESH_H ``` ### src/graphics/mesh.cpp ```cpp #include "mesh.h" #include <iostream> Mesh::Mesh(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices) : vertices(vertices), indices(indices) { SetupMesh(); } Mesh::~Mesh() { glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); } void Mesh::SetupMesh() { // Create buffers/arrays glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO); glBindVertexArray(VAO); // Load data into vertex buffers glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); // Set the vertex attribute pointers // Vertex Positions glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); // Vertex Normals glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); // Vertex Texture Coords glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); glBindVertexArray(0); } void Mesh::Draw() const { glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_INT, 0); glBindVertexArray(0); } ``` ### src/scene/scene.h ```cpp #ifndef SCENE_H #define SCENE_H #include <vector> #include <memory> #include "gameobject.h" #include "../graphics/renderer.h" class Scene { private: std::vector<std::unique_ptr<GameObject>> gameObjects; public: Scene(); ~Scene(); void Update(float deltaTime); void Render(Renderer* renderer); GameObject* AddGameObject(std::unique_ptr<GameObject> gameObject); const std::vector<std::unique_ptr<GameObject>>& GetGameObjects() const { return gameObjects; } }; #endif // SCENE_H ``` ### src/scene/scene.cpp ```cpp #include "scene.h" #include <iostream> Scene::Scene() { // Create default camera } Scene::~Scene() { // Cleanup } void Scene::Update(float deltaTime) { for (auto& obj : gameObjects) { obj->Update(deltaTime); } } void Scene::Render(Renderer* renderer) { if (renderer) { renderer->Render(*this); } } GameObject* Scene::AddGameObject(std::unique_ptr<GameObject> gameObject) { GameObject* ptr = gameObject.get(); gameObjects.push_back(std::move(gameObject)); return ptr; } ``` ### src/scene/gameobject.h ```cpp #ifndef GAMEOBJECT_H #define GAMEOBJECT_H #include <memory> #include <vector> #include "../graphics/mesh.h" #include "../components/transform.h" class Component; class GameObject { private: std::vector<std::unique_ptr<Component>> components; Transform* transform; public: GameObject(); ~GameObject(); void Update(float deltaTime); template<typename T> T* AddComponent() { static_assert(std::is_base_of<Component, T>::value, "T must inherit from Component"); std::unique_ptr<T> component = std::make_unique<T>(); T* ptr = component.get(); components.push_back(std::move(component)); return ptr; } template<typename T> T* GetComponent() const { for (const auto& component : components) { if (T* comp = dynamic_cast<T*>(component.get())) { return comp; } } return nullptr; } Transform* GetTransform() const { return transform; } }; #endif // GAMEOBJECT_H ``` ### src/scene/gameobject.cpp ```cpp #include "gameobject.h" #include "../components/transform.h" GameObject::GameObject() { transform = AddComponent<Transform>(); } GameObject::~GameObject() { // Cleanup components } void GameObject::Update(float deltaTime) { for (auto& component : components) { component->Update(deltaTime); } } ``` ### src/components/transform.h ```cpp #ifndef TRANSFORM_H #define TRANSFORM_H #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> class Component; class Transform : public Component { private: glm::vec3 position; glm::vec3 rotation; glm::vec3 scale; public: Transform(); const glm::vec3& GetPosition() const { return position; } const glm::vec3& GetRotation() const { return rotation; } const glm::vec3& GetScale() const { return scale; } void SetPosition(const glm::vec3& pos) { position = pos; } void SetRotation(const glm::vec3& rot) { rotation = rot; } void SetScale(const glm::vec3& sc) { scale = sc; } glm::mat4 GetModelMatrix() const; void Update(float deltaTime) override; }; #endif // TRANSFORM_H ``` ### src/components/transform.cpp ```cpp #include "transform.h" Transform::Transform() : position(0.0f), rotation(0.0f), scale(1.0f) { } glm::mat4 Transform::GetModelMatrix() const { glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, position); model = glm::rotate(model, rotation.x, glm::vec3(1.0f, 0.0f, 0.0f)); model = glm::rotate(model, rotation.y, glm::vec3(0.0f, 1.0f, 0.0f)); model = glm::rotate(model, rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)); model = glm::scale(model, scale); return model; } void Transform::Update(float deltaTime) { // Transform doesn't need to update anything } ``` ### src/components/component.h ```cpp #ifndef COMPONENT_H #define COMPONENT_H class Component { public: virtual ~Component() = default; virtual void Update(float deltaTime) = 0; }; #endif // COMPONENT_H ``` ### src/components/meshrenderer.h ```cpp #ifndef MESHRENDERER_H #define MESHRENDERER_H #include "component.h" #include "../graphics/mesh.h" class MeshRenderer : public Component { private: Mesh* mesh; public: MeshRenderer(); void SetMesh(Mesh* m) { mesh = m; } Mesh* GetMesh() const { return mesh; } void Update(float deltaTime) override; }; #endif // MESHRENDERER_H ``` ### src/components/meshrenderer.cpp ```cpp #include "meshrenderer.h" MeshRenderer::MeshRenderer() : mesh(nullptr) { } void MeshRenderer::Update(float deltaTime) { // Mesh renderer doesn't need to update anything } ``` ### src/main.cpp ```cpp #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include "scene/scene.h" #include "graphics/mesh.h" #include "components/meshrenderer.h" // Error callback for GLFW void glfwErrorCallback(int error, const char* description) { std::cerr << "GLFW Error: " << description << std::endl; } int main() { // Initialize GLFW if (!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return -1; } glfwSetErrorCallback(glfwErrorCallback); // Set OpenGL version and profile glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Create window GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Engine", nullptr, nullptr); if (!window) { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // Initialize GLEW if (glewInit() != GLEW_OK) { std::cerr << "Failed to initialize GLEW" << std::endl; return -1; } // Set up OpenGL settings glEnable(GL_DEPTH_TEST); // Create scene Scene scene; // Main loop while (!glfwWindowShouldClose(window)) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Update and render scene float deltaTime = 0.016f; // Assuming 60 FPS scene.Update(deltaTime); glfwSwapBuffers(window); glfwPollEvents(); } // Cleanup glfwTerminate(); return 0; } ``` ### CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.10) project(OpenGLEngine) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Find required packages find_package(glfw3 REQUIRED) find_package(GLEW REQUIRED) find_package(glm REQUIRED) # Add executable add_executable(${PROJECT_NAME} src/main.cpp src/scene/scene.cpp src/scene/gameobject.cpp src/graphics/mesh.cpp src/graphics/shader.cpp src/graphics/renderer.cpp src/components/transform.cpp src/components/meshrenderer.cpp ) # Include directories target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${GLFW_INCLUDE_DIRS} ${GLEW_INCLUDE_DIRS} ${GLM_INCLUDE_DIRS} ) # Link libraries target_link_libraries(${PROJECT_NAME} glfw GLEW::GLEW ${OPENGL_LIBRARIES} ) # Set C++ standard set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON ) ``` ### Shader Files (src/shaders/vertex.glsl and src/shaders/fragment.glsl) **vertex.glsl:** ```glsl #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; uniform mat4 projection; out vec3 FragPos; out vec3 Normal; out vec2 TexCoord; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model)))) * aNormal; TexCoord = aTexCoord; gl_Position = projection * view * vec4(FragPos, 1.0); } ``` **fragment.glsl:** ```glsl #version 330 core out vec4 FragColor; in vec3 FragPos; in vec3 Normal; in vec2 TexCoord; uniform vec3 objectColor; uniform vec3 lightPos; uniform vec3 viewPos; void main() { // Ambient float ambientStrength = 0.1; vec3 ambient = ambientStrength * objectColor; // Diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diff * objectColor; vec3 result = ambient + diffuse; FragColor = vec4(result, 1.0); } ``` This implementation provides a basic but functional OpenGL engine framework with: 1. **Core Architecture**: Scene management, GameObjects with components 2. **Rendering Pipeline**: Meshes, shaders, and transforms 3. **Component System**: Extensible component architecture 4. **OpenGL Integration**: Proper setup and rendering loop 5. **Build System**: CMake configuration for easy compilation The engine supports: - Basic 3D rendering with lighting - Transform-based object positioning - Component-based architecture - Modular design for extensibility - Cross-platform compatibility To build and run: 1. Install required dependencies (GLFW, GLEW, GLM) 2. Create a build directory and run CMake 3. Compile the project 4. Run the executable This framework provides a solid foundation that can be extended with features like audio, physics, scripting, etc.