Company
교육 철학

Guizmo 이동, 회전, 스케일링

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과 유사한 직관적인 오브젝트 조작 경험을 제공하며, 확장 가능한 구조로 설계되어 새로운 기능을 쉽게 추가할 수 있습니다.