CEngine 클래스 init 함수 구현 중, DC (1)

Yama·2024년 1월 22일
0

어소트락 수업

목록 보기
52/55
class CEngine
{
public:
	static CEngine* GetEngine()
	{
		static CEngine engine;
		return &engine;
	}

private:
	CEngine();
	CEngine(const CEngine& _other) = delete;
};
  • CEngine클래스에는 이제 두번쨰 구현한 싱글톤 패턴을 쓸 것이고 자주 반복되는 싱글톤 패턴이기 때문에 매크로로 만들것이다.
// define.h 안에 매크로 지정.
#define SINGLE(type) public:\
					static CEngine* GetInst()\
					{\
						static type mgr;\
						return &mgr;\
					}\
					private:\
						type();\
					type(const type& _other) = delete;\
					public:\
						~type();
  • SINGLE(type) 괄호안은 클래스트의 템플릿처럼 한다.
  • 일열로 쓰면 직관적이라 쓰면 보기 어렵기때문에 \를 쓰면 매크로가 계속 이어서 나간다.
  • \뒤에 공백 문자 있으면 인식이 안된다.(조심)
// pch.h 내부
#include "define.h"

  • 미리 컴파일 된 헤더에 매크로헤더파일 작성해두자.
    • "define.h"를 몰르니
class CEngine
{
	SINGLE(CEngine)
};

  • 미리 컴파일된 헤더에 "define.h" 참조해서 오류 해결됨.
  • CEngine 클래스의 싱글톤을 매크로 구문을 이용해서 작게 했다.
    • 마우스 올려보면 어떻게 매크로 되었는지 보여준다.
    • 매크로 전에는 GetEngine함수엿는데 매크로는 다른데에서도 싱글톤을 쓸것이라 이름이 이상하다 그래서 GetInst로 이름을 변경
// CEngine.h의 CEngine클래스 내부
public:
	int init();
  • init함수 선언 이니셜라이져 초기화 함수(엔진 초기화 함수)
// CEngine.cpp 내부
int CEngine::init()
{
	// 3
	FAILED(E_FAIL);
    // 1
	return S_OK;
	// 2
   	return E_FAIL;
}
  • 1번의 S_OK는 매크로 구문은 0을 반환한다.(성공)
  • 2번은 숫자 1을 반환한다(실패)
  • 16진수 맨앞에 8이 적혀있는데 맨앞쪽 비트가 1이라는 소리다.
    • 맨 앞쪽 비트가 1이면 음수다.
  • 3번의 FAILED매크로는 truefalse를 체크하는 매크로
    • 매크로에 들어온놈이 0보다 작냐 크냐를 따져서 체크한다.
    • 3번은 그래서 E_FAIL이 들어가면 0보다 작기 때문에 true
      • 실패 한것이다.
    • 3번에 S_OK을 넣으면 false가 된다.
      • 실패하지 않은것이다.
// main.cpp 내부
	// 1
    if (FAILED(CEngine::GetInst()->init()))
    {
    	// 2
        MessageBox(nullptr, L"엔진 초기화 실패", L"에러 발생", MB_OK);
        return 0;
    }
  • 전체 코드 Engine 초기화이다.

  • 1번의 if문은 Engine의 초기화 성공 실패 여부를 따지는 코드다.

    • 초기화 실패시 들어온다.

      • 초기화를 실패했기 떄문에 E_FAIL이 들어와서 true가 되기 떄문네 조건문 안으로 들어간다.
    • 1번은 Engine 초기화 실패시 1번의 조건문 으로 들어옴 ==> 프로그램 종료

  • 2번을 안적으면 프로그램이 키자마자 바로 꺼져서 왜 꺼진지 모른다.

    • 간단하게 메세지를 출력해주는 MessageBox함수

      • 첫번쨰 인자인 부모 원도우 지정하라하는데 알빠노

      • 두번쨰 인자는 원도우 내부에 띄울 메세지 넣는 인자

      • 세번쨰 인자는 caption은 생성되는 원도우의 머릿
        말.

      • 4번째 인자는 MB_OK은 단순하게 가운데에 확인 버튼만 있는 것 나온다.

  • 확인 눌르면 중단점으로 온다.

