Introduction to 2D game engine development

Posted by danielson2k on Thu, 03 Feb 2022 07:03:49 +0100

Previous section: https://blog.csdn.net/z736248591/article/details/122658701

Build engine framework

review

In the previous chapter, we created an empty window. In this chapter, we built an engine framework to encapsulate window display and rendering.

Frame introduction

First, let's introduce the four most important global classes:

  • Director: responsible for controlling the whole game.
  • Graphics: responsible for the rendering part of the game.
  • Input: responsible for processing all user inputs.
  • Audio: responsible for managing all audio operations globally.

This section mainly introduces the rendering part of Graphics.

establish

Create the header file and source file of the Graphics class.

statement

In graphics The header file of Direct2D is introduced into H.

#include <windows.h>
#include <d2d1_3.h>
#include <wincodec.h>

d2d1_3 is the header file of direct2d version 1.3 on Windows. You can view the details MSDN Updated content on.

Import STL header file.

#include <memory>
#include <string>
#include <iostream>

Link Direct2D library files.

#pragma comment(lib, "d2d1.lib")

Declare class member properties.

class Graphics
{
    public:
    static void Init(int width, int height,const std::wstring &title = L"");
    static void Render(std::shared_ptr<Scene> scene);
    static void Dispose();
    private:
    inline static HWND hwnd = NULL;
    inline static ID2D1Factory* d2dFactory = NULL;
    inline static ID2D1HwndRenderTarget* renderTarget = NULL;
    static void InitWindows(int width, int height,const std::wstring& title = L"");
    static void InitD2D();
};

initialization

The rendering needs to be initialized. The main task is to create a window and initialize Direct2D.

void Graphics::Init(int width, int height, const std::wstring& title)
{
    InitWindows(width, height, title);
    InitD2D();
}

Window creation is the content of the previous section, which will not be introduced one by one here.

LRESULT CALLBACK WindowProc(
	HWND hwnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam
);
void Graphics::InitWindows(int width, int height, const std::wstring& title)
{
    HINSTANCE hInstance;
    hInstance = GetModuleHandle(NULL);
    WNDCLASS wndClass{};
    // Set application icon
    wndClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wndClass.lpfnWndProc = WindowProc;
    // Set class name
    wndClass.lpszClassName = L"SGE";
    wndClass.hInstance = hInstance;
    // Specify a unique style
    wndClass.style = CS_OWNDC;

    // register
    RegisterClass(&wndClass);

    HWND hwnd = CreateWindow(
        L"SGE",							// Class name
        L"The Seed Game Engine",		// Window name
        WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE,
        38,
        20,
        width,
        height,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (FAILED(hwnd))
    {
        std::wcout << L"Error creating window!" << std::endl;
        throw;
    }
    Graphics::hwnd = hwnd;

    // Resize window
    RECT rc;
    GetClientRect(hwnd, &rc);
    MoveWindow(hwnd, 38, 20, width + width - rc.right, height + height - rc.bottom, true);
}

Initialize Direct2D.

void Graphics::InitD2D()
{
    HRESULT hr;

Open the debugging visual layer in Debug mode.

D2D1_FACTORY_OPTIONS options{};
#if defined(_DEBUG)
		// Start the Debug visualization layer
		options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

Initialize D2D1Factory class, which will be used to create D2D resource objects in future operations.

hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory);

if (FAILED(hr))
{
    std::wcout << L"establish D2D The factory failed." << std::endl;
    throw;
}

Create a paint render area.

RECT rc;
GetClientRect(hwnd, &rc);
hr = d2dFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),D2D1::HwndRenderTargetProperties(hwnd,D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top)),&renderTarget);
if (FAILED(hr))
{
    std::wcout << L"Failed to create render area!" << std::endl;
    throw;
}

Render

The Render function is responsible for rendering the content. The scene parameter is the scene class object, which organizes the content. See the next section for the specific implementation.

