전 수업 요약

void CEngine::progress()
{
	USE_PEN(m_hDC, PEN_RED);
	USE_BRUSH(m_hDC, BRUSH_BLACK);

	Rectangle(m_hDC, 50, 50, 150, 150);
}
  • 매크로를 사용한 펜과 브러쉬의 사용법만 남겨다.
    • 나머지 방법들은 다 지웠으므로 복습은 전 글로.
  • 펜 사용법과 브러쉬 사용법과 배웠고 최종적으로는 간단한 사용 클래스와 매크로로 축약을 해서 원하는 물체를 DC를 통해 렌더링을 할떄 DC에 펜이랑 브러쉬를 설정해주고 그릴수 잇었다.
    • 실제 동작과정
      • 펜이나 브러쉬를 쓰고나서 원래의 펜으로 돌아오는 과정이 있지만 사용햇던 클래스 객체의 소멸자를 통해 다 해결해서 이제 신경 쓸 필요가 없어졌다는 것.

CreateDefaultGDIObject 함수로 코드로 간결화.

// CEngine.h CEngine클래스
private:
	void CreateDefaultGDIObject();
  • 펜이나 브러쉬를 GDI라고 많이부른다.
    • 그래픽 디바이스 인터페이스
  • 렌더링을 할때 사용하는 그래픽 물체를 만들어주는 함수로 지저분한 어제 코드 정리할것이다.
// CEngine.cpp 내부
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);
	
    // 1
	CreateDefaultGDIObject();
    
    // 2
    //m_hDC = GetDC(m_hMainWnd);
    // 3
    // 자주 사용할 펜 생성
    //m_arrPen[PEN_RED] = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
	//m_arrPen[PEN_GREEN] = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
	//m_arrPen[PEN_BLUE] = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
    // 4
	// 자주 사용할 브러쉬 생성
	//m_arrBrush[BRUSH_RED] = CreateSolidBrush(RGB(255, 0, 0));
	//m_arrBrush[BRUSH_GREEN] = CreateSolidBrush(RGB(0, 255, 0));;
	//m_arrBrush[BRUSH_BLUE] = CreateSolidBrush(RGB(0, 0, 255));;
	//m_arrBrush[BRUSH_HOLLOW] = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
	//m_arrBrush[BRUSH_BLACK] = (HBRUSH)GetStockObject(BLACK_BRUSH);

	return S_OK;
}
  • 엔진 초기화 함수에 CreateDefaultGDIObject 1번함수를 호출함수 추가해서 2~4번을 저 CreateDefaultGDIObject 구현부분으로 옮긴다.
// // CEngine.cpp 내부
void CEngine::CreateDefaultGDIObject()
{
	m_hDC = GetDC(m_hMainWnd);
    
	m_arrPen[PEN_RED] = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
	m_arrPen[PEN_GREEN] = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
	m_arrPen[PEN_BLUE] = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));

	// 자주 사용할 브러쉬 생성
	m_arrBrush[BRUSH_RED] = CreateSolidBrush(RGB(255, 0, 0));
	m_arrBrush[BRUSH_GREEN] = CreateSolidBrush(RGB(0, 255, 0));;
	m_arrBrush[BRUSH_BLUE] = CreateSolidBrush(RGB(0, 0, 255));;
	m_arrBrush[BRUSH_HOLLOW] = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
	m_arrBrush[BRUSH_BLACK] = (HBRUSH)GetStockObject(BLACK_BRUSH);
}
  • 초기화에 있던걸 옮겼당.

Level 설계

  • 레벨안에서 물체를 관리할것이고 관리하는 물체들을 레벨이 시작되는 순간 비긴을 호출하고 평소의 매 프레임마다 레벨안에 존재하는 물체한테 알맞은 시점 함수를 호출할것이다.

  • 게임에서 여러 스테이지가 있는것처럼 레벨도 여러개가 있을것이다.

    • 레벨 클래스로 하나의 레벨이 이런 형태라고 생겼고 동작할것을 클래스로 만들어 둔것이다.

    • 여러개의 레벨을 관리하는 관리자 클래스가 있어야 한다.

      • CLevelMgr 클래스로 레벨들을 관리할것이다.
  • CEngine 클래스가 우리의 최고 관리자 라면 지금부터 만들 CLevelMgr 관리자 클래스는 레벨 클래스들만 관리하는 매니저다.

CLevelMgr 클래스

  • 레벨 매니저
    • 매니저라 기본 클래스는 없다.
// 2
class CLevel;

class CLevelMgr
{
	// 1
	SINGLE(CLevelMgr)
private:
	// 3
	CLevel m_pCurrentLevel;
	// 4
	CLevel* m_pCurrentLevel;

public:
};
// CLevelMgr.cpp 파일 내부
	#include "CLevel.h"
  1. 싱글톤 패턴 사용

    • CLevelMgr 클래스는 싱글톤 패턴을 사용하여 구현됩니다.

    • 싱글톤 패턴은 클래스의 인스턴스가 하나만 생성되어 전역적으로 접근 가능하도록 하는 디자인 패턴입니다.

  2. 전방 선언(Forward Declaration)

    • CLevel 클래스에 대한 전방 선언이 사용됩니다.

    • 전방 선언은 헤더 파일 간의 순환 종속성을 방지하는 방법으로, 클래스의 이름만 선언하고 실제 정의는 나중에 제공합니다.

      • 이 방식은 헤더 파일 간의 복잡한 참조를 줄이고, 컴파일 시간을 단축하는 데 도움이 됩니다.
  3. 포인터 대신 객체 사용의 문제점

  • CLevel m_pCurrentLevel;과 같이 CLevel 객체를 직접 멤버로 사용하는 것은 문제가 있을 수 있습니다.

    • 이 경우 CLevel 클래스의 전체 정의가 필요하므로 전방 선언만으로는 부족합니다.

      • 객체의 크기가 필요하지만 컴파일 시간에 결정되어야 한다.
  1. 포인터 멤버 사용
  • 대신 CLevel* m_pCurrentLevel;과 같이 포인터를 멤버로 사용합니다.

    • 포인터를 사용하면 클래스의 전체 정의가 필요하지 않으며, 전방 선언만으로 충분합니다.

    • 포인터는 고정된 크기를 가지므로 클래스의 전체 크기를 알 필요가 없습니다.

  1. .cpp 파일에서의 구체적 사용
  • CLevelMgr 클래스의 실제 구현은 .cpp 파일에서 이루어집니다.

    • 이 파일에서는 #include "CLevel.h"를 사용하여 CLevel 클래스의 전체 정의를 포함시킬 수 있습니다.

    -.cpp 파일은 헤더 파일과 달리 다른 파일에 포함되지 않으므로, 여기서 CLevel 클래스를 구체적으로 사용하는 데 문제가 없습니다.

  1. 전방 선언의 이점
  • 전방 선언을 사용하면 컴파일 시간이 단축됩니다.

    • 이는 헤더 파일이 다른 헤더 파일을 불필요하게 포함하지 않기 때문에 발생하는 이점입니다.

      • 종속성이 줄어들고, 전체적인 빌드 속도가 향상됩니다.
  • 헤더끼리 참조할때는 최대한 서로가 서로간에 참조를 방지하면서 실제 사용하는 .cpp에서는 링킹 되어잇는 것을 제대로 사용하기 위해서 구체적인 레벨의 헤더를 인클루두 한다.

  • 왠만하면 타입을 정확하게 알필요가 없다면 전방선언을 통해서 넘어간다.

