void CEngine::progress()
{
USE_PEN(m_hDC, PEN_RED);
USE_BRUSH(m_hDC, BRUSH_BLACK);
Rectangle(m_hDC, 50, 50, 150, 150);
}
// CEngine.h CEngine클래스
private:
void CreateDefaultGDIObject();
// 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);
}
레벨안에서 물체를 관리할것이고 관리하는 물체들을 레벨이 시작되는 순간 비긴을 호출하고 평소의 매 프레임마다 레벨안에 존재하는 물체한테 알맞은 시점 함수를 호출할것이다.
게임에서 여러 스테이지가 있는것처럼 레벨도 여러개가 있을것이다.
레벨 클래스로 하나의 레벨이 이런 형태라고 생겼고 동작할것을 클래스로 만들어 둔것이다.
여러개의 레벨을 관리하는 관리자 클래스가 있어야 한다.
CEngine
클래스가 우리의 최고 관리자 라면 지금부터 만들 CLevelMgr
관리자 클래스는 레벨 클래스들만 관리하는 매니저다.
// 2
class CLevel;
class CLevelMgr
{
// 1
SINGLE(CLevelMgr)
private:
// 3
CLevel m_pCurrentLevel;
// 4
CLevel* m_pCurrentLevel;
public:
};
// CLevelMgr.cpp 파일 내부
#include "CLevel.h"
싱글톤 패턴 사용
CLevelMgr
클래스는 싱글톤 패턴을 사용하여 구현됩니다.
싱글톤 패턴은 클래스의 인스턴스가 하나만 생성되어 전역적으로 접근 가능하도록 하는 디자인 패턴입니다.
전방 선언(Forward Declaration)
CLevel
클래스에 대한 전방 선언이 사용됩니다.
전방 선언은 헤더 파일 간의 순환 종속성을 방지하는 방법으로, 클래스의 이름만 선언하고 실제 정의는 나중에 제공합니다.
포인터 대신 객체 사용의 문제점
CLevel m_pCurrentLevel;
과 같이 CLevel
객체를 직접 멤버로 사용하는 것은 문제가 있을 수 있습니다.
이 경우 CLevel
클래스의 전체 정의가 필요하므로 전방 선언만으로는 부족합니다.
대신 CLevel* m_pCurrentLevel;
과 같이 포인터를 멤버로 사용합니다.
포인터를 사용하면 클래스의 전체 정의가 필요하지 않으며, 전방 선언만으로 충분합니다.
포인터는 고정된 크기를 가지므로 클래스의 전체 크기를 알 필요가 없습니다.
CLevelMgr
클래스의 실제 구현은 .cpp
파일에서 이루어집니다.
#include "CLevel.h"
를 사용하여 CLevel
클래스의 전체 정의를 포함시킬 수 있습니다. -.cpp
파일은 헤더 파일과 달리 다른 파일에 포함되지 않으므로, 여기서 CLevel
클래스를 구체적으로 사용하는 데 문제가 없습니다.
전방 선언을 사용하면 컴파일 시간이 단축됩니다.
이는 헤더 파일이 다른 헤더 파일을 불필요하게 포함하지 않기 때문에 발생하는 이점입니다.
헤더끼리 참조할때는 최대한 서로가 서로간에 참조를 방지하면서 실제 사용하는 .cpp
에서는 링킹 되어잇는 것을 제대로 사용하기 위해서 구체적인 레벨의 헤더를 인클루두 한다.
왠만하면 타입을 정확하게 알필요가 없다면 전방선언을 통해서 넘어간다.
// 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_TYPE
과 PLAYER_STATE
의 열거형이 END가 겹쳐서 이름을 항상 END를 다르게 줘야만 한다.
LEVEL_TYPE
과 PLAYER_STATE
의 END값이 6과 7이라 달라져 버린다.
맨날 이름을 END_LEVEL과 END_PLAYER_STATE 이래야 하는데 불편해지니까 enum class
를 써서 강제성을 준다.
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,
};
PEN_END
와 BRUSH_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]; }
// 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]);
}
// 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번 현재 시점이 있다면 레벨안에 있는 물체들이 매프레임마다 할일을 정의한다.
// 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프레임이다.
전체흐름
// CLevel_Stage01.h 파일
#pragma once
#include "CLevel.h"
class CLevel_Stage01 :
public CLevel
{
};
// CLevel_Stage01.cpp 파일
#include "pch.h"
#include "CLevel_Stage01.h"
// 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을 많이 만들어 뒀다.
// 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번은 모든 레벨을 생성해주는 것이다.
2번 지금은 구현해둔게 1번 스테이지밖에 없어서 현재 레벨을 스테이지 1로 만든다.
// CLevel_Stage01.h 파일
#pragma once
#include "CLevel.h"
class CLevel_Stage01 :
public CLevel
{
private:
public:
// 1
CLevel_Stage01();
~CLevel_Stage01();
};
#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();
}
}
// 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);
}
// CObj.cpp의 내부
void CObj::render()
{
m_Pos; // 1
m_Scale; // 2
// 3
Rectangle()
}
레벨안에서 오브젝트들이 뛰어 놀려면 어떤 오브젝트가 있어야 할까?
1번위치
2번 크기(힘)
3번처럼 Rectangle
을 사용해서 한동안 오브젝트는 네모로 인식
물체 == 네모
Rectangle
함수의 인자 정보
// CEngine.h의 CEngine클래스 내부
private:
HWND m_hMainWnd; // 메인 윈도우 핸들
POINT m_Resoulution; // 메인 윈도우 해상도
// 1
HDC m_hDC; // 메인 윈도우 DC
// CEngine.h의 CEngine클래스 내부
public:
// 1
HDC GetMainDC() { return m_hDC; }
// CObj.cpp의 내부
void CObj::render()
{
// 4
HDC dc = CEngine::GetInst()->GetMainDC();
m_Pos; // 1
m_Scale; // 2
// 3
Rectangle(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
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
CObj* pObject = new CObj;
// 2
pObject->SetPos(640, 384);
// 3
pObject->SetScale(100,100);
// 4
m_pCurrentLevel->AddObject(pObject);
1번은 오브젝트 힙메모리 생성하는 코드
2번은 위치값 설정
3번은 크기를 설정.
4번은 현재 레벨에 넣어준다.
// CEngine.cpp의 내부.
void CEngine::CreateDefaultGDIObject()
{
// 1
m_hDC = GetDC(m_hMainWnd);
// 2
m_hDC = ::GetDC(m_hMainWnd);
// 펜 브러쉬 생성 코드 생략
}
오류가 발생하고있다.
::
범위지정자를 사용하여 엔진말고 외부에서 구현된 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
tick는 매 프레임 마다 해야할 작업을 구현
오브젝트 별로 다 할일이 다를것이다.
오브젝트에서 구현을 했지만 잘못된것이다.
원래라면 오브젝트를 상속받는 플레이어가 tick을 오버라이딩해서 플레이어 할일을 구현 해야한다.
키 입력에 따른 이동 점프, 공격 등등
각자 물체가
finaltick
finaltick
을 다 호출해주면 tick에서 발생한 일을 마무리 해주는 단계다.구현을 tick에다 하고 finaltick에는 수행된 것들을 연산을 마무리 해주는 단계다.
https://m.blog.naver.com/power2845/50143021565
// CObj.cpp 의 내부
void CObj::tick()
{
// 1
GetAsyncKeyState('K');
}
특정키가 현시점에 눌려있는지 안눌려있는지 알려주는 함수
입력으로 들어온게 무슨키인지 물어보는것(이전 포함)
영문값이 들어오면 아스키 코드값으로 해석
K
라는 아스키 코드값이 함수에 들어가면 K
가 눌렸는지 확인함 특수키는 정의를 해둠.
각 키마다 고유한 숫자를 매핑해서 강제로 정해뒀다.
숫자를 물어보고 싶다면 '1'은 상단숫자는 아스키 코드로 물어보고 넘버판에 있는 숫자들은
GetAsyncKeyState함수 규칙
// 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쪽에서 게임의 프레임이 도는데 도는 성능이 너무 빨르니까 게임 오브젝트의 이동한다는 구현을 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()
{}
};
vec2
구조체 로 표현할것// 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다.
실수타입으로 바꾸면 뭐가 달라질가.
근데 오른쪽 키를 누르고 있다면 플레이어를 단위로 1로 가는게 아니라 실수 다보니 0.01만큼 움직일수 있다.
1보다 100배 느려지게 보인다.
0.01움직인다고해서 1픽셀 움직이지는 않는다.
사각형도 정수형을 요구한다
그렇지만 float형이 2.6만큼 이동하라고 하면 float이 int로 캐스팅되서 소수점을 버리고 102.6가 아니라 102로 움직인다.
사각형이 포지션(위치)가 해상도를 한참 넘어가버리면 어떻게 넘어갈것이냐?
소수점으로 아무리 100배를 느리게 해도 컴퓨터의 성능에 따라 두배 세배 빠르게 이동할수 있다.
컴퓨터의 성능차이떄문에 게임에 영향을 주면 안된다.
같은 게임을 실행하는데 컴퓨터의 성능에 따라 케릭터의 이동이 달라지면 안된다.
겔로그(미사일 뽕뽕 쏘는 게임)
한번에 tick이 발생할떄 고정 상수를 쓰면 안된고 시간 동기화를 사용한다.
시간동기화
// CTimeMgr.h
#pragma once
class CTimeMgr
{
SINGLE(CTimeMgr)
private:
public:
};
// CTimeMgr.cpp
#include "pch.h"
#include "CTimeMgr.h"
픽셀 라인이 비트맵에 넣어버리면서 지나온길을 펜때문에
사각형이 누적으로 계속 그려지니까 픽셀 라인(사각형테두리)이 계속 비트맵에 검은색으로 라인이 채워지기 때문에 검은색으로 채워져 온것이다.
비슷한 환경 상에서 이동을 하고 나면 이전부분이 원상복구가 되야한다.
2D 및 3D 렌더링의 유사성
스톱 모션 애니메이션
스톱 모션은 프레임별로 물체를 조금씩 움직이고 사진을 찍어서 연속된 이미지를 만드는 과정입니다.
예를 들어, 24프레임 애니메이션을 만들기 위해 1초 동안의 애니메이션을 만들기 위해 24번의 사진 촬영과 물체 조정이 필요합니다.
이 과정은 매우 시간이 많이 걸리는 수작업이다.
게임 렌더링과 스톱 모션의 유사성
게임 렌더링은 스톱 모션 애니메이션과 유사한 방식으로 작동합니다.
각각의 프레임에서 게임 엔진은 게임의 상태를 업데이트하고 그 결과를 화면에 렌더링합니다.
컴퓨터의 역할
GPU의 역할
게임 렌더링 과정에서 GPU(Graphics Processing Unit)는 매우 중요한 역할을 합니다.
GPU는 그래픽 연산을 효율적으로 처리하여 부드러운 애니메이션과 복잡한 시각적 효과를 실시간으로 렌더링하는 데 필수적입니다.
현실 시간과의 동기화
렌더링과 클리어링
// 위 생략
#include "enum.h"
// 열거형 헤더 아래 추가됨.
#include "struct.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))
#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,
};
#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()
{
}
};
#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]; }
};
#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);
}
#pragma once
class CLevel;
class CLevelMgr
{
SINGLE(CLevelMgr)
private:
CLevel* m_arrLevel[(UINT)LEVEL_TYPE::END];
CLevel* m_pCurrentLevel;
public:
void init();
void progress();
};
#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();
}
#pragma once
class CTimeMgr
{
SINGLE(CTimeMgr)
private:
public:
};
#include "pch.h"
#include "CTimeMgr.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();
};
#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();
}
}
#pragma once
#include "CLevel.h"
class CLevel_Stage01 :
public CLevel
{
private:
public:
CLevel_Stage01();
~CLevel_Stage01();
};
#include "pch.h"
#include "CLevel_Stage01.h"
CLevel_Stage01::CLevel_Stage01()
{
}
CLevel_Stage01::~CLevel_Stage01()
{
}
#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();
};
#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