언리얼 수학
LineTrace, 감지 및 추적시스템
GoF 디자인 패턴 - 생성 패턴
1U = 1cm
Local->World->View->Clip Space로 좌표를 변환하기 위해 행렬 이용
World Matrix, View Matrix, Projection Matrix
언리얼에서의 각도 : degree
C++ / HLSL에서의 각도 : Radian

Get Mouse Position on ViewPort
게임 화면 내에서의 마우스 위치를 반환
Convert Screen Location To World Space
스크린의 위치를 월드 스페이스에서의 위치와 방향을 반환
단, 2D에서 3D로의 변환이므로 월드에서 카메라화면이 위치한 좌표를 반환
따라서 방향과 LineTrace를 이용하여 원하는 좌표를 얻음
Line Trace By Channel
LineTrace하여 Hit했는지에 대한 bool값과 Hit 결과 Structure을 반환

Draw Debug Cone
디버깅 용도의 원뿔을 그림
해당 원뿔이 있는 위치를 감지 구역으로 설정하고, 이 위치 안에 들어왔을 시 "Find!"를 출력하는 시스템
적 AI에 넣어주어 플레이어를 감지하고 이후 추격 로직을 추가할 수 있음
코드를 집 짓기에 비교한다면
생성 패턴 : 객체 생성 메커니즘을 다루는 패턴
구조 패턴 : 클래스나 객체를 조합, 연결하는 패턴
행동 패턴 : 객체 간 상호작용과 책임 분배 패턴
패턴을 사용하면, 유지보수와 확장성 등에서 용이하지만, 성능에서는 떨어짐
무분별한 패턴은 오히려 디버깅에도 어려워짐
각 패턴들의 장단점을 알고 써야함
하나만 있어야 하는 객체의 생성 패턴
캐릭터, 게임인스턴스, 게임 매니저 등에 사용
class GameManager {
private:
// 1. static 인스턴스
static GameManager* instance;
// 2. private 생성자 (외부에서 new 금지)
GameManager() {
score = 0;
gameTime = 0.0f;
}
public:
// 3. 접근 메서드
static GameManager* GetInstance() {
if (instance == nullptr) {
instance = new GameManager();
}
return instance;
}
};
// static 멤버 초기화
GameManager* GameManager::instance = nullptr;
객체 생성을 별도의 메서드로 캡슐화하여, 생성 로직을 한 곳에서 관리하는 패턴
필요한 상황
// 문제상황
class Game {
void SpawnEnemy() {
// 적 종류 추가되면 하드코딩으로 추가해줘야함
Enemy* enemy = new Zombie();
enemy->Spawn();
}
};
따라서 Game 클래스는 적 소환하라는 메서드만 호출하고, 실제 호출은 하위 클래스가 함
생성할 몬스터/아이템의 기본 인터페이스를 먼저 생성 (Product)
// 모든 적의 기본 인터페이스
class Enemy {
public:
virtual void Attack() = 0;
};
// 진짜로 게임에 등장할 몬스터들
class Zombie : public Enemy {
// ... //
}
};
class Skeleton : public Enemy {
// ... //
};
class EnemySpawner {
// SpawnEnemy함수 //
};
// 좀비 공장
class ZombieSpawner : public EnemySpawner {
// SpawnEnemy함수 override //
};
// 스켈레톤 공장
class SkeletonSpawner : public EnemySpawner {
// SpawnEnemy함수 override //
}
class GameMode {
std::unique_ptr<EnemySpawner> currentSpawner;
// 스테이지에 따라 다른 스포너 사용
void SetupStage(int stageNumber) {
switch (stageNumber) {
case 1:
currentSpawner = std::make_unique<ZombieSpawner>();
break;
case 2:
currentSpawner = std::make_unique<SkeletonSpawner>();
break;
}
}
// 적 스폰
void SpawnEnemyWave() {
if (currentSpawner)
currentSpawner->SpawnEnemy();
}
};
// 문제상황: 생성자가 너무 복잡함
class Character {
public:
Character(
string name,
string className,
int level,
float health,
// ... 더 많은 멤버변수
);
};
// 사용에도 문제
Character* hero = new Character(
"Arthas",
"Warrior",
// ... 헷갈리기 시작
);
class Character {
private:
FString Name;
FString Class;
int Level;
// ...
public:
friend class CharacterBuilder;
};
friend class CharacterBuilder : Builder만 접근 가능class CharacterBuilder {
private:
Character* character; // 조립 중인 캐릭터
public:
CharacterBuilder() {
character = new Character();
// 기본값 세팅
character->Level = 1;
character->Height = 1.8f;
character->Health = 100.0f;
character->Mana = 100.0f;
character->Strength = 10.0f;
character->Intelligence = 10.0f;
character->Agility = 10.0f;
}
// 모든 메서드는 자기 자신의 참조자를 반환하여 체인 가능하도록
CharacterBuilder& WithName(const FString& name) {
character->Name = name;
return *this; // 자기 자신 참조 반환
}
// 직업 설정
CharacterBuilder& WithClass(const FString& className) {
character->Class = className;
return *this;
}
// 설정 메서드들 //
// 최종 빌드
Character* Build() {
// 마지막 검증
if (character->Name.IsEmpty()) {
character->Name = "Unknown Hero"; // 이름 없으면 기본값
}
return character; // 최종 캐릭터를 반환
}
};
기본값을 세팅해주어, Builder로 생성시 몇 개 빼먹어도 괜찮도록 설게
각 메서드들은 자기 자신의 참조자를 반환하여 체인 가능하도록 설계
마지막에 Build를 호출하여 완성된 캐릭터를 반환
Character* wizard = CharacterBuilder()
.AddSkill("Fireball")
.WithClass("Mage")
.WithName("Gandalf")
.AddSkill("Teleport")
.WithLevel(99)
.Build();
순서 상관없고 멤버변수 빼먹어도 상관없음. 마지막에 Build만 호출하면 됨
장점 : 복잡한 객체생성에 사용하기 편하고 안전성도 높음
단점
정말 복잡한 객체 생성에서만 사용하기