CLevelMgr 클래스의 맴버

// CLevelMgr.h 의 CLevelMgr 클래스 내부
private:
	// 1
	CLevel* m_pCurrentLevel;
  • 1번처럼 현재 CLevel 포인터를 들고있을것이다.

    • 레벨은 광장히 많아질것이기 떄문에 현재 지칭을 해줘야함.

enum /*class*/LEVEL_TYPE
{
	// 1
	LOGO_START,
    // 2
	EDITOR,
	// 3
	STAGE_01,
	STAGE_02,
	STAGE_03,
	// 4
	BOSS_01,
	// 5
	END,
};
  • 다른 레벨들의 후보들도 미리 들고있기 위한 열거형에서 LEVEL_TYPE을 만들었다.

  • 1번은 로고와 스타트를 하나의 레벨로 합친것.

    • 게임 로고가 뜨면서 스타트 화면이 뜨는 것까지 하나의 레벨로 취급할것.
  • 2번은 에디터 기능

    • 게임에 필요한 각종 기능을 주는 레벨.

    • 슈퍼마리오 메이커, 스타크래프트 유즈맵 등등

  • 3번은 게임이 직접 실행되는 스테이지 관리 4번은 보스 레벨 관리.

    • 다양한 스테이지를 별개의 레벨로 관리한다.
  • 5번 END!

    • 문제가 있다.
enum /*class*/ PLAYER_STATE
{
	IDLE,
	RUN,
	MOVE,
	DASH,
	ATTACK,
	HIT,
	DEAD,

	END,
};
  • 컴파일러는 똑같은 END를 주면 누구 END인지 애매해진다.

    • LEVEL_TYPEPLAYER_STATE의 열거형이 END가 겹쳐서 이름을 항상 END를 다르게 줘야만 한다.

    • LEVEL_TYPEPLAYER_STATE의 END값이 6과 7이라 달라져 버린다.

  • 맨날 이름을 END_LEVEL과 END_PLAYER_STATE 이래야 하는데 불편해지니까 enum class를 써서 강제성을 준다.

    • 플레이어 타입 END와 레벨 스테이트 END과 공존이 가능해진다.
enum class PEN_TYPE
{
	PEN_RED,
	PEN_GREEN,
	PEN_BLUE,

	//PEN_END,
	END,
};
enum class BRUSH_TYPE
{
	BRUSH_RED,
	BRUSH_GREEN,
	BRUSH_BLUE,

	BRUSH_HOLLOW,
	BRUSH_BLACK,

	//BRUSH_END,
	END,
};
  • 펜과 브러쉬도 이제 enum class를 사용해서 PEN_ENDBRUSH_END를 END로 바꾼다.
// CEngine.h의 CEngine클래스 내부.
private:

	// 1
	//HPEN		m_arrPen[PEN_END];
	// 2
	//HPEN		m_arrPen[PEN_TYPE::END];
    // 3
	HPEN		m_arrPen[(UINT)PEN_TYPE::END];
    // 4
	//HBRUSH		m_arrBrush[BRUSH_END];
	// 5
	//HBRUSH		m_arrBrush[BRUSH_TYPE::END];
	// 6
	// 이넘 클래스로 이넘 값을 사용할것이다. 불편하고 무제가 생겨도 캐스팅 다시듣기.
	HBRUSH		m_arrBrush[(UINT)BRUSH_TYPE::END];
  • enum class를 사용하면 1,2,4,5은 문제가 생긴다.

    • 1번과 4번을 그냥 END로 그냥 바꿔주고 2번과 5번에 있는 PEN_TYPE::BRUSH_TYPE::을 안적어 주면 컴파일러가 누구의 END인지 몰라서 enum class를 사용하면 귀찮지만 적어줘야한다.

    • enum class는 문법적으로 enum값을 사용할떄 무조건 본인이 어디 enum쪽 열거형인지 강제 된다.

    • enum class는 enum은 정수형이였지만 enum class는 컴파일러가 완전히 구별을 해버린다.

      • 정수의 의미이기 하지만 문법적으로 정수로 쓰고싶다면 3번 6번처럼 강제로 캐스팅 해야한다.

      • 자연스럽게 숫자로 인식해주던 enum과 다르게 enum class는 문법적으로 더 엄격해진다.

      • 배열의 갯수니까 상수가 들어와야하니 enum class를 강제 캐스팅해해서 나의 의도를 보여줘야 한다.

// CEngine.h의 CEngine클래스 내부.
public:
	HPEN GetPen(PEN_TYPE _type) { return m_arrPen[(UINT)_type]; }
	HBRUSH GetBrush(BRUSH_TYPE _type) { return m_arrBrush[(UINT)_type]; }
  • GetPen과 GetBrush도 원하는 타입을 알려주면 바로 인덱스에 접근했었지만 이제는 숫자로 강제 캐스팅해서 넣어줘야 한다.
