이제부터는 우리의 오브젝트들이 뛰어놀수 잇는 기반을 만들어야한다.
우리가 게임 코드를 만들다 보면 하나하나의 CEntity객체들은 무수히 많이 쏘아지겠지만 저 객체들을 관리해주는 관리자 기능은 딱 하나만 존재해야한다.
물체들의 맵을 관리(게임레벨)을 관리하는 놈이 있어야 한다.
프로그램 전체를 담당하는 관리자가 있어야한다.
저 클래스의 명시해둔것을 실제로 하는것은 해당 객체기 때문에 우리가 CEntity클래스를 만들어서 CEntity객체를 엄청나게 많이 만든 것처럼
CEO라는 자료형을 설계하고 객체를 만들어서 그 만들어진 객체가 그걸 담당할것인데 문제는 CEO객체를 하나까지만 생성되게 제한해야한다는 것.
프레임 워크틀을 설계하다보면 공통되는 패턴이 등장한다.
설계유형들을 정리해놓은게 디자인 패턴이라고 부른다
언어에 국한되는 개념이 아니다.
패턴은 동일한 경우고 우리는 C++을 이용해서 하고 있기 떄문에 싱글톤 패턴으로 C++로는 어떻게 해결할가라는 방식으로 접근해야한다.
CEngine
클래스로 할것이고 따라서 CEngine
클래스는 하나의 객체만 만들어지게 제한해야한다.class CEngine
{
private:
public:
};
CEngine
클래스의 기본틀// main.cpp
CEngine engin1; // 가능
CEngine engin1; // 불가능
private:
CEngine();
// 3
private:
static CEngine* pEngine;
public:
CEngine* GetEngine()
{
// 1
CEngine engine;
return &engine;
// 2
CEngine* pEngine = new CEngine;
return pEngine;
}
객체를 1개 만들어서 반환해주는 함수를 구현.
1번처럼 지역변수로 만들어도 함수가 끝나면 날라가고
2번은 동적할당해서 주는 방식이다.
매번 달라할때마다 객체를 계속 해서 준다.
외부에서는 접근이 안되지만 내부에서 접근이 가능하다.
계속 생성이 되면 안되니까 포인터 변수를 하나 선언해줘야한다.
// CEngine.cpp 내부
CEngine* CEngine::pEngine = nullptr;
CEngine* GetEngine()
{
if(nullptr == pEngine)
{
pEngine = new CEngine;
}
return pEngine;
}
// CEngine.h의 내부
public:
~CEngine();
// CEngine.cpp내부
CEngine::~CEngine()
{}
void Destroy()
{
if (nullptr != pEngine)
delete pEngine;
}
// 1
CEngine* pEngine = CEngine::GetEngine();
// 2
pEngine = CEngine::GetEngine();
pEngine = CEngine::GetEngine();
pEngine = CEngine::GetEngine();
// main.cpp 내부
CEntity entity;
entity.SetName(L"Monster");
// CEntity.h 내부
void SetName(const wstring& _Name) { this-> m_strName = _Name; }
private:
wstring m_Name;
public:
CEngine* GetEngine()
{
/*this->*/m_Name = L"안녕";
}
// 1
static CEngine* GetEngine()
{
if(nullptr == pEngine)
{
pEngine = new CEngine;
}
return pEngine;
}
정적 맴버 함수
라 한다.// CEntity.h 의 CEntity클래스 내부
static void SetName(const wstring& _Name) { m_strName = _Name; }
// CEngine.h의 따로 구현한 전역함수다.
CEngine* GetEngine()
{
new CEngine; // 프라이빗필드에 있어서 접근 불가능.
}
전역함수를 쓰면 되지 왜 정적 맴버 함수를 사용하지?
Destroy함수도 정적 맴버 함수가 되어야 한다.
// main.cpp
// 1
CEngine::Destroy();
// 2
CEngine::GetEngine()->Destroy();
static void Destroy()
{
if (nullptr != pEngine)
delete pEngine;
}
wstring m_Name;
에는 접근할수 없지만 static CEngine* pEngine;
에는 접근이 가능하다.객체가 없어도 호출가능하다.
해당 클래스의 맴버에 접근 불가능하다.
맴버함수의 특징인 클리스의 private에 접근 가능하다.
클래스의 정적 맴버변수에는 접근 가능
class CEngine
{
private:
public:
// 1
static CEngine* GetEngine()
{
// 2
//static int a = 0;
// 3
static CEngine engine;
// 4
++ a;
// 5
return &engine;
}
// 6
private:
CEngine();
};
1번처럼 정적 맴버 함수가 되어야하는 이유는 객체가 없이도 호출할수 있어야 되기 떄문이고 전역 함수가 아니라 정적 맴버함수여야 private필드에 접근할려고
2번처럼 지역변수에 static를 해버리면 a는 함수안에 있는게 아니라 데이터 영역에 있다. 함수 끝나도 a라는 변수는 살아있다.
3번처럼 객체를 선언하면 엔진이라는 클래스로 객체가 하나 만들어졌는데 데이터 영역에 존재한다.
5번처럼 주소를 리턴해버리면 데이터 영역이여도 주소는 알수 있다.
싱글톤 패턴 완성
CEngine engine(*pEngine);
class AAA
{
private:
int m_a;
int m_b;
};
// 메인함수 안
AAA a;
AAA b(a);
// AAA 클래스 내부
public:
AAA()
: m_a(0)
, m_b(0)
{}
// 2
AAA(const AAA& _other)
: m_a(_other.m_a)
, m_b(_other.m_b)
{}
public:
void operator = (const AAA& _other)
{
m_a = _other.m_a;
m_b = _other.m_b;
}
// 메인함수 안
AAA b = a;
// 1
CEngine engine(*pEngine);
// 2
CEngine engine = *pEngine;
// CEngine.h의 CEngine 클래스 내부
public:
CEngine(const CEngine& _other)
{
}
private:
CEngine(const CEngine& _other) = delete
//AAA(const AAA& _other)
// : m_a(_other.m_a)
// , m_b(_other.m_b)
//{}
AAA(const AAA& _other) = delete;
CEngine(const CEngine& _other) = delete;
첫번째 구현한 싱글톤도 저렇게 private으로 막히겠지만 막아주는게 좋다.
생성을 막는게 중요해서 소멸자는 걍 퍼블릭에 뒀다.
첫번째는 동적할당으로 했고 두번쨰는 전역변수에 올려서 쉽게 구현을 했다.
#include "pch.h"
#include "framework.h"
#include "GameClient.h"
#include "Resource.h"
#include "CEngine.h"
#include "CEntity.h"
class AAA
{
private:
int m_a;
int m_b;
public:
void operator = (const AAA& _other)
{
m_a = _other.m_a;
m_b = _other.m_b;
}
public:
AAA()
: m_a(0)
, m_b(0)
{}
/*AAA(const AAA& _other)
: m_a(_other.m_a)
, m_b(_other.m_b)
{}*/
AAA(const AAA& _other) = delete;
};
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
HWND g_hWnd; // 메인 윈도우 핸들
wchar_t szTitle[100]; // 제목 표시줄 텍스트입니다.
wchar_t szWindowClass[100]; // 기본 창 클래스 이름입니다.
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
// SAL : 주석 언어
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
CEntity entity;
entity.SetName(L"Monster");
CEngine* pEngine = CEngine::GetEngine();
pEngine = CEngine::GetEngine();
pEngine = CEngine::GetEngine();
pEngine = CEngine::GetEngine();
// 복사생성자를 막지 않으면,
// singleton 객체를 얻어서 새로운 객체를 복사생성자를 통해서 생성시킬 수 있다.
CEngine engine = *pEngine;
// 삭제된 복사생성자를 호출하려고 하는 경우
//AAA a;
//AAA b = a;
// 윈도우 클래스 등록
MyRegisterClass(hInstance);
// 윈도우 생성
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
// CreateWindow 윈도우 생성
// 반환값은 윈도우 핸들(ID) 값,
// 커널 오브젝트 : OS 가 관리하는 오브젝트, 직접적인 접근이 불가능하고 ID 값을 통해서 제어함
g_hWnd = CreateWindowW(L"Window Class Key", L"My Game", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!g_hWnd)
return FALSE;
// 윈도우를 화면에 보여줄지 말지 설정
ShowWindow(g_hWnd, true);
UpdateWindow(g_hWnd);
// 리소스(단축키 테이블 핸들 얻기)
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GAMECLIENT));
// 메세지 루프
// 메세지 정보를 받을 구조체 변수
MSG msg;
while (true)
{
// Peek 엿보다
// 메세지가 있던 없던, 리턴된다.
// 메세지가 있었으면 true, 메세지가 없었으면 false
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
// 꺼내온 메세지가 WM_QUIT 이면 프로그램 종료
if (msg.message == WM_QUIT)
break;
// 꺼내온 메세지 처리
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
// 메세지가 큐에 없을 때에는 게임 코드 실행
}
}
//CEngine::Destroy();
return (int) msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_GAMECLIENT));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
//wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_GAMECLIENT);
wcex.lpszClassName = L"Window Class Key";
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
// CallBack
// 함수의 주소를 알려줘서, 특정 상황(조건) 이 맞으면 알려준 함수가 호출되는 구조
// 각 윈도우들은 해당 윈도우에 메세지가 발생했을 때 처리를 해줄 프로시저함수를 등록해야한다.
// 메인 윈도우는 MyRegisterClass 함수 안에서 윈도우 정보를 만들때 호출함 함수의 주소를 등록해둠
// 도움말 윈도우는 다이얼로그 형태로서, DialogBox 함수를 호출할때 사용할
// 프로시저 함수의 주소를 입력으로 넣어줬다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
ShowWindow(g_hWnd, false);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
// 윈도우 생성 함수
//
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
EndPaint(hWnd, &ps);
}
break;
case WM_TIMER:
{
int a = 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
//EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
#pragma once
#include <Windows.h>
#include <vector> // 동적배열
#include <list> // 연결형 리스트
#include <map> // 이진탐색트리
#include <string> // 문자열 전용 관리
using std::vector;
using std::list;
using std::map;
using std::make_pair;
using std::string;
using std::wstring;
#pragma once
// 정적 변수
// 1. 함수 내
// 2. 파일
// 3. 클래스
class CEntity
{
private:
// 정적 맴버 선언
static UINT g_NextID; // 특정 클래스 내에 맴버로 정적변수를 선언
private:
const UINT m_ID; // 객체별 고유 ID
wstring m_strName;
public:
UINT GetID() { return m_ID; }
void SetName(const wstring& _Name) { m_strName = _Name; }
const wstring& GetName() { return m_strName; }
public:
// CEntity 클래스는 추상클래스이다.
//virtual CEntity* Clone() = 0;
public:
CEntity();
virtual ~CEntity();
};
#include "pch.h"
#include "CEntity.h"
// 구현
UINT CEntity::g_NextID = 0;
CEntity::CEntity()
: m_ID(g_NextID++)
{
}
CEntity::~CEntity()
{
}
#pragma once
// =====================
// 디자인 패턴(설계 유형)
// =====================
// ==================================
// 싱글톤 패턴
// 클래스로 생성되는 객체를 1개로 제한
// ==================================
//class CEngine
//{
//private:
// static CEngine* pEngine; // 정적 맴버
//
//private:
// wstring m_Name; // 맴버
//
//public:
// // 객체를 1개 만들어서 반화해주는 함수를 구현
// // 정적 맴버함수 - this 를 사용하지 않는다
// // 1. 객체가 없어도 호출 가능
// // 2. 맴버에 접근 불가능
// // 3. 맴버함수의 특징, 클래스의 private 에 접근 가능
// // 클래스의 정적 맴버변수에는 접근 가능
// static CEngine* GetEngine() // 정적 맴버함수
// {
// if (nullptr == pEngine)
// {
// pEngine = new CEngine;
// }
//
// return pEngine;
// }
//
// static void Destroy()
// {
// if (nullptr != pEngine)
// delete pEngine;
// }
//
//private:
// CEngine();
// CEngine(const CEngine& _other) = delete;
//
//public:
// ~CEngine();
//};
class CEngine
{
private:
public:
static CEngine* GetEngine()
{
static CEngine engine;
return &engine;
}
private:
CEngine();
CEngine(const CEngine& _other) = delete;
};
#include "pch.h"
#include "CEngine.h"
CEngine* CEngine::pEngine = nullptr;
CEngine::~CEngine()
{
}
1차 24.01.19
2차 24.01.22
3차 24.01.23
4차 24.01.24
5차 24.01.25