CEngine 클래스의 맴버를 누구로 들것인가

  • 우리는 게임이다.
  • 원도우 프로그래밍을 꼭 특정 원도우가 종료된다고해서 창하나가 꺼진다고해서 프로세스가 꼭 꺼지는것은아니다 메인은 프로세스는 원도우를 가질수도있고 안가질수도있고 여러개를 가질수도있고 백그라운드로 유저와 소통되지않는 프로세스는 돌아가고 원도우에 창이 뜨는것은 사용자와 소통을 하기 위해서이다.
  • 예를 들자면 크롬이라는 창이 안뜨면 웹서핑을 어케함?
  • 화면에 보이는것이 있어야 한다.
  • 상호작용을 위해서 중간층인 원도우 비주얼적인 ui를 통해서 상호작용한다.
  • 백신잡는 프로그램이 화면에 창을 띄을 필요가없다 백드라운에서 돌면되니까 창없이 돈다
  • 프로세스는 필요할떄 창을 그떄그떄 만들어서 중간 인터페이스 역할을 하는것이다.
  • 우리는 게임 프로그램을 만드니 게임은 창을 여러개를 만들수 있지만 하나의 게임창에 게임을 띄운다.
  • 그창이 꺼지면 유저의 의지기 때문에 창이 꺼진다.
  • 게임프로그래밍에서는 창과 게임의 라이프 사이클을 동일시하는게 별로 나쁘지 않은 선택이다.
  • 우리의 게임 최고 관리자인 엔진은 첫번쨰 맴버로 화면을 담당하는 맴버를 쓸것이다.

gpt

CEngine 클래스의 멤버로 화면을 담당하는 멤버를 선택하는 이유

개발자 블로그에 올릴 형식으로 정리해 보겠습니다.

개요

CEngine 클래스는 게임 개발에서 핵심적인 역할을 하는 클래스입니다. 이 클래스의 멤버를 결정하는 것은 게임의 전반적인 구조와 성능에 큰 영향을 미칩니다. 특히, 화면을 담당하는 멤버의 선택은 게임 프로그래밍의 중요한 측면 중 하나입니다.

게임과 윈도우 프로그래밍의 관계

  • 게임은 기본적으로 윈도우 프로그래밍의 원리를 따릅니다. 하지만, 특정 윈도우가 종료되더라도 프로세스가 꼭 종료되는 것은 아닙니다.
  • 메인 프로세스는 윈도우를 가질 수도, 안 가질 수도 있으며, 여러 개를 가질 수도 있습니다.
  • 백그라운드 프로세스는 사용자와 직접적인 소통이 없어도 동작할 수 있으며, 윈도우에 창이 뜨는 것은 사용자와의 소통을 위함입니다.

사용자와의 상호작용

  • 상호작용을 위해서는 중간층인 윈도우와 시각적인 UI가 필요합니다.
  • 예를 들어, 크롬 브라우저의 경우 창이 없으면 웹 서핑을 할 수 없습니다.
  • 반면, 백신 프로그램과 같이 백그라운드에서 동작하는 프로그램은 화면에 창을 띄울 필요가 없습니다.

게임 프로그래밍에서의 창 관리

  • 게임 프로그래밍에서는 창과 게임의 라이프 사이클을 동일시하는 것이 합리적인 선택일 수 있습니다.
  • 게임은 창을 여러 개 만들 수 있지만, 일반적으로 하나의 게임 창에 게임을 띄웁니다.
  • 게임 창이 닫히는 것은 사용자의 의지에 따른 것이며, 이는 게임의 종료를 의미합니다.