// CEngine.cpp 내부
void CEngine::CreateDefaultGDIObject()
{
	m_hDC = GetDC(m_hMainWnd);
	// 자주 사용할 펜 생성
	m_arrPen[(UINT)PEN_TYPE::PEN_RED] = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
	m_arrPen[(UINT)PEN_TYPE::PEN_GREEN] = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
	m_arrPen[(UINT)PEN_TYPE::PEN_BLUE] = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));

	// 자주 사용할 브러쉬 생성
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_RED] = CreateSolidBrush(RGB(255, 0, 0));
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_GREEN] = CreateSolidBrush(RGB(0, 255, 0));;
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_BLUE] = CreateSolidBrush(RGB(0, 0, 255));;
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_HOLLOW] = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_BLACK] = (HBRUSH)GetStockObject(BLACK_BRUSH);
}
  • 자주 사용할 펜 브러쉬도 이제 강제 캐스팅을 해줘야 한다.
// define.h 파일 
#define USE_PEN(DC, TYPE) CSelectObj SelectPen(DC, CEngine::GetInst()->GetPen(PEN_TYPE::TYPE))
#define USE_BRUSH(DC, TYPE) CSelectObj SelectBrush(DC, CEngine::GetInst()->GetBrush(BRUSH_TYPE::TYPE))
  • 매크로 부분도 강제 캐스팅 버전으로 수정해준다.
// CEngine.cpp의 소멸자 부분
	for (int i = 0; i < (UINT)PEN_TYPE::END; ++i)
	{
		DeleteObject(m_arrPen[i]);
	}
  • for문에 조건체크문도 바꿔준다.
// CLevelMgr.h의 CLevelMgr클래스 내부
class CLevel;

class CLevelMgr
{
	// 4
	SINGLE(CLevelMgr)
private:
	// 1
	CLevel* m_arrLevel[(UINT)LEVEL_TYPE::END];
    // 2
	CLevel* m_pCurrentLevel;

public:
	// 3
	void init();
	void progress();

public:



};
  • 1번처럼 레벨 타입이 여러개 생길수 있으니 레벨매니저에서는 레벨 포인터로 모든 레벨을 관리할것이고 갯수는 enum class값에 몇개를 선언할지에 따라서 달라질것이다.

  • 2번은 저 1번의 레벨중에서도 누가 현재 레벨인지 가리키는 레벨 포인터 맴버다.

  • 3번에 초기화 함수와 진행함수를 선언.

  • 4번에 싱글톤 매크로에 이미 생성자와 소멸자가 있어서 CLevelMgr클래스에서 선언을 할필요가 없다.

    • 주의.
#include "pch.h"
#include "CLevelMgr.h"

#include "CLevel.h"

// 1
CLevelMgr::CLevelMgr()
	: m_arrLevel{}
	, m_pCurrentLevel(nullptr)
{

}
// 2
CLevelMgr::~CLevelMgr()
{

}


// 3
void CLevelMgr::init()
{
}

// 4
void CLevelMgr::progress()
{
	// 5
	if (nullptr == m_pCurrentLevel)
		return;
    // 6
    m_pCurrentLevel->tick();
	m_pCurrentLevel->finaltick();
	m_pCurrentLevel->render();
}
  • 1번에 기본생성자 구현과 2번은 소멸자

  • 3번은 매니저클래스 초기화 구현부분

  • 4번은 진행함수 레벨 구성 구현부분

    • 5번은 현재 레벨을 nullptr로 만든다.

      • 현재 레벨이 지정이 안되어 있다.
    • 6번 현재 시점이 있다면 레벨안에 있는 물체들이 매프레임마다 할일을 정의한다.

      • tick->finaltick->render순으로 순차적으로 시점들을 호출할것이다.
// CEngine.cpp 파일 내부
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);

	CreateDefaultGDIObject();
	// 1
	CLevelMgr::GetInst()->init();
    
	return S_OK;
}
  • 엔진부터 초기화해주고 1번으로와서 레벨 매니저를 초기화 해준다

    // main.cpp의 while문 안
            else
           {
               CEngine::GetInst()->progress();
           }
  • progress는 메 프레임마다 들어온다

    • 프레임이란 메인함수를 한번 수행하는것 메세지 루프없을떄 한번 수행되서 나오면 1프레임이라고 한다.

    • 한번 else코드까지 시행하고 나오면 엔진이 해야할 일을 완벽하게 전체 수행과정을하면 1프레임이다.

    • 레벨을 플레이하는 과정이 1프레임이다.

  • 전체흐름

    • 메인함수에서 항상 엔진에 progress를 주면 그 안에서 하고 있는것은 레벨 매니저한테 프로세스를 호출한다.
    • 레벨 매니전는 이번 프레임에 자기가 현재 레벨이라고 생각하는것에 틱->파이널->렌더를 순차적으로 시행해주는게 1프레임이다.

CLevel_Stage01 클래스

// CLevel_Stage01.h 파일
#pragma once
#include "CLevel.h"
class CLevel_Stage01 :
    public CLevel
{
};
  • CLevel_Stage01선언부분
    • 스테이지1은 레벨 클래스를 상속받을것이다.
// CLevel_Stage01.cpp 파일
#include "pch.h"
#include "CLevel_Stage01.h"
  • CLevel_Stage01 클래스의 .cpp파일

CLevel 클래스의 시점 함수들 가상함수로 만들기

// CLevel.h의 CLevel 클래스 내부
public:
    //void begin(); 	// 레벨이 시작될 때
    //void tick();    	// 매 프레임마다 호출
    //void finaltick(); // 매 프레임마다 호출
    //void render();  	// 매프레임마다 호출

    virtual void begin(); 		// 레벨이 시작될 때
    virtual void tick();    	// 매 프레임마다 호출
    virtual void finaltick(); 	// 매 프레임마다 호출
    virtual void render();  	// 매프레임마다 호출
  • CLevel클래스는 이제부터 나올 레벨들의 부모클래스이기 때문에 시점함수들을 가상함수로 선언했다.

    • 레벨쪽에서도 다형성이 발생.

      • 레벨 매니저가 레벨 포인터로 관리하지만 안에 들어있는 레벨은 레벨 객체가 아니라 레벨클래스를 상속받아가서 파생된 어는 레벨중에 하나지만 부모인 레벨 포인터로 다 가리킬수 있다.
    • 자식들이 재정의 했다면 거기에 비긴, 틱, 파이널틱, 렌더가 설정한것에 맞게 구동될것이다.

    • 부모쪽에서도 어느정도 구현을 해놓을건대 부모쪽에 구현해 놓은게 마음에 안들면 재정의하라.

      • 마음에 들면 그대로 쓰고 마음에 안들면 재정의하고 등등
