ImGuizmo 키 입력 시스템: Transform 조작 구현
게임 엔진 에디터에서 GameObject의 Transform을 직관적으로 조작하는 것은 필수적인 기능입니다. ImGuizmo는 Unity나 Unreal Engine처럼 시각적인 기즈모(Gizmo)를 제공하여 오브젝트의 위치, 회전, 스케일을 손쉽게 편집할 수 있게 해줍니다. 이 문서에서는 키보드 입력으로 기즈모 모드를 전환하는 시스템을 상세히 다룹니다.
ImGuizmo 개요
ImGuizmo는 3D 변환 조작을 위한 오픈소스 라이브러리로, 에디터에서 오브젝트를 마우스로 직접 드래그하여 변환할 수 있는 시각적 위젯을 제공합니다.
ImGuizmo의 주요 기능
Transform 조작 모드
ImGuizmo는 세 가지 기본 조작 모드를 제공합니다.
•
Translate (이동): 오브젝트를 X, Y, Z 축을 따라 이동시킵니다. 화살표 형태의 기즈모가 표시되며, 각 축을 드래그하여 이동합니다.
•
Rotate (회전): 오브젝트를 회전시킵니다. 원형 기즈모가 표시되며, 각 축을 중심으로 회전할 수 있습니다.
•
Scale (크기 조절): 오브젝트의 크기를 조절합니다. 각 축의 끝에 큐브가 표시되며, 드래그하여 크기를 변경합니다.
좌표계 모드
•
Local (로컬): 오브젝트 자체의 좌표계를 기준으로 변환합니다. 회전된 오브젝트의 경우 기즈모도 함께 회전합니다.
•
World (월드): 월드 좌표계를 기준으로 변환합니다. 기즈모는 항상 월드 축에 정렬됩니다.
스냅 기능
그리드나 특정 각도 단위로 스냅하여 정확한 배치를 돕습니다.
•
이동 스냅: 0.5 단위, 1.0 단위 등
•
회전 스냅: 15도, 45도, 90도 등
•
크기 스냅: 0.1 배율, 0.5 배율 등
에디터에서의 활용
ImGuizmo는 다음과 같은 작업에 필수적입니다.
•
레벨 디자인: 건물, 소품, 지형 요소 배치
•
캐릭터 설정: 본, 콜라이더, 이펙트 포인트 조정
•
UI 레이아웃: UI 요소 위치와 크기 조정
•
카메라 설정: 카메라 위치와 시점 조정
•
라이트 배치: 조명 위치와 방향 설정
키 입력 이벤트 시스템 아키텍처
ImGuizmo의 모드 전환을 위한 키 입력 처리는 다음과 같은 계층적 구조로 이루어집니다.
SetKeyPressed: 원시 입력을 이벤트로 변환
SetKeyPressed() 함수는 GLFW에서 전달받은 원시 키보드 입력을 이벤트 객체로 캡슐화하여 이벤트 시스템에 주입합니다.
함수 시그니처
void Window::SetKeyPressed(int keyCode, int scancode, int action, int mods)
{
// keyCode: GLFW 키 코드 (GLFW_KEY_W, GLFW_KEY_Q 등)
// scancode: 플랫폼별 스캔 코드 (일반적으로 사용하지 않음)
// action: 키 상태 (GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT)
// mods: 수정 키 상태 (Shift, Ctrl, Alt 등의 비트 마스크)
// 키 상태에 따라 이벤트 생성
if (action == GLFW_RELEASE)
{
// 키가 떼어진 경우
KeyReleasedEvent event(keyCode);
mEventCallback(event);
}
else if (action == GLFW_PRESS)
{
// 키가 처음 눌린 경우
KeyPressedEvent event(keyCode, false); // isRepeat = false
mEventCallback(event);
}
else if (action == GLFW_REPEAT)
{
// 키가 계속 눌려 있는 경우 (OS 키 반복)
KeyPressedEvent event(keyCode, true); // isRepeat = true
mEventCallback(event);
}
}
C++
복사
키 액션 상태 상세 분석
GLFW_PRESS (값: 1)
키가 처음 눌렸을 때 발생합니다. 이 이벤트는 키를 누르는 순간 단 한 번만 발생합니다.
// 사용자가 W 키를 누르는 순간
// action = GLFW_PRESS
// → KeyPressedEvent(GLFW_KEY_W, false) 생성
C++
복사
GLFW_RELEASE (값: 0)
키를 뗄 때 발생합니다. 키를 누르고 있다가 손을 떼는 순간 발생합니다.
// 사용자가 W 키를 떼는 순간
// action = GLFW_RELEASE
// → KeyReleasedEvent(GLFW_KEY_W) 생성
C++
복사
GLFW_REPEAT (값: 2)
키를 계속 누르고 있을 때 OS의 키 반복 설정에 따라 주기적으로 발생합니다. 일반적으로 초기 지연(약 500ms) 후 일정 간격(약 30ms)으로 반복됩니다.
// 사용자가 W 키를 누른 채 유지
// 초기 GLFW_PRESS 발생 → 500ms 대기 → GLFW_REPEAT 연속 발생
// 각 GLFW_REPEAT마다
// → KeyPressedEvent(GLFW_KEY_W, true) 생성
C++
복사
키 반복 동작 예시
이벤트 콜백 메커니즘
mEventCallback은 함수 포인터 또는 std::function으로, Window 클래스가 생성될 때 외부에서 주입됩니다.
class Window
{
public:
using EventCallbackFn = std::function<void(Event&)>;
void SetEventCallback(const EventCallbackFn& callback)
{
mEventCallback = callback;
}
private:
EventCallbackFn mEventCallback;
};
// Application에서 콜백 설정
void Application::Initialize()
{
mWindow.SetEventCallback([this](Event& e)
{
OnEvent(e); // Application의 이벤트 핸들러로 전달
});
}
C++
복사
이 패턴을 사용하면 Window 클래스가 Application에 직접 의존하지 않으면서도 이벤트를 전달할 수 있습니다 (의존성 역전 원칙).
OnEvent: 이벤트 디스패칭
OnEvent() 함수는 발생한 이벤트를 적절한 핸들러로 라우팅하는 중앙 디스패처 역할을 합니다.
구현 구조
void EditorLayer::OnEvent(Event& e)
{
// EventDispatcher를 사용하여 이벤트 타입별로 분기
EventDispatcher dispatcher(e);
// 키 눌림 이벤트 처리
dispatcher.Dispatch<KeyPressedEvent>([this](KeyPressedEvent& event) -> bool
{
return OnKeyPressed(event);
});
// 키 떼기 이벤트 처리 (현재 미구현)
dispatcher.Dispatch<KeyReleasedEvent>([this](KeyReleasedEvent& event) -> bool
{
// TODO: 키 떼기 이벤트 처리
return false;
});
// 마우스 이동 이벤트 처리
dispatcher.Dispatch<MouseMovedEvent>([this](MouseMovedEvent& event) -> bool
{
// 현재는 처리하지 않고 true 반환
return true;
});
// 이벤트가 처리되지 않았다면 ImGui 에디터로 전달
if (!e.Handled)
{
mImguiEditor->OnEvent(e);
}
}
C++
복사
EventDispatcher 동작 원리
EventDispatcher는 이벤트 타입을 확인하고 적절한 핸들러를 호출하는 헬퍼 클래스입니다.
class EventDispatcher
{
public:
EventDispatcher(Event& event)
: mEvent(event)
{
}
template<typename T, typename F>
bool Dispatch(const F& func)
{
// 이벤트 타입이 T와 일치하는지 확인
if (mEvent.GetEventType() == T::GetStaticType())
{
// 타입 캐스팅 후 핸들러 호출
mEvent.Handled = func(static_cast<T&>(mEvent));
return true;
}
return false;
}
private:
Event& mEvent;
};
C++
복사
동작 예시
// KeyPressedEvent가 발생한 경우
Event& e = someKeyPressedEvent; // 실제로는 KeyPressedEvent 타입
EventDispatcher dispatcher(e);
// 첫 번째 Dispatch 호출
dispatcher.Dispatch<KeyPressedEvent>([](auto& event) {
// 타입 일치! 이 람다가 실행됨
return OnKeyPressed(event);
});
// 두 번째 Dispatch 호출
dispatcher.Dispatch<MouseMovedEvent>([](auto& event) {
// 타입 불일치, 이 람다는 실행되지 않음
return false;
});
C++
복사
이벤트 처리 우선순위
이벤트는 다음과 같은 우선순위로 처리됩니다.
1.
EditorLayer의 핸들러: 에디터 전용 기능 (기즈모 모드 전환 등)
2.
ImGui 에디터: UI 위젯과의 상호작용
3.
기타 레이어: 게임 로직 레이어 등
이벤트가 한 단계에서 처리되면 (Handled = true), 다음 단계로 전파되지 않습니다. 이를 통해 UI 위에서 클릭했을 때 게임 오브젝트가 선택되는 것을 방지합니다.
OnKeyPressed: 기즈모 모드 전환
OnKeyPressed() 함수는 키 입력에 따라 ImGuizmo의 조작 모드를 변경하거나 에디터 기능을 실행합니다.
전체 구현
bool EditorLayer::OnKeyPressed(KeyPressedEvent& e)
{
// 키 반복 이벤트는 무시 (한 번만 처리)
if (e.IsRepeat())
return false;
// 수정 키(Modifier Keys) 상태 확인
bool control = Input::IsKeyPressed(Key::LeftControl) ||
Input::IsKeyPressed(Key::RightControl);
bool shift = Input::IsKeyPressed(Key::LeftShift) ||
Input::IsKeyPressed(Key::RightShift);
// 입력된 키 코드 확인
switch (e.GetKeyCode())
{
case Key::Q:
{
// 기즈모 사용 중이 아니면 기즈모 비활성화
if (!ImGuizmo::IsUsing())
SetGuizmoType(-1);
break;
}
case Key::W:
{
// 기즈모 사용 중이 아니면 이동 모드로 전환
if (!ImGuizmo::IsUsing())
SetGuizmoType(ImGuizmo::OPERATION::TRANSLATE);
break;
}
case Key::E:
{
// 기즈모 사용 중이 아니면 회전 모드로 전환
if (!ImGuizmo::IsUsing())
SetGuizmoType(ImGuizmo::OPERATION::ROTATE);
break;
}
case Key::R:
{
// Ctrl+R: 스크립트 리로드 (현재 주석 처리)
if (control)
{
// ScriptEngine::ReloadAssembly();
}
// R: 크기 조절 모드로 전환
else if (!ImGuizmo::IsUsing())
{
SetGuizmoType(ImGuizmo::OPERATION::SCALE);
}
break;
}
}
return false;
}
C++
복사
키 반복 필터링
if (e.IsRepeat())
return false;
C++
복사
키를 누르고 있을 때 발생하는 GLFW_REPEAT 이벤트를 무시합니다. 기즈모 모드 전환은 키를 누르는 순간에만 한 번 실행되어야 하므로, 반복 이벤트를 처리하면 불필요한 중복 호출이 발생합니다.
키 반복을 필터링하지 않으면?
수정 키 감지
bool control = Input::IsKeyPressed(Key::LeftControl) ||
Input::IsKeyPressed(Key::RightControl);
bool shift = Input::IsKeyPressed(Key::LeftShift) ||
Input::IsKeyPressed(Key::RightShift);
C++
복사
Ctrl, Shift와 같은 수정 키의 상태를 확인합니다. 이를 통해 단축키 조합을 구현할 수 있습니다.
수정 키 조합 예시
•
W: 이동 모드
•
Shift+W: 로컬 좌표계로 이동 모드 (향후 구현 가능)
•
Ctrl+S: 씬 저장
•
Ctrl+Z: 실행 취소
•
Ctrl+Shift+Z: 다시 실행
ImGuizmo 사용 중 체크
if (!ImGuizmo::IsUsing())
SetGuizmoType(ImGuizmo::OPERATION::TRANSLATE);
C++
복사
ImGuizmo::IsUsing()은 사용자가 현재 기즈모를 드래그하여 오브젝트를 조작 중인지 확인합니다.
왜 이 체크가 필요한가?
사용자가 오브젝트를 드래그하는 도중에 모드가 갑자기 바뀌면 매우 혼란스럽습니다.
기즈모 모드 전환 시스템
SetGuizmoType() 함수는 ImGuizmo의 현재 조작 모드를 설정합니다.
구현 예시
class EditorLayer
{
public:
void SetGuizmoType(int type)
{
mGuizmoType = type;
}
int GetGuizmoType() const
{
return mGuizmoType;
}
void OnImGuiRender()
{
// ImGuizmo 렌더링
if (mGuizmoType != -1 && mSelectedEntity.IsValid())
{
ImGuizmo::SetOrthographic(false);
ImGuizmo::SetDrawlist();
ImGuizmo::SetRect(
mViewportBounds[0].x,
mViewportBounds[0].y,
mViewportBounds[1].x - mViewportBounds[0].x,
mViewportBounds[1].y - mViewportBounds[0].y
);
// 카메라 행렬
const Matrix& cameraProjection = mCamera.GetProjectionMatrix();
Matrix cameraView = mCamera.GetViewMatrix();
// 선택된 엔티티의 Transform
auto& tc = mSelectedEntity.GetComponent<TransformComponent>();
Matrix transform = tc.GetTransform();
// 기즈모 렌더링 및 조작
ImGuizmo::Manipulate(
glm::value_ptr(cameraView),
glm::value_ptr(cameraProjection),
(ImGuizmo::OPERATION)mGuizmoType,
ImGuizmo::LOCAL,
glm::value_ptr(transform)
);
// 변경된 Transform 적용
if (ImGuizmo::IsUsing())
{
Matrix localTransform = transform;
// Transform 분해
Vector3 position, rotation, scale;
DecomposeTransform(localTransform, position, rotation, scale);
// 컴포넌트 업데이트
tc.Position = position;
tc.Rotation = rotation;
tc.Scale = scale;
}
}
}
private:
int mGuizmoType = -1; // -1: 비활성화, 그 외: ImGuizmo::OPERATION
Entity mSelectedEntity;
};
C++
복사
ImGuizmo 조작 모드 상세
TRANSLATE (이동)
SetGuizmoType(ImGuizmo::OPERATION::TRANSLATE);
C++
복사
3개의 화살표 축이 표시됩니다.
•
빨간 화살표: X축 이동
•
초록 화살표: Y축 이동
•
파란 화살표: Z축 이동
•
평면 사각형: 두 축으로 이루어진 평면 이동 (XY, YZ, XZ)
ROTATE (회전)
SetGuizmoType(ImGuizmo::OPERATION::ROTATE);
C++
복사
3개의 원형 고리가 표시됩니다.
•
빨간 원: X축 중심 회전
•
초록 원: Y축 중심 회전
•
파란 원: Z축 중심 회전
•
흰색 외곽 원: 카메라 시점을 기준으로 자유 회전
SCALE (크기 조절)
SetGuizmoType(ImGuizmo::OPERATION::SCALE);
C++
복사
3개의 선과 큐브가 표시됩니다.
•
빨간 선: X축 크기 조절
•
초록 선: Y축 크기 조절
•
파란 선: Z축 크기 조절
•
중앙 큐브: 균등 크기 조절 (모든 축 동시에)
비활성화
SetGuizmoType(-1);
C++
복사
기즈모를 화면에서 제거합니다. 오브젝트 선택은 유지되지만 기즈모는 보이지 않습니다.
키 바인딩 테이블
에디터에서 사용하는 모든 키 조합을 정리하면 다음과 같습니다.
키 | 수정 키 | 기능 | 설명 |
Q | 없음 | 기즈모 비활성화 | 선택된 오브젝트는 유지하되 기즈모를 숨깁니다. 카메라 조작 시 유용합니다. |
W | 없음 | 이동 모드 | 오브젝트를 X, Y, Z 축을 따라 이동시킵니다. 가장 자주 사용되는 모드입니다. |
E | 없음 | 회전 모드 | 오브젝트를 각 축을 중심으로 회전시킵니다. 캐릭터나 소품의 방향을 조정할 때 사용합니다. |
R | 없음 | 크기 조절 모드 | 오브젝트의 크기를 각 축별로 조절합니다. 균등 크기 조절도 가능합니다. |
R | Ctrl | 스크립트 리로드 | 게임 스크립트를 다시 로드합니다. 현재는 주석 처리되어 있으며 향후 구현 예정입니다. |
향후 확장 가능한 단축키
키 조합 | 제안 기능 | 설명 |
Shift+W/E/R | 로컬/월드 좌표계 전환 | Shift를 누른 상태에서 모드 키를 누르면 로컬/월드 좌표계 토글 |
Ctrl+D | 선택 오브젝트 복제 | 현재 선택된 오브젝트를 같은 위치에 복제 |
Delete | 선택 오브젝트 삭제 | 선택된 오브젝트를 씬에서 제거 |
F | 선택 오브젝트로 포커스 | 카메라를 선택된 오브젝트 중심으로 이동 |
Ctrl+G | 그리드 스냅 토글 | 그리드 스냅 기능 활성화/비활성화 |
Transform 행렬 분해와 적용
ImGuizmo가 반환하는 변환 행렬을 위치, 회전, 스케일로 분해하는 과정입니다.
Transform 분해 함수
bool DecomposeTransform(
const Matrix& transform,
Vector3& outPosition,
Vector3& outRotation,
Vector3& outScale)
{
using namespace glm;
// 변환 행렬을 GLM 행렬로 변환
mat4 localMatrix = transform;
// 1. 위치 추출 (4번째 열)
outPosition = vec3(localMatrix[3]);
// 2. 스케일 추출
vec3 scale;
scale.x = length(vec3(localMatrix[0]));
scale.y = length(vec3(localMatrix[1]));
scale.z = length(vec3(localMatrix[2]));
outScale = scale;
// 3. 스케일 제거하여 회전 행렬 추출
mat3 rotationMatrix(
vec3(localMatrix[0]) / scale.x,
vec3(localMatrix[1]) / scale.y,
vec3(localMatrix[2]) / scale.z
);
// 4. 회전 행렬을 오일러 각으로 변환
outRotation = degrees(eulerAngles(quat_cast(rotationMatrix)));
return true;
}
C++
복사
변환 행렬 구조
4x4 변환 행렬은 다음과 같은 구조를 가집니다.
분해 과정 상세
1단계: 위치 추출
위치는 행렬의 4번째 열(인덱스 3)에 직접 저장되어 있습니다.
outPosition = vec3(localMatrix[3]);
// localMatrix[3][0] = Px
// localMatrix[3][1] = Py
// localMatrix[3][2] = Pz
C++
복사
2단계: 스케일 추출
각 축 벡터의 길이가 스케일 값입니다.
scale.x = length(vec3(localMatrix[0]));
// = sqrt(Xx² + Xy² + Xz²)
scale.y = length(vec3(localMatrix[1]));
// = sqrt(Yx² + Yy² + Yz²)
scale.z = length(vec3(localMatrix[2]));
// = sqrt(Zx² + Zy² + Zz²)
C++
복사
3단계: 회전 행렬 추출
스케일을 제거하여 순수한 회전 행렬을 얻습니다.
mat3 rotationMatrix(
vec3(localMatrix[0]) / scale.x, // 정규화된 X축
vec3(localMatrix[1]) / scale.y, // 정규화된 Y축
vec3(localMatrix[2]) / scale.z // 정규화된 Z축
);
C++
복사
4단계: 오일러 각 변환
회전 행렬을 쿼터니언으로 변환한 후 오일러 각으로 변환합니다.
// 회전 행렬 → 쿼터니언
quat rotation = quat_cast(rotationMatrix);
// 쿼터니언 → 오일러 각 (라디안)
vec3 euler = eulerAngles(rotation);
// 라디안 → 도(Degree)
outRotation = degrees(euler);
C++
복사
ImGuizmo 통합 전체 예제
에디터 레이어에서 ImGuizmo를 완전히 통합한 예제입니다.
class EditorLayer : public Layer
{
public:
void OnAttach() override
{
// 에디터 초기화
mGuizmoType = ImGuizmo::OPERATION::TRANSLATE;
mGuizmoMode = ImGuizmo::MODE::LOCAL;
}
void OnUpdate(float deltaTime) override
{
// 씬 업데이트
mScene->Update(deltaTime);
}
void OnImGuiRender() override
{
// 뷰포트 렌더링
RenderViewport();
// ImGuizmo 렌더링
RenderGuizmo();
// 프로퍼티 패널
RenderProperties();
}
void OnEvent(Event& e) override
{
EventDispatcher dispatcher(e);
// 키 입력 처리
dispatcher.Dispatch<KeyPressedEvent>([this](KeyPressedEvent& event) -> bool
{
return OnKeyPressed(event);
});
// 마우스 클릭 처리
dispatcher.Dispatch<MouseButtonPressedEvent>([this](MouseButtonPressedEvent& event) -> bool
{
return OnMouseButtonPressed(event);
});
// 처리되지 않은 이벤트는 ImGui로 전달
if (!e.Handled)
{
mImGuiLayer->OnEvent(e);
}
}
private:
bool OnKeyPressed(KeyPressedEvent& e)
{
// 키 반복 무시
if (e.IsRepeat())
return false;
// 수정 키 상태
bool control = Input::IsKeyPressed(Key::LeftControl) ||
Input::IsKeyPressed(Key::RightControl);
bool shift = Input::IsKeyPressed(Key::LeftShift) ||
Input::IsKeyPressed(Key::RightShift);
// 키 코드 처리
switch (e.GetKeyCode())
{
// 기즈모 모드 전환
case Key::Q:
if (!ImGuizmo::IsUsing())
mGuizmoType = -1;
break;
case Key::W:
if (!ImGuizmo::IsUsing())
mGuizmoType = ImGuizmo::OPERATION::TRANSLATE;
break;
case Key::E:
if (!ImGuizmo::IsUsing())
mGuizmoType = ImGuizmo::OPERATION::ROTATE;
break;
case Key::R:
if (control)
{
// Ctrl+R: 스크립트 리로드
ScriptEngine::ReloadAssembly();
}
else if (!ImGuizmo::IsUsing())
{
mGuizmoType = ImGuizmo::OPERATION::SCALE;
}
break;
// 좌표계 전환 (향후 구현)
case Key::T:
if (shift)
{
mGuizmoMode = (mGuizmoMode == ImGuizmo::MODE::LOCAL)
? ImGuizmo::MODE::WORLD
: ImGuizmo::MODE::LOCAL;
}
break;
// 오브젝트 조작
case Key::D:
if (control)
{
DuplicateSelectedEntity();
}
break;
case Key::Delete:
DeleteSelectedEntity();
break;
// 카메라 포커스
case Key::F:
FocusOnSelectedEntity();
break;
}
return false;
}
void RenderGuizmo()
{
// 선택된 엔티티가 없으면 리턴
if (!mSelectedEntity || mGuizmoType == -1)
return;
// ImGuizmo 설정
ImGuizmo::SetOrthographic(false);
ImGuizmo::SetDrawlist();
// 뷰포트 영역 설정
ImGuizmo::SetRect(
mViewportBounds[0].x,
mViewportBounds[0].y,
mViewportBounds[1].x - mViewportBounds[0].x,
mViewportBounds[1].y - mViewportBounds[0].y
);
// 카메라 행렬
const Matrix& cameraProjection = mEditorCamera.GetProjectionMatrix();
Matrix cameraView = mEditorCamera.GetViewMatrix();
// 엔티티 Transform
auto& tc = mSelectedEntity.GetComponent<TransformComponent>();
Matrix transform = tc.GetTransform();
// 스냅 설정
bool snap = Input::IsKeyPressed(Key::LeftControl);
float snapValue = 0.5f; // 이동 스냅
if (mGuizmoType == ImGuizmo::OPERATION::ROTATE)
snapValue = 45.0f; // 회전 스냅
else if (mGuizmoType == ImGuizmo::OPERATION::SCALE)
snapValue = 0.1f; // 크기 스냅
float snapValues[3] = { snapValue, snapValue, snapValue };
// 기즈모 렌더링 및 조작
ImGuizmo::Manipulate(
glm::value_ptr(cameraView),
glm::value_ptr(cameraProjection),
(ImGuizmo::OPERATION)mGuizmoType,
mGuizmoMode,
glm::value_ptr(transform),
nullptr,
snap ? snapValues : nullptr
);
// Transform 적용
if (ImGuizmo::IsUsing())
{
Vector3 position, rotation, scale;
DecomposeTransform(transform, position, rotation, scale);
// 컴포넌트 업데이트
tc.Position = position;
tc.Rotation = rotation;
tc.Scale = scale;
}
}
void DuplicateSelectedEntity()
{
if (mSelectedEntity)
{
Entity newEntity = mScene->DuplicateEntity(mSelectedEntity);
mSelectedEntity = newEntity;
}
}
void DeleteSelectedEntity()
{
if (mSelectedEntity)
{
mScene->DestroyEntity(mSelectedEntity);
mSelectedEntity = {};
}
}
void FocusOnSelectedEntity()
{
if (mSelectedEntity)
{
auto& tc = mSelectedEntity.GetComponent<TransformComponent>();
mEditorCamera.FocusOn(tc.Position);
}
}
private:
// 씬
Scene* mScene = nullptr;
Entity mSelectedEntity;
// 에디터 카메라
EditorCamera mEditorCamera;
// ImGuizmo 상태
int mGuizmoType = ImGuizmo::OPERATION::TRANSLATE;
ImGuizmo::MODE mGuizmoMode = ImGuizmo::MODE::LOCAL;
// 뷰포트
Vector2 mViewportBounds[2];
// 레이어
ImGuiLayer* mImGuiLayer = nullptr;
};
C++
복사
성능 고려사항
이벤트 처리 최적화
불필요한 이벤트 필터링
// 나쁜 예: 모든 이벤트를 처리
void OnEvent(Event& e)
{
EventDispatcher dispatcher(e);
dispatcher.Dispatch<KeyPressedEvent>(...);
dispatcher.Dispatch<KeyReleasedEvent>(...);
dispatcher.Dispatch<MouseMovedEvent>(...);
// ... 수십 개의 Dispatch 호출
}
// 좋은 예: 필요한 이벤트만 처리
void OnEvent(Event& e)
{
// 카테고리 필터링
if (e.IsInCategory(EventCategory::Keyboard))
{
EventDispatcher dispatcher(e);
dispatcher.Dispatch<KeyPressedEvent>(...);
dispatcher.Dispatch<KeyReleasedEvent>(...);
}
}
C++
복사
ImGuizmo 렌더링 최적화
조건부 렌더링
void RenderGuizmo()
{
// Early return으로 불필요한 연산 방지
if (!mSelectedEntity || mGuizmoType == -1)
return;
if (!mViewportHovered)
return;
// ImGuizmo 렌더링...
}
C++
복사
행렬 캐싱
// 매 프레임 계산하지 않고 카메라가 변경될 때만 계산
class EditorCamera
{
public:
const Matrix& GetViewMatrix()
{
if (mViewDirty)
{
RecalculateViewMatrix();
mViewDirty = false;
}
return mViewMatrix;
}
private:
Matrix mViewMatrix;
bool mViewDirty = true;
};
C++
복사
디버깅 팁
키 입력 로깅
bool OnKeyPressed(KeyPressedEvent& e)
{
LOG_INFO("Key Pressed: {} (Code: {})",
KeyCodeToString(e.GetKeyCode()),
e.GetKeyCode());
if (e.IsRepeat())
{
LOG_INFO(" (Repeat event, ignoring)");
return false;
}
// 키 처리...
}
C++
복사
ImGuizmo 상태 확인
void RenderGuizmo()
{
// ... ImGuizmo 렌더링 ...
// 디버그 정보 표시
ImGui::Begin("Gizmo Debug");
ImGui::Text("Gizmo Type: %s", GuizmoTypeToString(mGuizmoType));
ImGui::Text("Is Using: %s", ImGuizmo::IsUsing() ? "Yes" : "No");
ImGui::Text("Is Over: %s", ImGuizmo::IsOver() ? "Yes" : "No");
ImGui::End();
}
C++
복사
요약
ImGuizmo 키 입력 시스템은 다음과 같은 계층적 구조로 동작합니다.
단계 | 역할 |
SetKeyPressed | GLFW 원시 입력을 KeyPressedEvent/KeyReleasedEvent 객체로 변환합니다. |
OnEvent | 이벤트를 적절한 핸들러로 라우팅합니다. EventDispatcher를 사용하여 타입 안전성을 보장합니다. |
OnKeyPressed | 키 조합을 확인하고 ImGuizmo 모드를 설정합니다. 반복 이벤트를 필터링하고 사용 중 체크를 수행합니다. |
ImGuizmo::Manipulate | 설정된 모드에 따라 기즈모를 렌더링하고 사용자 입력을 처리합니다. Transform 행렬을 반환합니다. |
Transform 분해 및 적용 | 변환 행렬을 위치, 회전, 스케일로 분해하여 엔티티 컴포넌트에 적용합니다. |
이 시스템은 Unity나 Unreal Engine과 유사한 직관적인 오브젝트 조작 경험을 제공하며, 확장 가능한 구조로 설계되어 새로운 기능을 쉽게 추가할 수 있습니다.
