이득우의 게임 수학
게임 엔진이 제공하는 실행 흐름을 워크플로우(Workflow)라고 한다.
책의 예제인 CK소프트렌더러는 총 4단계의 워크플로우를 제시한다.
- 리소스 로딩
메모리에 게임 진행에 필요한 메시, 텍스처 등의 리소스를 미리 올려둔다. - 씬 구축
씬에 속한 게임 오브젝트를 생성하고 트랜스폼 정보가 설정되며, 리소스 정보가 게임 오브젝트에 연결된다. - 게임 로직
게임 오브젝트의 트랜스폼을 변경하는 작업이다. - 렌더링 로직
게임 로직에서 완성된 씬 정보를 바탕으로 최종 화면을 그려내는 과정이다.
게임 오브젝트의 트랜스폼 정보와 오브젝트에 연결된 메시, 텍스처 등의 리소스를 활용한다. - 매 프레임마다 3 -> 4 과정을 반복한다.
1. 리소스 로딩 단계
- 불러들인 리소스에 고유한 키를 설정한다.
// 책의 예제인 CK소프트렌더러에 구현된 리소스 로딩 단계
// 메시의 리소스 키
const size_t GameEngine::QuadMesh = hash<string>()("SM_Quad");
// 텍스처의 리소스 키
const size_t GameEngine::BaseTexture = hash<string>()("Base");
// 텍스처의 파일 경로
const string GameEngine::CharacterTexturePath("CKMan.png");
// 리소스 로딩
bool GameEngine::LoadResources()
{
// GameEngine::QuadMesh를 리소스 키로 갖는 사각형 메시 생성
Mesh& quadMesh = CreateMesh(GameEngine::QuadMesh);
constexpr float squareHalfSize = 0.5f;
constexpr int vertexCount = 4;
constexpr int triangleCount = 2;
constexpr int indexCount = triangleCount * 3;
auto& v = quadMesh.GetVertices();
auto& i = quadMesh.GetIndices();
auto& uv = quadMesh.GetUVs();
v = {
Vector2(-squareHalfSize, -squareHalfSize),
Vector2(-squareHalfSize, squareHalfSize),
Vector2(squareHalfSize, squareHalfSize),
Vector2(squareHalfSize, -squareHalfSize)
};
uv = {
Vector2(0.125f, 0.75f),
Vector2(0.125f, 0.875f),
Vector2(0.25f, 0.875f),
Vector2(0.25f, 0.75f)
};
i = {
0, 2, 1,
0, 3, 2
};
quadMesh.CalculateBounds();
// GameEngine::BaseTexture를 리소스 키로 갖는 CharacterTexturePath 경로의 텍스처 생성
Texture& baseTexture = CreateTexture(GameEngine::BaseTexture, GameEngine::CharacterTexturePath);
if (!baseTexture.IsIntialized())
{
return false;
}
return true;
}
2. 씬 구축 단계
- 이름을 통해 게임 오브젝트를 생성하고 메시 리소스 키, 트랜스폼 정보 등을 설정한다.
// 책의 예제인 CK소프트렌더러에 구현된 씬 구축 단계
// 게임 오브젝트 목록
static const string PlayerGo("Player");
// 씬 구축
void SoftRenderer::LoadScene2D()
{
// 최초 씬 로딩에서 사용하는 모듈 내 주요 레퍼런스
auto& g = Get2DGameEngine();
// 플레이어의 생성과 설정
// 플레이어 게임 오브젝트의 최초 크기
constexpr float playerScale = 30.f;
// PlayerGo 이름을 갖는 플레이어 게임 오브젝트 생성
GameObject& goPlayer = g.CreateNewGameObject(PlayerGo);
// GameEngine::QuadMesh를 메시의 리소스 키로 설정
goPlayer.SetMesh(GameEngine::QuadMesh);
// 최초 트랜스폼 설정
goPlayer.GetTransform().SetScale(Vector2::One * playerScale);
// 와이어프레임 모드에서의 색 설정
goPlayer.SetColor(LinearColor::Red);
// 100개의 배경 게임 오브젝트 생성과 설정
char name[64];
// 배경 게임 오브젝트의 최초 크기
constexpr float squareScale = 20.f;
mt19937 generator(0);
uniform_real_distribution<float> dist(-1000.f, 1000.f);
for (int i = 0; i < 100; ++i)
{
snprintf(name, sizeof(name), "GameObject%d", i);
GameObject& newGo = g.CreateNewGameObject(name);
newGo.GetTransform().SetPosition(Vector2(dist(generator), dist(generator)));
newGo.GetTransform().SetScale(Vector2::One * squareScale);
newGo.SetMesh(GameEngine::QuadMesh);
newGo.SetColor(LinearColor::Blue);
}
}
3. 게임 로직 단계
- 실시간 사용자 입력을 통해 트랜스폼 정보를 변경한다.
// 책의 예제인 CK소프트렌더러에 구현된 게임 로직 단계
// 게임 로직
void SoftRenderer::Update2D(float InDeltaSeconds)
{
// 게임 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& g = Get2DGameEngine();
const InputManager& input = g.GetInputManager();
// 게임 로직의 로컬 변수
static float moveSpeed = 200.f;
static float rotateSpeed = 180.f;
static float scaleMin = 15.f;
static float scaleMax = 30.f;
static float scaleSpeed = 180.f;
// 플레이어에 대한 주요 레퍼런스
GameObject& goPlayer = g.GetGameObject(PlayerGo);
TransformComponent& transform = goPlayer.GetTransform();
// 입력에 따른 플레이어 위치와 크기의 변경
float newScale = Math::Clamp(transform.GetScale().X + scaleSpeed * input.GetAxis(InputAxis::ZAxis) * InDeltaSeconds, scaleMin, scaleMax);
transform.SetScale(Vector2::One * newScale);
transform.AddRotation(input.GetAxis(InputAxis::XAxis) * rotateSpeed * InDeltaSeconds);
transform.AddPosition(transform.GetLocalY() * input.GetAxis(InputAxis::YAxis) * moveSpeed * InDeltaSeconds);
}
4. 렌더링 로직 단계
- 씬에 속한 모든 게임 오브젝트를 순회하면서, 각 오브젝트의 트랜스폼 정보로부터 생성된 모델링 행렬을 통해 화면에 오브젝트들을 그린다.
// 책의 예제인 CK소프트렌더러에 구현된 렌더링 로직 단계
// 렌더링 로직
void SoftRenderer::Render2D()
{
// 렌더링 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& r = GetRenderer();
const auto& g = Get2DGameEngine();
// 배경에 격자 그리기
DrawGizmo2D();
// 씬에 속한 전체 게임 오브젝트의 개수
size_t totalObjectCount = g.GetScene().size();
// 씬을 구성하는 모든 게임 오브젝트 순회
for (auto it = g.SceneBegin(); it != g.SceneEnd(); ++it)
{
// 게임 오브젝트의 레퍼런스를 얻기
const GameObject& gameObject = *(*it);
// 메시가 없거나, Visible 속성이 없으면 그리지 않는다.
if (!gameObject.HasMesh() || !gameObject.IsVisible())
{
continue;
}
// 렌더링에 필요한 게임 오브젝트의 주요 레퍼런스를 얻기
// 게임 오브젝트에 설정된 메시 리소스 키로 가져온 메시
const Mesh& mesh = g.GetMesh(gameObject.GetMeshKey());
// 게임 오브젝트의 트랜스폼
const TransformComponent& transform = gameObject.GetTransform();
// 게임 오브젝트의 모델링 행렬
Matrix3x3 finalMatrix = transform.GetModelingMatrix();
// 게임 오브젝트의 메시, 모델링 행렬, 색상 정보를 이용해 렌더링 수행
// DrawMesh2D() 함수에서는 기본적으로 GameEngine::BaseTexture 리소스 키를 통해 텍스처를 가져와 그린다.
DrawMesh2D(mesh, finalMatrix, gameObject.GetColor());
}
}
리소스 로딩 -> 씬 구축 -> 게임 로직 -> 렌더링 로직 -> 게임 로직 -> 렌더링 로직 -> ... 단계를 거쳐, 1개의 메시 리소스와 1개의 텍스처 리소스를 공유하는 101개의 플레이어 게임 오브젝트를 그려냈다.
'게임 수학 > 이득우의 게임 수학' 카테고리의 다른 글
카메라 시스템 (0) | 2023.05.03 |
---|---|
렌더링 파이프라인 (0) | 2023.05.02 |
리소스 저장소 (0) | 2023.04.30 |
로컬 공간과 로컬 축 (0) | 2023.04.29 |
모델링 행렬의 설계 (0) | 2023.04.28 |