대충 자주 사용하는 코딩 패턴이다.
프로그래머 -> 이렇게 하니까 편하게 구조가 만들어진다.
디자인 패턴을 만든 사람들 -> 이 구조는 퍼사드 패턴이라고 부르자. 커맨드 패턴, 싱글톤 패턴 ... 24개의 패턴을 만들었다.(늘어나서 26개)
패턴에 이름을 붙이고 책을 내버림.
이런 디자인 패턴 이외에도 웹 분야 등에서 MVC, MVVM 등의 패턴도 있다.
(디자인 패턴의 구조도를 설명할 때 UML을 많이 사용한다. 게임 회사에서는 잘 안 만든다는 것 같다. StarUML이라는 프로그램에 클래스를 넣어주면 알아서 만들어준다고 한다.)
다른건 몰라도 싱글톤 패턴은 알아두는 게 좋다. 정말 많이 사용함.
생성 : 객체의 생성 방식에 대해서 정의 및 관여하는 패턴 (싱글톤, 팩토리)
ex) static으로 만드는 방식 => 싱글톤
구조 : 객체의 관계를 정의하는 패턴.
행위 : 객체의 행동을 구조화 시키는 패턴.
1) Builder : 생산 단계를 캡슐화 하여 구축 공정을 동일하게 이용하도록 하는 패턴
2) Prototype : 복사하여 새 개체를 생성할 수 있도록 하는 패턴
3) Factory Method : 객체를 생성하기 위한 인터페이스를 정의하여 어떤 클래스가 인스턴스화 될 것인지는 서브 클래스가 결정하도록 하는 패턴
4) Abstract Method : 생성군들을 하나의 모아놓고 팩토리 중에서 선택하게 하는 패턴
5) Singleton : 유일한 하나의 인스턴스를 보장하도록 하는 패턴
1) Bridge : 추상과 구현을 분리하여 결합도를 낮춘 패턴
2) Decorator : 소스를 변경하지 않고 기능을 확장하도록 하는 패턴
3) Facade : 하나의 인터페이스를 통해 느슨한 결합을 제공하는 패턴
4) Flyweight : 대량의 작은 객체들을 공유하는 패턴
5) Proxy : 대리인이 대신 그 일을 처리하는 패턴
6) Composite : 개별 객체와 복합 객체를 클라이언트에서 동일하게 사용하도록 하는 패턴
7) Adapter : 인터페이스로 인해 함께 사용하지 못하는 클래스를 함께 사용하도록 하는 패턴
1) Interpreter : 언어 규칙 클래스를 이용하는 패턴
2) Template Method : 알고리즘 골격의 구조를 정의한 패턴
3) Chain of Responsibility : 객체들끼리 연결 고리를 만들어 내부적으로 전달하는 패턴
4) Command : 요청 자체를 캡슐화하여 파라미터로 넘기는 패턴
5) Iterator : 내부 표현은 보여주지 않고 순회하는 패턴
6) Mediator : 객체 간 상호작용을 캡슐화한 패턴
7) Memento : 상태 값을 미리 저장해 두었다가 복구하는 패턴
8) Observer : 상태가 변할 때 의존자들에게 알리고, 자동 업데이트하는 패턴
9) State : 객체 내부 상태에 따라서 행위를 변경하는 패턴
10) Strategy : 다양한 알고리즘 캡슐화하여 알고리즘 대체가 가능하도록 한 패턴
11) Visitor : 오퍼레이션을 별도의 클래스에 새롭게 정의한 패턴
객체가 무조건 1개만 생기게 만드는 패턴이다. 그것만 지키면 싱글톤임.
Monster 클래스가 싱글톤이라면 메모리적으로 Monster 클래스가 딱 1개만 만들어질 수 있어야 한다.
(싱글톤을 만들었는데 파괴해야하는 경우가 거의 없기 때문에 ResourceManager에서 포인터로 만들필요가 없다.)
싱글톤이 클래스에만 해당되는 것이 아니다.
객체지향의 캡슐화 => private
필요한 메모리를 공개하고 싶은 곳에만 공개함으로써 건강한 객체간의 관계를 만드는 게 캡슐화의 목적이다.
(이걸 이론으로 따지면서 만들진 않음)
함수에서만 사용할 수 있으면서 메모리는 전역으로 유지되는 변수가 필요할 때 지역 static 사용.
static int A는 빌드되면 데이터영역으로 옮겨버린다.
void Test() {
static int A = 0;
std::cout << ++A << std::endl;
}
int main() {
while (true) {
Test();
}
}
#pragma once
#include <string>
#include <string_view>
#include <map>
// class Monster 싱글톤이라면 메모리적으로
// class Monster가 딱 1개만 만들어질수 있어야 한다.
// class UEngineResourcesManager 싱글톤이라면 메모리적으로
// class UEngineResourcesManager가 딱 1개만 만들어질수 있어야 한다.
class UImage;
// 설명 : 이미지 사운드 등등을 종합 관리할겁니다.
// 게임에서 리소스라고 불리는 객체들을 전부다 이녀석이 관리할 예정.
class UEngineResourcesManager
{
public:
// delete Function
UEngineResourcesManager(const UEngineResourcesManager& _Other) = delete;
UEngineResourcesManager(UEngineResourcesManager&& _Other) noexcept = delete;
UEngineResourcesManager& operator=(const UEngineResourcesManager& _Other) = delete;
UEngineResourcesManager& operator=(UEngineResourcesManager&& _Other) noexcept = delete;
// 싱글톤이라고 한다.
static UEngineResourcesManager& GetInst()
{
// 지역static 싱글톤
static UEngineResourcesManager Inst = UEngineResourcesManager();
return Inst;
}
// 포인터형 싱글톤
//static UEngineResourcesManager* GetPInst()
//{
// // 포인터형의 장점
// // 1. 내가 삭제하고 싶을때 삭제할수 있다.
// // 2. new는 동적할당이죠?
// // new는 가장 마지막에 마지막에 해야한다는 격언이 있다.
// // ?????? 100바이트 아껴서 뭐하게?
// if (nullptr == pInst)
// {
// pInst = new UEngineResourcesManager();
// }
//
// return pInst;
//}
// 언제든지 싱글톤 객체를 파괴할수 있다는 장점이 있다.
// 근데 왜 파괴하는데?
//static void Destroy()
//{
// if (nullptr != pInst)
// {
// delete pInst;
// pInst = nullptr;
// }
// return;
//}
// 인터페이스가 없으면 너무 추상적인 생각이다.
// 생각이 공상가식 프로그래밍.
// 하드디스크에 있는 이미지를 로드한다.
UImage* LoadImg(std::string_view _Path);
UImage* FindImg(std::string_view _Name);
protected:
private:
// constrcuter destructer
UEngineResourcesManager();
~UEngineResourcesManager();
std::map<std::string, UImage*> Images;
// C++에서는 이렇게 static으로 자기자신을 자기가 내부에서 만듭니다.
// 자기자신이 내부에서 여러개 만들면 만들수 있죠.
// 그걸 딱 1개만 만드는게 싱글톤의 정석
// static UEngineResourcesManager Inst;
// static UEngineResourcesManager* pInst;
};
리소스를 사용하기위해서는 파일 경로가 필요하다 -> EnginePath 만듦.
IsFile, IsDirectory
윈도우에게 리소스 load 부탁할 때는 무조건 파일의 풀경로를 넣어줘야 한다.
C++ 17부터 표준에 파일 경로 관리해주는 게 생겼다. #include <filesystem>
함수만 호출해도 그냥 경로가 튀어나오는 편리한 기능 대거 지원. 내부에서 윈도우의 함수를 사용하고 있다.
std::filesystem::directory_iterator
: 디렉토리의 파일 원소를 다 돌 수 있게 해주는 반복자 (서브디렉토리 방문 X)std::filesystem::directory_entry& Entry
: Entry 디렉토리의 정보를 담고 있는 객체_Ext
인자로 넣어준 확장자의 파일을 모두 찾아주는 함수.
std::list<UEngineFile> UEngineDirectory::AllFile(
std::vector<std::string> _Ext /*= std::vector<std::string>()*/,
bool _Rescursive /*= false*/
)
{
std::list<UEngineFile> Result;
for (size_t i = 0; i < _Ext.size(); i++)
{
_Ext[i] = UEngineString::ToUpper(_Ext[i]);
}
AllFileRecursive(Path.string(), Result, _Ext, _Rescursive);
return Result;
}
확장자에 해당하는 파일과 하위 폴더에 있는 파일까지 재귀로 찾아주는 함수.
void UEngineDirectory::AllFileRecursive(
const std::string_view _Path,
std::list<UEngineFile>& _Result,
std::vector<std::string> _Ext /*= std::vector<std::string>()*/,
bool _Recursive /*= false*/)
{
std::filesystem::directory_iterator DirIter = std::filesystem::directory_iterator(_Path);
// const Monster& NewMonster;
// NewMonster.Fight
// NewMonster.Damage
// const std::filesystem::directory_entry& Entry
// Entry.
for (const std::filesystem::directory_entry& Entry : DirIter)
{
// Entry 디렉토리의 정보를 담고 있는 C++이 지원해주는 객체입니다.
// 객체는 우리가 고민할 필요가 없다.
// 객체라면 우리가 할껀 .찍어보는것 밖에 없다.
// 특정 경로 안에 있는 또다른 폴더
// 폴더라는 거네
std::filesystem::path Path = Entry.path();
std::filesystem::path Ext = Entry.path().extension();
std::string UpperExt = UEngineString::ToUpper(Ext.string());
if (true == Entry.is_directory())
{
if (true == _Recursive)
{
AllFileRecursive(Path.string(), _Result, _Ext, _Recursive);
}
continue;
}
if (0 == _Ext.size())
{
_Result.push_back(UEngineFile(Path.string()));
continue;
}
bool Check = false;
for (size_t i = 0; i < _Ext.size(); i++)
{
if (_Ext[i] == UpperExt)
{
Check = true;
}
}
if (true == Check)
{
_Result.push_back(UEngineFile(Path.string()));
}
}
}
#include <iostream>
#include <list>
class Renderer
{
public:
bool IsDeath = true;
int Value = 0;
public:
void Render()
{
++Value;
}
};
int main()
{
Renderer* NewRenderer = new Renderer();
std::list<Renderer*> ActorList;
ActorList.push_back(NewRenderer);
std::list<Renderer*> LevelList;
LevelList.push_back(NewRenderer);
while (true)
{
for (Renderer* Render : LevelList)
{
Render->Render();
}
for (Renderer* Render : ActorList)
{
delete Render;
}
ActorList.clear();
}
std::cout << "Hello World!\n";
}
override할 때 부모의 함수도 호출해주는 게 좋다.
액터의 DestroyUpdate에서는 TickObject::Destroy로 나 자신만(액터) 죽이고 있었는데 Renderer들도 죽여야하니까 Destroy override해서 액터의 Destroy를 만들었다.