결론: CEngine 클래스의 첫 번째 멤버로 화면을 담당하는 멤버 선택

  • CEngine 클래스의 첫 번째 멤버로 화면을 담당하는 멤버를 선택하는 것은 게임과 사용자 간의 상호작용을 원활하게 하기 위함입니다.
  • 이 멤버는 게임의 시각적 요소를 관리하고, 사용자의 입력에 반응하여 게임의 상태를 업데이트하는 중요한 역할을 합니다.
  • 따라서, 게임의 최고 관리자인 엔진의 중요한 멤버로 화면을 담당하는 멤버를 포함하는 것은 효과적인 게임 프로그래밍을 위한 핵심적인 결정입니다.
// CEngine.h의 CEngine클래스 내부.
private:
	// 1
	HWND	m_hMainWnd; // 메인 원도우 핸들
	// 2
	POINT	m_Resoulution;	// 메인 원도우의 해상도
  • 1번은 메인 원도우의 핸들이다.
    • 원도우는 OS에서 관리하는 커널 오브젝트이기 때문에 메인 원도우의 핸들값을 가지고 있어야한다.
  • 2번째 맴버는 메인 원도우의 해상도다.
    • 가로세로 원도우 크기를 정해줘야한다
    • 1280 * 768 픽셀수(해상도), 1920 * 1080 커질수록 가로세로 픽셀(메모리 조각)수가 많아진다는 것.
  • 그림판에 줄 그음
    • 확대해서 보면 엄청난 수에 픽셀(메모리 조각)수들 있다.
      - 한칸한칸이 픽셀 하나다.(3바이트)
  • POINT의 구조체는 맴버로 x,y밖에 없어서 나중에 자체적인 구조체를 따로 만들어서 POINT를 안쓸것이다.
    • 4바이트 정수 타입이다.
// CEngine.h의 CEngine클래스 내부
public:
	int init(HWND _hWnd, POINT _Resolution);
  • 초기화 함수의 인자들로는 메인원도우의 핸들값, 두번쨰 인자는 핸들의 원도우가 해상도가 몇이 될지를 설정해주는 인자다.
// main.cpp
	// 1
    if (FAILED(CEngine::GetInst()->init(g_hWnd, POINT{ 1280, 768 })))
    {
        // Engine 초기화 실패 ==> 프로그램 종료
        MessageBox(nullptr, L"엔진 초기화 실패", L"에러 발생", MB_OK);
        return 0;
    }
  • 첫번째 인자로 메인함수의 전역변수로 선언한 메인 원도우 핸들값을 줬다
  • 두번쨰 인자로는 해상도를 내가 정해서 준다.(1280 * 768)
// CEngine.cpp
CEngine::CEngine()
	: m_hMainWnd(nullptr)
	, m_Resoulution{}
{}
  • 기본 생성자를 만들어 둔다.
// CEngine.cpp 내부
// 1
int CEngine::init(HWND _hWnd, POINT _Resolution)
{
	// 2
	m_hMainWnd = _hWnd;
    // 3
	m_Resoulution = _Resolution;
    
    // 4
	return S_OK;
}
  • init(이니셜라이저) 초기화 함수의 구현 부분이다.

    • 인자로 핸들값과 해상도를 받는다
  • 2번에서는 메인함수에서 전달해준 메인 원도우의 핸들값을 사용한다.

  • 3번에서는 해상도를 메인함수에서 입력으로 준 해상도

  • 4번은 초기화 함수가 걍 강제로 S_OK를 했으니 원도우 뜬다.

  • 실제 원도우는 켜질떄 화면의 해상도는 자동 지정되기 때문에 우리가 원하는 수치 일리가 없다.

    • 엔진 초기화할떄 넣어주는 숫자가 내가 원하는 지정하고 싶은 해상도 값을 넣어줘서 엔진을 초기화 시킨다.

2교시

SetWindowPos 함수

  • 원도우 해상도 변경해줄수 있다.
  • 원도우 위치값을 변경할 수 있다.