// CLevel.h의 CLevel 클래스 내부
public:
    void AddObject(CObj* _Obj) { m_vecObj.push_back(_Obj); }
  • 레벨안에 물체를 넣어주는 함수다.
// CLevelMgr.h파일 CLevelMgr클래스안 
private:
	CLevel* m_arrLevel[(UINT)LEVEL_TYPE::END];
	CLevel* m_pCurrentLevel;
  • 배열로 치면 LEVEL_TYPE을 많이 만들어 뒀다.

    • 타입별로 실제 레벨이 존재할거같아서 enum값을 설정해둔것인데 그중에 스테이지01 레벨을 클래스로 만들었다.
// CLevelMgr.cpp 파일 내부
void CLevelMgr::init()
{
    // 1
	m_arrLevel[(UINT)LEVEL_TYPE::STAGE_01] = new CLevel_Stage01;
    // 2
	m_pCurrentLevel = m_arrLevel[(UINT)LEVEL_TYPE::STAGE_01];
}
  • 레벨을 상속받아간 스테이지01이라는 레벨이 있고 레벨 매니저에서는 스테이지01레벨을 만들어 둔다.

  • 1번은 모든 레벨을 생성해주는 것이다.

    • 모든 레벨 생성이라고 했지만 생성된 레벨은 1개다.
  • 2번 지금은 구현해둔게 1번 스테이지밖에 없어서 현재 레벨을 스테이지 1로 만든다.

    • 레벨 매니저가 초기화될때마다 현재 레벨이 존재하니까 매 프레임 들어올때마다 스테이지 01의이 아직 오버라이딩 안했으므로 부모쪽에 레벨 틱->파이널틱->렌더가 호출될것이다.
// CLevel_Stage01.h 파일 
#pragma once
#include "CLevel.h"
class CLevel_Stage01 :
    public CLevel
{
private:

public:
	// 1
    CLevel_Stage01();
    ~CLevel_Stage01();

};
  • 방금 상속 받은거라 자기쪽에 새롭게 추가한 것은 없으므로 부모(레벨)의 기능을 갖다 쓸것이다.
  • 1번처럼 생성자 소멸자 선언
#include "pch.h"
#include "CLevel_Stage01.h"

CLevel_Stage01::CLevel_Stage01()
{
}

CLevel_Stage01::~CLevel_Stage01()
{
}
  • 생성자와 소멸자만 해둠.

  • 상속을 받기는 햇지만 아직 대부분의 기능을 부모쪽걸 쓸거라서 나중에 레벨들을 상속을 해서 내리니까 레벨마다 자기만의 고유한 특징이 있을수도 있어서 상속을 내리긴했다.

    • 부모의 기능을 오버라이딩하거나 변경할것은 아직 없다.
void CLevel::begin()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->begin();
	}
}

void CLevel::tick()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->tick();
	}
}

void CLevel::finaltick()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->finaltick();
	}
}

void CLevel::render()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->render();
	}
}
  • 부모쪽(레벨)의 .cpp를 채워나갈것이다 스테이지01이 호출되면 부모쪽을 오버라이딩이 안되서 부모쪽 함수로 온다.
  • 레벨하는일
    • 자기가 보유한 물체들한테 해당 시점을 똑같이 호출한다.
  • 레벨이 만들어졌지만 텅 비어있다
// CLevel.h
class CLevel :
    public CEntity
{
private:
	// 1
    vector<CObj*>   m_vecObj;

public:
    virtual void begin(); // 레벨이 시작될 때
    virtual void tick();    // 매 프레임마다 호출
    virtual void finaltick(); // 매 프레임마다 호출
    virtual void render();  // 매프레임마다 호출

public:
    void AddObject(CObj* _Obj) { m_vecObj.push_back(_Obj); }

public:
    CLevel();
    ~CLevel();
};
  • 1번의 벡터 오브젝트에는 아무것도 들어있지 않다.

    • 뭐라도 넣자.
// CLevelMgr.cpp 내부 
void CLevelMgr::init()
{
	// 모든 레벨 생성
	m_arrLevel[(UINT)LEVEL_TYPE::STAGE_01] = new CLevel_Stage01;
	// 현재 레벨 지정
	m_pCurrentLevel = m_arrLevel[(UINT)LEVEL_TYPE::STAGE_01];
    // 1
	// 레벨에 물체 추가하기
	m_pCurrentLevel->AddObject(pObject);
}
  • 1번은 현재 레벨에 물체를 추가한다.
    • 오브젝트를 생성해야 하는데 아직 오브젝트를 설계 다 안했다.

오브젝트 클래스의 render함수 구현