void Graphics::Render(std::shared_ptr<Scene> scene)
{

Ready to start drawing

renderTarget->BeginDraw();

Only BeginDraw can be called to start drawing.

Use a black background color to empty the drawing area

static D2D1::ColorF blackgroundColor = D2D1::ColorF::Black;
renderTarget->Clear(blackgroundColor);

Draw content

scene->Render();

End drawing

HRESULT hr = renderTarget->EndDraw();
if (FAILED(hr))
{
    std::wcout << L"Drawing error!" << std::endl;
    throw;
}

Destroy

When the game ends running, you need to destroy the previously applied resources.

Here is a macro to safely release resources.

#define SAFE_RELEASE(P) if(P){P->Release() ; P = NULL ;}

Destroy all requested resources.

void Graphics::Dispose()
{
    SAFE_RELEASE(renderTarget);
    SAFE_RELEASE(d2dFactory);
}

Complete code

Graphics.h

#pragma once
#include <windows.h>
#include <d2d1_3.h>
#include <wincodec.h>

#include <memory>
#include <string>
#include <iostream>
#include "Scene.h"

#pragma comment(lib, "d2d1.lib")

namespace sge {
	class Graphics
	{
	public:
		static void Init(int width, int height,const std::wstring &title = L"");
		static void Render(std::shared_ptr<Scene> scene);
		static void Dispose();
	private:
		inline static HWND hwnd = NULL;
		inline static ID2D1Factory* d2dFactory = NULL;
		inline static ID2D1HwndRenderTarget* renderTarget = NULL;
		static void InitWindows(int width, int height,const std::wstring& title = L"");
		static void InitD2D();
	};
}

Graphics.cpp

#include "Graphics.h"

LRESULT CALLBACK WindowProc(
	HWND hwnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam
);

#define SAFE_RELEASE(P) if(P){P->Release() ; P = NULL ;}

namespace sge {
	void Graphics::Init(int width, int height, const std::wstring& title)
	{
		InitWindows(width, height, title);
		InitD2D();
	}
	void Graphics::Render(std::shared_ptr<Scene> scene)
	{
		renderTarget->BeginDraw();

		// Empty drawing area
		static D2D1::ColorF blackgroundColor = D2D1::ColorF::Black;
		renderTarget->Clear(blackgroundColor);

		scene->Render();

		HRESULT hr = renderTarget->EndDraw();
		if (FAILED(hr))
		{
			std::wcout << L"Drawing error!" << std::endl;
			throw;
		}
	}

	void Graphics::Dispose()
	{
		SAFE_RELEASE(renderTarget);
		SAFE_RELEASE(d2dFactory);
	}

	void Graphics::InitWindows(int width, int height, const std::wstring& title)
	{
		HINSTANCE hInstance;
		hInstance = GetModuleHandle(NULL);
		WNDCLASS wndClass{};
		// Set application icon
		wndClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
		wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
		wndClass.lpfnWndProc = WindowProc;
		// Set class name
		wndClass.lpszClassName = L"SGE";
		wndClass.hInstance = hInstance;
		// Specify a unique style
		wndClass.style = CS_OWNDC;

		// register
		RegisterClass(&wndClass);

		HWND hwnd = CreateWindow(
			L"SGE",							// Class name
			L"The Seed Game Engine",		// Window name
			WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE,
			38,
			20,
			width,
			height,
			NULL,
			NULL,
			hInstance,
			NULL);

		if (FAILED(hwnd))
		{
			std::wcout << L"Error creating window!" << std::endl;
			throw;
		}
		Graphics::hwnd = hwnd;

		// Resize window
		RECT rc;
		GetClientRect(hwnd, &rc);
		MoveWindow(hwnd, 38, 20, width + width - rc.right, height + height - rc.bottom, true);
	}

	void Graphics::InitD2D()
	{
		HRESULT hr;
		D2D1_FACTORY_OPTIONS options{};
#if defined(_DEBUG)
		// Start the Debug visualization layer
		options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
		hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory);

		if (FAILED(hr))
		{
			std::wcout << L"establish D2D The factory failed." << std::endl;
			throw;
		}

		// Get drawing area
		RECT rc;
		GetClientRect(hwnd, &rc);
		hr = d2dFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
			D2D1::HwndRenderTargetProperties(
				hwnd,
				D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top)),
			&renderTarget);
		if (FAILED(hr))
		{
			std::wcout << L"Failed to create render area!" << std::endl;
			throw;
		}
	}
}

summary

This section mainly introduces the four main classes of the game engine framework and the preparation of the rendering part. The next section continues with the remaining sections that have not been written.

Topics: C++ Visual Studio DirectX