// CEngine.cpp의 init함수. 내부.
	SetWindowPos(m_hMainWnd, nullptr, 0, 0, m_Resoulution.x, m_Resoulution.y, 0);

  • 첫번째 인자로는 어떤 원도우의 위치와 크기를 변경할지에 대한 핸들값(ID)을 요구.
  • 두번째 인자는 무시해도 된다.
  • 세번쨰 인자와 네 번째 인자는 지정된 원도우의 좌상단 꼭지점을 어디로 지정할지다.
    • 0, 0으로 지정한다면 모니터의 끄트머리로 간다.
    • 100,100으로 변경한다면 좌측으로 + 100, 아래쪽으로 + 100방향으로 내려와서 원도우 프로그램이 보여진다.
      • 오른쪽 방향과 아래쪽 방향이 +다.
  • 5번째 6번쨰 인자로는 5번째에는 1280, 6번쨰 인자에는 768을 입력했으므로 들어온다.
    • 1000, 1000 입력햇을떄 크기.
    • 500, 500 입력했을떄의 크기.
  • 마지막인자는 플래그 값인데 0을 넣을것이다.

화면에 물체 그리깅.

  • 원도우에서 제공하는 렌더링 기능을 통해서 그림을 그릴 것이다.
  • 화면에 그림을 그리고 싶을떄 그릴수 잇어야 한다.
  • 화면에 그림을 그릴떄 DC가 중요하다.

DC(Device Context)

// CEngine.h의  CEngine 클래스 내부
private:
	HWND	m_hMainWnd; // 메인 원도우 핸들
	POINT	m_Resoulution;	// 메인 원도우의 해상도
	// 1
	HDC		m_hDC;			// 3.3 메인원도우 DC
  • 1번의 맴버는 메인 원도우의 DC다.
  • DC도 게속 사용할것이기 때문에 맴버에 기록을 한다.
m_hDC = GetDC(m_hMainWnd);

  • GetDC는 인자로 핸들값을 원한다.
    • 메인 원도우 핸들값을 넣었다.
  • DC도 커널 오브젝트이다.
// CEngine.cpp의 기본 생성자 
CEngine::CEngine()
	: m_hMainWnd(nullptr)
	, m_Resoulution{}
	// 1
	, m_hDC(nullptr)
{}

CEngine::~CEngine()
{
	// 2
	// DC 삭제.
	ReleaseDC(m_hMainWnd, m_hDC);
}
  • 1번은 기본 생성자에 추가해준것이고
  • 2번은 DC사용하고 나면 지워줘야하니 ReleaseDC함수로 DC제거한다.
    • 첫번째 인자로 메인쪽 핸들
    • 두번쨰 인자로 hDC를 준다.
  • 커널 오브젝트라 우리가 직접 메모리 해제를 못하고 OS가 관리하는 오브젝트이기 때문에 OS한테 ID값을 알려줘서 제거시키고 프로그램 종료 해야 한다.
// main.cpp의 메세지 처리 한 후의 게임로직 코드
        else
        {
            // 메세지가 큐에 없을 때에는 게임 코드 실행
      		// 1
            CEngine::GetInst()->progress();

        }
  • DC가 있다면 할수 있는 것
    • 메세지를 처리하고 이 메세지 없을떄는 else로 들어온다.

      • 컴퓨터의 성능을 볼아서 메세지를 존나 빠르게 반복문을 돌아서 처리하고 계속 else(게임코드)쪽에서 돌고있다.

      • GetMessage함수였을때는 메세지가 없으면 먹통이였는데 PeekMessage함수로 바꾸고 메세지가 없을때는 else로 가게했다.

  • 계속해서 일을 시켜야 하므로 progress함수를 둬서 계속 진행시킨다.
// CEngine.h의 CEngine클래스 내부
public:
	int init(HWND _hWnd, POINT _Resolution);	
    // 1
	void progress();
  • 1번은 progress함수의 선언부.