// CObj.cpp의 내부 
void CObj::render()
{
	m_Pos; 		// 1
	m_Scale;	// 2
    
    // 3
    Rectangle()
}
  • 레벨안에서 오브젝트들이 뛰어 놀려면 어떤 오브젝트가 있어야 할까?

    • 1번위치

    • 2번 크기(힘)

  • 3번처럼 Rectangle을 사용해서 한동안 오브젝트는 네모로 인식

    • 물체 == 네모

    • Rectangle함수의 인자 정보

      • 첫번쨰 인자로 DC는 원도우에 매칭된 비트맵이여야한다.
      // CEngine.h의 CEngine클래스 내부
      private:
      HWND		m_hMainWnd;		// 메인 윈도우 핸들
      POINT		m_Resoulution;	// 메인 윈도우 해상도
      // 1
      HDC			m_hDC;			// 메인 윈도우 DC
      • 1번의 메인 원도우의 첫번쨰 인자를 DC값을 넣어줘여한다.
      // CEngine.h의 CEngine클래스 내부
      public:
      // 1
      HDC GetMainDC() { return m_hDC; }
      • CEngine클래스에 맴버로 있으니 가져가는 1번처럼 가져가는 함수 만들면 된다.
      	// CObj.cpp의 내부 
      	void CObj::render()
      	{	
          	// 4
          	HDC dc = CEngine::GetInst()->GetMainDC();
          
      		m_Pos; 		// 1
      		m_Scale;	// 2
      
      		// 3
      		Rectangle(dc,)
      	}
      • 4번처럼 DC값을 받아와서 쓰면 된다.
      • 3번에 첫번쨰 인자로 dc받아 왔다.
	// 1
	m_Scale.x;
    // 2
	m_Scale.y;
    // 3
	m_Pos.x;
    // 4
	m_Pos.y;
  • m_Pos.x, m_Pos.y

    • 사각형의 중심 위치를 나타냅니다.
  • m_Scale.x, m_Scale.y

    • 사각형의 가로와 세로 크기를 결정하는 값입니다.
  • 사각형의 2~5번쨰 인자들은 저것들을 조합해서 쓰면 된다.

    • 1번은 가로 길이 2번은 세로 길이

    • 3번4번은 원도우상에서 x축 좌표 y축 좌표

  • Rectangle의 규칙에 따르면 좌상단을 알려줘야하니까

    • m_Pos.x - m_Scale.x * 0.5f

      • 포지션x에서 스케일의 절반을 빼진 좌표가 내 원도우 좌상단 x좌표 된다.
    • m_Pos.y - m_Scale.y * 0.5f

      • 포지션y에서 스케일의 절반을 빼진 좌표가 내 원도우 좌상단 y좌표 된다.
    • m_Pos.x + m_Scale.x * 0.5f

      • 포지션x에서 스케일의 절반을 더하면 좌표가 내 원도우 우하단 x좌표 된다.
    • m_Pos.y + m_Scale.y * 0.5f

      • 포지션y에서 스케일의 절반을 더하면 좌표가 내 원도우 우하단 y좌표 된다.
	// 1
	CObj* pObject = new CObj;
    // 2
	pObject->SetPos(640, 384);
    // 3
	pObject->SetScale(100,100);
	// 4
	m_pCurrentLevel->AddObject(pObject);
  • 1번은 오브젝트 힙메모리 생성하는 코드

  • 2번은 위치값 설정

    • 중앙값(640, 384)
    • 중앙 픽셀.
  • 3번은 크기를 설정.

    • 100,100으로 설정
    • 가로세로 몇 픽셀짜리가 되냐?
  • 4번은 현재 레벨에 넣어준다.

// CEngine.cpp의 내부.
void CEngine::CreateDefaultGDIObject()
{
	// 1
	m_hDC = GetDC(m_hMainWnd);
    // 2
    m_hDC = ::GetDC(m_hMainWnd);
	
    // 펜 브러쉬 생성 코드 생략
}

  • 오류가 발생하고있다.

    • 이름이 겹치는 맴버함수가 엔진 클래스에 있어서 전역함수 GetDC를 지칭할려면 범위 지정자를 사용해야한다.

  • 1번은 GetDC라고 하면 맴버함수니까 엔진내부의 GetDC를 쓰는지 안다.

  • 2번처럼 :: 범위지정자를 사용하여 엔진말고 외부에서 구현된 GetDC호출할것이다.
// CEngine.h의 CEngine클래스 내부
public:
	HDC GetDC() { return m_hDC; }
	HDC GetMainDC() { return m_hDC; }
  • ::범위 지정자를 쓰기 싫어서 엔진의 맴버함수 이름을 바꿧다.
    • 이게 젤 좋은 방법.
void CLevelMgr::progress()
{
	if (nullptr == m_pCurrentLevel)
		return;
	// 1
	m_pCurrentLevel->tick();
	m_pCurrentLevel->finaltick();
	m_pCurrentLevel->render();
}
  • 레벨안에 있는 물체들을 매프레임마다 할일을 정의함.

    • tick함수에 방향키가 눌리면 이동하는 걸 구현을 했다면

    • 매프레임마다 방향키가 눌린 방향으로 이동을 할것이다.

    • 이동되고 렌더링되면 x가 증가해서 이동된 위치의 사각형이 그려진다(플레이어가 이동)

tick(), finaltick()함수 차이

  • tick

    • tick는 매 프레임 마다 해야할 작업을 구현

      • 오브젝트 별로 다 할일이 다를것이다.

      • 오브젝트에서 구현을 했지만 잘못된것이다.

        • 원래라면 오브젝트를 상속받는 플레이어가 tick을 오버라이딩해서 플레이어 할일을 구현 해야한다.

          • 키 입력에 따른 이동 점프, 공격 등등

          • 각자 물체가

  • finaltick

    • finaltick은 tick이 모두 끝난 시점에서 모든 오브젝트들을 tick을 호출하면 이번 프레임때 할일이 끝나 있을 것이고 그떄 finaltick을 다 호출해주면 tick에서 발생한 일을 마무리 해주는 단계다.
  • 구현을 tick에다 하고 finaltick에는 수행된 것들을 연산을 마무리 해주는 단계다.

방향키가 눌리면 이동 구현.

GetAsyncKeyState함수

https://m.blog.naver.com/power2845/50143021565

  • 함수 설명 블로그
// CObj.cpp 의 내부
void CObj::tick()
{
    // 1
	GetAsyncKeyState('K');

}
  • 특정키가 현시점에 눌려있는지 안눌려있는지 알려주는 함수

    • 입력으로 들어온게 무슨키인지 물어보는것(이전 포함)

    • 영문값이 들어오면 아스키 코드값으로 해석

      • 모든 문자는 정수다. K라는 아스키 코드값이 함수에 들어가면 K가 눌렸는지 확인함
    • 특수키는 정의를 해둠.

      • VK_LEFT
      • 매크로로 정의해둠.
      • 37을 넣으면 왼쪽키가 눌린지 안눌린지 체크한다.
  • 각 키마다 고유한 숫자를 매핑해서 강제로 정해뒀다.

    • 알맞은 값을 넣어서 눌렀는지 안눌렀는지 확인한다.
  • 숫자를 물어보고 싶다면 '1'은 상단숫자는 아스키 코드로 물어보고 넘버판에 있는 숫자들은

    • 매크로로 물어볼수 있다.
  • GetAsyncKeyState함수 규칙

    • 반환값을 true와 false가 아닌 사진의 형식으로 반환 해준다.
	// 1
	if (0x8001 & GetAsyncKeyState(VK_LEFT));
	{
		m_Pos.x -= 1;
	}
    // 2
    if (0x8001 & GetAsyncKeyState(VK_RIGHT));
	{
		m_Pos.x += 1;
	}
  • 1번은 왼쪽키가 눌린적이 있으면(눌려있다면) 왼쪽으로 1픽셀 이동.

  • 0x8001이랑 비트 논리 연산을 해서 하나라도 있다면 이동 시킨다.

    • 하나라도 있다면 이전에 눌렀거나 지금 눌르고 있는것이기 떄문이다.

    • 0x8001 분석 0x8000은 최상단 비트가 1, 0x0001은 최하단 비트가 1

    • 0x0001은 키 씹힘 현상을 막아주기 위해서 사용한다.

  • 2번은 오른쪽 키가 눌린적이 있으면(눌려있다면) 오른쪽으로 1픽셀 이동.

화면이 채워지는 이유

  • 1픽셀씩 계속 이동하다보면 검은색으로 칠해진다.

    • 메인함수의 else코드부분(게임 로직)이 얼마나 빨리 작동하는지 보여준다.(오른쪽키 잠깐 왼쪽키 잠깐 눌렀는데)
      • 결론 : 컴퓨터는 매우 빠르다.
  • 문제 발생

    • 캐릭터의 이동을 컴퓨터가 else쪽에서 게임의 프레임이 도는데 도는 성능이 너무 빨르니까 게임 오브젝트의 이동한다는 구현을 1픽셀 보다 적게 이동을 하지 못한다.

      • 최소단위가 1픽셀
  • 문제 해결

    • 이제 포지션의 자료형을 우리가 만든 자료형으로 바꿔줄것이다.

    • struct.h를 만들것이다.

// struct.h 파일
struct Vec2
{
public:
	float x;
	float y;

public:
	Vec2()
		: x(0.f)
		, y(0.f)
	{}
	Vec2(float _x, float _y)
		: x(_x)
		, y(_y)
	{}
	~Vec2()
	{}
};
  • x,y로 표현되는 vec2구조체 로 표현할것
    • float x,y 맴버로 설정
  • 기본 생성자와 소멸자 구현했고 입력인자를 받는 초기화 생성자 도 만들었다.
// pch.h
#include "struct.h"
  • 미리 컴파일 헤더에 추가.
#pragma once
#include "CEntity.h"


class CObj :
    public CEntity
{
private:
    Vec2   m_Pos; // 위치
    Vec2   m_Scale; // 크기

public:
	// 1
    void SetPos(Vec2 _Pos) { m_Pos = _Pos; }
    void SetScale(Vec2 _Scale) { m_Scale = _Scale; }
	// 2
    void SetPos(float _x, float _y) { m_Pos.x = _x; m_Pos.y = _y; }
    void SetScale(float _width, float _height) { m_Scale.x = _width; m_Scale.y = _height; }

    Vec2 GetPos() { return m_Pos; }
    Vec2 GetScale() { return m_Scale; }

public:
    virtual void begin();

    virtual void tick(); //  오브젝트가 매 프레임마다 해야할 작업을 구현
    virtual void finaltick();
    virtual void render();

public:
    CObj();
    ~CObj();
};
  • CObj.h의 CObj클래스를 POINT구조체로 되있던걸 Vec2구조체로 변경 했다.

  • 1번은 처음부터 Vec2구조체 넣는것 인자 하나를 받는 버전이다.

  • 2번은 하나하나 float인자 2개를 받아서 쓰는 SetPos,SetScale다.

float형으로 바꾸면 뭐가 좋음?

  • 실수타입으로 바꾸면 뭐가 달라질가.

    • 물리적으로 표현되는 단위는 픽셀이다(정수)
  • 근데 오른쪽 키를 누르고 있다면 플레이어를 단위로 1로 가는게 아니라 실수 다보니 0.01만큼 움직일수 있다.

    • 1보다 100배 느려지게 보인다.

    • 0.01움직인다고해서 1픽셀 움직이지는 않는다.

      • 0.01이 연산이 100번 쌓여서 정수가 되는 순간 1픽셀을 움직이게 된다.
  • 사각형도 정수형을 요구한다

    • 그렇지만 float형이 2.6만큼 이동하라고 하면 float이 int로 캐스팅되서 소수점을 버리고 102.6가 아니라 102로 움직인다.

      • 대신에 미세한 단위의 내부적인 데이터 상으로라도 좌표 단위를 표현할수 있다.
  • 사각형이 포지션(위치)가 해상도를 한참 넘어가버리면 어떻게 넘어갈것이냐?

    • 4000에 5000에 그리라고한다면 현재 비트맵(1280*768)을 넘어가버리기 떄문에 그쪽에 칠할 메모리가 없기 떄문에 아무 일도 안일어나고 돌아온다.(무시된다.)

시간 동기화

  • 소수점으로 아무리 100배를 느리게 해도 컴퓨터의 성능에 따라 두배 세배 빠르게 이동할수 있다.

    • 컴퓨터의 성능차이떄문에 게임에 영향을 주면 안된다.

      • 같은 게임을 실행하는데 컴퓨터의 성능에 따라 케릭터의 이동이 달라지면 안된다.

      • 겔로그(미사일 뽕뽕 쏘는 게임)

        • 몬스터를 죽일떄마다 렌더링할게 줄어서 더 빠르게 렌더링이 되서 사운드와 게임이 더 빨라졌었다.(처음에 시간이 지날수록 난이도 올라가는건줄 알았는데 걍 성능때문에 그랫던것.)
  • 한번에 tick이 발생할떄 고정 상수를 쓰면 안된고 시간 동기화를 사용한다.

  • 시간동기화

    • 같은 pc의 상황에서도 몬스터 수가 많다면 프레임이 떨어질수가 있고 몬스터 수가 적으면 프레임이 빨라 질수도 있다. 환경자체가 동일한 상황에서도 물체들의 상황에 따라서도 프레임은 요동칠수 있기 때문에 그런 상황에 영향을 안받기 위해서 현실 시간과 움직임을 동기화 해야한다.

CTimeMgr클래스

// CTimeMgr.h
#pragma once
class CTimeMgr
{
	SINGLE(CTimeMgr)
private:

public:
};
// CTimeMgr.cpp
#include "pch.h"
#include "CTimeMgr.h"
  • 현실 시간과 동기화를 위해서 타임 매니저를 추가했당.(내일 구현)

화면 색깔이 돌아오지 않고 검은색이 계속 남는 이유?

  • 픽셀 라인이 비트맵에 넣어버리면서 지나온길을 펜때문에

  • 사각형이 누적으로 계속 그려지니까 픽셀 라인(사각형테두리)이 계속 비트맵에 검은색으로 라인이 채워지기 때문에 검은색으로 채워져 온것이다.

    • 내부를 브러쉬로 채우기로 햇기 떄문에 안에가 하얀색으로 칠해지는것이지 지나온 길은 펜(테두리)가 검은색으로 칠하면서 온다.
  • 비슷한 환경 상에서 이동을 하고 나면 이전부분이 원상복구가 되야한다.

    • 지우고 렌더링하고 반복해야 한다.