// CEngine.cpp의 내부.
void CEngine::progress()
{
	Ellipse(m_hDC, 0,0, 100, 100);
}
  • progress함수의 구현부분
  • Elliapse함수는 동그라미를 그릴수 있게해주는 함수다.
    • 첫번쨰 값으로 DC의 핸들값을 요구
    • 두번쨰 ~ 다섯번째인자로는 원을 그리는 요구사항
      - 좌상단 좌표와 가로세로지름길이.
  • 0,0, 100, 100 줬을떄 위치와 크기
    • 원도우의 좌표랑 이 원을 그리는 함수랑은 좌표계가 다르다.
      • 원도우는 존재할곳으로 0,0을 지정해서 좌상단으로 왔다.
      • 원도우를 오지게 옮겨도 원은 원도우의 좌상단을 따라간다.
    • Elliapse에서 말하는 0,0은 현재 원도우안에서의 좌상단이다.
      - 좌측끝을 0,0 우측끝을 100,100으로 지정해둔것이다.
  • 50,50, 100, 100
    • 50,50~ 100,100까지의 원이 나온다.
      - 원도우와 다르게 가로 세로 길이가 아니다.
  • 50,50, 150, 150
    • 처음 원이랑 가로 세로 길이 같게 할려면 이렇게 지정 해야한다.
// CEngine.cpp의 내부.
void CEngine::progress()
{
	Ellipse(m_hDC, 50, 50, 150, 150);
	Rectangle(m_hDC, 50, 50, 150, 150);
}
  • 원과 네모를 위치를 같게 설정해서 만들어버리면 깜빡깜빡거린다.
    • 나중에.

DC란?

  • 비트맵에 렌더링하기 위해 필요한 필수 정보 집합체

  • 화면에 렌더링하는 함수들은 인자로 DC를 항상 넣어달라한다.

    • DC는 렌더링과 관련된 것이다.
  • 장치에다 렌더링하기 위해 필요한것이다.(직역)

  • 비트맵에 렌더링하기 위해 필요한 필수 정보 집합체

  • 비트맵은 단순한 메모리 구조를 가지며, 픽셀 데이터는 압축되지 않습니다.

    • 예를 들어, 1280x768 해상도는 약 백만 픽셀로 구성되며, 각 픽셀은 3바이트(레드, 그린, 블루 각각 1바이트)를 사용해 색상을 나타냅니다.

    • 전체 이미지 크기는 대략 3백만 바이트(약 2.8MB)가 됩니다.

  • 비트맵은 커널 오브젝트로, 운영체제(OS)가 관리합니다.

    • SetWindowPos 함수를 사용하여 원하는 해상도에 맞는 윈도우를 만들면, 해당 해상도의 비트맵이 메모리에 생성됩니다.
    • 운영체제는 현재 활성화된 창의 비트맵을 관리하고, 이 비트맵의 픽셀 데이터를 모니터로 송출하여 화면에 표시합니다.
  • 모니터는 주사율에 따라 비트맵 데이터를 받아 화면에 표시합니다.

    • 예를 들어, 60Hz 주사율의 모니터는 초당 60번의 비트맵 데이터를 받아 화면에 표시합니다.
    • 모니터는 화면 정보를 시각적으로 표시하는 그래픽 출력 장치 역할을 합니다.
  • 프로그램을 실행하면 해당 해상도의 비트맵이 생성되고, 이 비트맵은 화면에 표시할 그래픽 정보를 담당합니다.

  • 전체 바탕화면은 더 큰 메모리 판에 각 윈도우 프로그램의 비트맵이 해당 영역을 담당하고, 모니터는 이 정보를 받아 화면에 표시합니다.

    • 만약 특정 윈도우 프로그램을 화면에서 숨기면, 해당 비트맵은 존재하지만 모니터로 송출되지 않아 화면에 표시되지 않습니다.

네모와 동그라미의 깜빡임 현상 이해하기

문제 상황

컴퓨터 그래픽스에서는 때때로 간단한 도형을 화면에 그리는 것만으로도 예상치 못한 현상이 발생할 수 있습니다. 예를 들어, C++에서 Ellipse 함수와 Rectangle 함수를 사용하여 네모와 동그라미를 겹쳐 그릴 경우, 화면에서 깜빡이는 현상이 나타날 수 있습니다. 이는 다음과 같은 코드로 구현할 수 있습니다:

void CEngine::progress() 
{
    Ellipse(m_hDC, 50, 50, 150, 150); // 동그라미 그리기
    Rectangle(m_hDC, 50, 50, 150, 150); // 네모 그리기
}

현상의 원인

  • 색상 채우기

    • EllipseRectangle 함수는 테두리를 검은색으로 그릴 뿐만 아니라, 도형의 내부도 하얀색으로 채웁니다. 따라서, 네모와 동그라미가 서로를 덮어쓰면서 그려집니다.
  • 무한 반복

    • 이 과정이 끊임없이 반복되면서, 도형이 계속해서 새로 그려지고 덮어지는 것이 반복됩니다.
  • 고속 렌더링

    • 컴퓨터의 높은 성능으로 인해 이러한 그리기 작업이 초당 수십만 번 이루어질 수 있습니다.
  • 모니터 주사율

    • 대부분의 모니터는 초당 60번의 주사율로 작동합니다. 따라서, 네모와 동그라미가 매우 빠르게 번갈아 가면서 그려지기 때문에, 이것이 깜빡이는 것처럼 보이게 됩니다.

인식의 한계

  • 화면 갱신 속도
    • 화면의 갱신 속도를 높게 설정한다고 해도, 깜빡임 현상을 완전히 해결하기는 어렵습니다.
      • 인간의 시각이 약 100~120프레임을 한계로 인식하기 때문이다.

기술적 배경

  • 비트맵 메모리

    • 각 픽셀의 데이터는 비트맵이라는 메모리에 저장됩니다.
  • 운영체제의 역할

    • 운영체제는 화면에 표시할 전체 픽셀 정보를 취합하여, 모니터의 주사율에 맞추어 이를 송출합니다.
      • 화면에 색상 정보가 시각적으로 표시됩니다.

추가된 강의 코드

main.cpp

// 위의 코드 생략 그대로임 
    // Engine 초기화
    if (FAILED(CEngine::GetInst()->init(g_hWnd, POINT{ 1280, 768 })))
    {
        // Engine 초기화 실패 ==> 프로그램 종료
        MessageBox(nullptr, L"엔진 초기화 실패", L"에러 발생", MB_OK);
        return 0;
    }
 // 아래코드도 생략 그대로임

pch.h

#pragma once
// 위의 선언들 생략.

#include "define.h"

define.h

#pragma once


#define SINGLE(type) public:\
					static CEngine* GetInst()\
					{\
						static type mgr;\
						return &mgr;\
					}\
					private:\
						type();\
					type(const type& _other) = delete;\
					public:\
						~type();

CEngine.h

#pragma once

class CEngine
{
	SINGLE(CEngine)

private:
	HWND	m_hMainWnd; // 메인 원도우 핸들
	POINT	m_Resoulution;	// 메인 원도우의 해상도
	HDC		m_hDC;			// 3.3 메인원도우 DC


public:

	int init(HWND _hWnd, POINT _Resolution);

	void progress();

};

CEngine.cpp

#include "pch.h"
#include "CEngine.h"

CEngine::CEngine()
	: m_hMainWnd(nullptr)
	, m_Resoulution{}
	, m_hDC(nullptr)
{
}

CEngine::~CEngine()
{
	// DC 삭제.
	ReleaseDC(m_hMainWnd, m_hDC);
}


int CEngine::init(HWND _hWnd, POINT _Resolution)
{
	m_hMainWnd = _hWnd;
	m_Resoulution = _Resolution;

	SetWindowPos(m_hMainWnd, nullptr, 0, 0, m_Resoulution.x, m_Resoulution.y, 0);

	m_hDC = GetDC(m_hMainWnd);

	return S_OK;
}


void CEngine::progress()
{
	Ellipse(m_hDC, 50,50, 150, 150);
	Rectangle(m_hDC, 50, 50, 150, 150);
}

1차 24.01.22
2차 24.01.23
3차 24.01.24
4차 24.01.25
5차 24.01.26

0개의 댓글