렌더링

  1. 2D 및 3D 렌더링의 유사성

    • 렌더링 과정 자체는 2D와 3D 간에 큰 차이가 없으며, 기본 원리는 동일합니다.
      • 주된 차이는 표현하려는 차원의 수와 관련된 복잡성에 있습니다.
  2. 스톱 모션 애니메이션

    • 스톱 모션은 프레임별로 물체를 조금씩 움직이고 사진을 찍어서 연속된 이미지를 만드는 과정입니다.

      • 예를 들어, 24프레임 애니메이션을 만들기 위해 1초 동안의 애니메이션을 만들기 위해 24번의 사진 촬영과 물체 조정이 필요합니다.

      • 이 과정은 매우 시간이 많이 걸리는 수작업이다.

  3. 게임 렌더링과 스톱 모션의 유사성

    • 게임 렌더링은 스톱 모션 애니메이션과 유사한 방식으로 작동합니다.

    • 각각의 프레임에서 게임 엔진은 게임의 상태를 업데이트하고 그 결과를 화면에 렌더링합니다.

      • "틱(tick)"이라고 불리는 업데이트 사이클을 통해 반복됩니다.
  4. 컴퓨터의 역할

    • 스톱 모션과 달리 게임에서는 컴퓨터가 이러한 렌더링 과정을 매우 빠르게 처리합니다. 이로 인해 게임은 연속적이고 자연스럽게 보이는 애니메이션을 생성할 수 있습니다.
  5. GPU의 역할

    • 게임 렌더링 과정에서 GPU(Graphics Processing Unit)는 매우 중요한 역할을 합니다.

    • GPU는 그래픽 연산을 효율적으로 처리하여 부드러운 애니메이션과 복잡한 시각적 효과를 실시간으로 렌더링하는 데 필수적입니다.

  6. 현실 시간과의 동기화

    • 게임 렌더링에서는 현실 시간과의 동기화가 중요합니다. 이는 게임 내부의 시간과 현실 세계의 시간이 일치하도록 보장하는 것을 의미합니다.
  7. 렌더링과 클리어링

    • 렌더링 과정에서는 화면을 깔끔하게 지우고 새로운 프레임을 그려야 합니다. 이전 프레임이 제대로 지워지지 않으면 잔상이 남을 수 있습니다. 따라서 각 프레임을 그리기 전에 화면을 클리어하는 것이 중요합니다.

바뀌고 추가된 강의 코드

pch.h

// 위  생략
#include "enum.h"
// 열거형 헤더 아래 추가됨.
#include "struct.h"

define.h

// 위 생략
// enum class로 열거형 수정해서 추가됨.
#define USE_PEN(DC, TYPE) CSelectObj SelectPen(DC, CEngine::GetInst()->GetPen(PEN_TYPE::TYPE))
#define USE_BRUSH(DC, TYPE) CSelectObj SelectBrush(DC, CEngine::GetInst()->GetBrush(BRUSH_TYPE::TYPE))

enum.h

#pragma once


enum class PEN_TYPE
{
	PEN_RED,
	PEN_GREEN,
	PEN_BLUE,	

	END,
};

enum class BRUSH_TYPE
{
	BRUSH_RED,
	BRUSH_GREEN,
	BRUSH_BLUE,

	BRUSH_HOLLOW,
	BRUSH_BLACK,

	END,
};

enum class LEVEL_TYPE
{
	LOGO_START,
	EDITOR,

	STAGE_01,
	STAGE_02,
	STAGE_03,

	BOSS_01,

	END,
};

enum class PLAYER_STATE
{
	IDLE,
	RUN,
	MOVE,
	DASH,
	ATTACK,
	HIT,
	DEAD,

	END,
};

struct.h

#pragma once

struct Vec2
{
public:
	float x;
	float y;

public:
	Vec2()
		: x(0.f)
		, y(0.f)
	{}

	Vec2(float _x, float _y)
		: x(_x)
		, y(_y)
	{}

	~Vec2()
	{

	}
};

CEngine.h

#pragma once

// 게임 최고 관리자
class CEngine
{
	SINGLE(CEngine)
private:
	HWND		m_hMainWnd;		// 메인 윈도우 핸들
	POINT		m_Resoulution;	// 메인 윈도우 해상도
	HDC			m_hDC;			// 메인 윈도우 DC

	HPEN		m_arrPen[(UINT)PEN_TYPE::END];
	HBRUSH		m_arrBrush[(UINT)BRUSH_TYPE::END];

public:
	int init(HWND _hWnd, POINT _Resolution);
	void progress();

private:
	void CreateDefaultGDIObject();


public:
	HDC GetMainDC() { return m_hDC; }

	HPEN GetPen(PEN_TYPE _type) { return m_arrPen[(UINT)_type]; }
	HBRUSH GetBrush(BRUSH_TYPE _type) { return m_arrBrush[(UINT)_type]; }
};

CEngine.cpp

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

#include "CLevelMgr.h"

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

}

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

	// Pen 삭제
	for (int i = 0; i < (UINT)PEN_TYPE::END; ++i)
	{
		DeleteObject(m_arrPen[i]);
	}

	// Brush 삭제
	for (int i = 0; i < 3; ++i)
	{
		DeleteObject(m_arrBrush[i]);
	}
}

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);

	// DC 및 펜, 브러쉬 생성
	CreateDefaultGDIObject();	

	// Manager 초기화
	CLevelMgr::GetInst()->init();

	return S_OK;
}

void CEngine::progress()
{	
	CLevelMgr::GetInst()->progress();
}

void CEngine::CreateDefaultGDIObject()
{
	// DC(Device Context) 생성
	// DC 란? 렌더링과 관련
	// 비트맵에 렌더링하기 위해 필요한 필수 정보 집합체
	m_hDC = ::GetDC(m_hMainWnd);
	// DC 보유 정보
	// GetDC 로 생성되는 DC 의 정보
	// 목적지 비트맵 - 입력 윈도우의 비트맵
	// 펜 - BlackPen(Default)
	// 브러쉬 - White Brush(Default)


	// 자주 사용할 펜 생성
	m_arrPen[(UINT)PEN_TYPE::PEN_RED] = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
	m_arrPen[(UINT)PEN_TYPE::PEN_GREEN] = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
	m_arrPen[(UINT)PEN_TYPE::PEN_BLUE] = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));

	// 자주 사용할 브러쉬 생성
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_RED] = CreateSolidBrush(RGB(255, 0, 0));
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_GREEN] = CreateSolidBrush(RGB(0, 255, 0));;
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_BLUE] = CreateSolidBrush(RGB(0, 0, 255));;
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_HOLLOW] = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
	m_arrBrush[(UINT)BRUSH_TYPE::BRUSH_BLACK] = (HBRUSH)GetStockObject(BLACK_BRUSH);
}

CLevelMgr.h

#pragma once

class CLevel;

class CLevelMgr
{
	SINGLE(CLevelMgr)
private:
	CLevel*		m_arrLevel[(UINT)LEVEL_TYPE::END];
	CLevel*		m_pCurrentLevel;
	
public:
	void init();
	void progress();
};

CLevelMgr.cpp

#include "pch.h"
#include "CLevelMgr.h"


#include "CLevel.h"
#include "CLevel_Stage01.h"

#include "CObj.h"

CLevelMgr::CLevelMgr()
	: m_arrLevel{}
	, m_pCurrentLevel(nullptr)
{

}

CLevelMgr::~CLevelMgr()
{

}


void CLevelMgr::init()
{
	// 모든 레벨 생성
	m_arrLevel[(UINT)LEVEL_TYPE::STAGE_01] = new CLevel_Stage01;

	// 현재 레벨 지정
	m_pCurrentLevel = m_arrLevel[(UINT)LEVEL_TYPE::STAGE_01];

	// 레벨에 물체 추가하기
	CObj* pObject = new CObj;
	pObject->SetPos(640.f, 384.f);
	pObject->SetScale(100.f, 100.f);

	m_pCurrentLevel->AddObject(pObject);
}

void CLevelMgr::progress()
{
	if (nullptr == m_pCurrentLevel)
		return;

	// 레벨안에 있는 물체들이 매프레임마다 할일을 정의함
	m_pCurrentLevel->tick();

	m_pCurrentLevel->finaltick();

	m_pCurrentLevel->render();
}

CTimeMgr.h

#pragma once
class CTimeMgr
{
	SINGLE(CTimeMgr)
private:

public:
};

CTimeMgr.cpp

#include "pch.h"
#include "CTimeMgr.h"

CLevel.h

#pragma once
#include "CEntity.h"

class CObj;

class CLevel :
    public CEntity
{
private:
    vector<CObj*>   m_vecObj;

public:
    // 시점 함수
    virtual void begin(); // 레벨이 시작될 때
    virtual void tick();    // 매 프레임마다 호출
    virtual void finaltick(); // 매 프레임마다 호출
    virtual void render();  // 매프레임마다 호출

public:
    void AddObject(CObj* _Obj) { m_vecObj.push_back(_Obj); }

public:
    CLevel();
    ~CLevel();
};

CLevel.cpp

#include "pch.h"
#include "CLevel.h"

#include "CObj.h"

CLevel::CLevel()
{
}

CLevel::~CLevel()
{
}

void CLevel::begin()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->begin();
	}
}

void CLevel::tick()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->tick();
	}
}

void CLevel::finaltick()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->finaltick();
	}
}

void CLevel::render()
{
	for (size_t i = 0; i < m_vecObj.size(); ++i)
	{
		m_vecObj[i]->render();
	}
}

CLevel_Stage01.h

#pragma once
#include "CLevel.h"


class CLevel_Stage01 :
    public CLevel
{
private:

public:
    CLevel_Stage01();
    ~CLevel_Stage01();
};

CLevel_Stage01.cpp

#include "pch.h"
#include "CLevel_Stage01.h"

CLevel_Stage01::CLevel_Stage01()
{
}

CLevel_Stage01::~CLevel_Stage01()
{
}

CObj.h

#pragma once
#include "CEntity.h"


class CObj :
    public CEntity
{
private:
    Vec2   m_Pos; // 위치
    Vec2   m_Scale; // 크기

public:
    void SetPos(Vec2 _Pos) { m_Pos = _Pos; }
    void SetScale(Vec2 _Scale) { m_Scale = _Scale; }

    void SetPos(float _x, float _y) { m_Pos.x = _x; m_Pos.y = _y; }
    void SetScale(float _width, float _height) { m_Scale.x = _width; m_Scale.y = _height; }

    Vec2 GetPos() { return m_Pos; }
    Vec2 GetScale() { return m_Scale; }

public:
    virtual void begin();
    virtual void tick(); // 오브젝트가 매 프레임마다 해야할 작업을 구현
    virtual void finaltick();
    virtual void render();

public:
    CObj();
    ~CObj();
};

CObj.cpp

#include "pch.h"
#include "CObj.h"

#include "CEngine.h"

CObj::CObj()
{
}

CObj::~CObj()
{
}

void CObj::begin()
{
}

void CObj::tick()
{
	// 방향키가 눌리면 이동한다.
	
	// 현실시간 동기화
	 
	// 왼쪽키가 눌린적이 있으면(눌려있으면) 왼쪽으로 1픽셀 이동
	if (0x8001 & GetAsyncKeyState(VK_LEFT))
	{
		m_Pos.x -= 0.01f;
	}

	if (0x8001 & GetAsyncKeyState(VK_RIGHT))
	{
		m_Pos.x += 0.01f;
	}

	if (0x8001 & GetAsyncKeyState(VK_UP))
	{
		m_Pos.y -= 0.01f;
	}

	if (0x8001 & GetAsyncKeyState(VK_DOWN))
	{
		m_Pos.y += 0.01f;
	}
}

void CObj::finaltick()
{

}

void CObj::render()
{
	HDC dc = CEngine::GetInst()->GetMainDC();

	// 렌더링 과정 문제점(화면 클리어)

	Rectangle(dc, m_Pos.x - m_Scale.x * 0.5f
				, m_Pos.y - m_Scale.y * 0.5f
				, m_Pos.x + m_Scale.x * 0.5f
				, m_Pos.y + m_Scale.y * 0.5f);
}

1차 24.01.24
2차 24.01.25
3차 24.01.26

0개의 댓글

Powered by GraphCDN, the GraphQL CDN