-
[DirectX11] GameObject,Transform2024년 02월 12일
- 유니얼
-
작성자
-
2024.02.12.:42
728x90DirectX 11로 게임 엔진 아키텍처 만들기
게임 개발 과정에서 객체의 위치, 회전, 크기를 관리하는 것은 필수적입니다. 유니티 엔진에서는 이를 위해 Transform 컴포넌트를 사용하여 각 게임 오브젝트의 공간적 속성을 정의합니다. 본 글에서는 DirectX 11 환경에서 유니티의 GameObject 및 Transform 컴포넌트를 모델링하는 방법에 대해 설명합니다. 이러한 구현을 통해, 개발자는 DirectX 프로젝트 내에서 더욱 구조화되고 유연한 게임 오브젝트 관리 시스템을 구축할 수 있습니다.
참고강의 링크:
1. GameObject 클래스 구현
기존의 Game 클래스는 렌더링 파이프라인 설정, 셰이더 관리, 상태 관리 등 다양한 작업을 모두 처리했습니다. 이로 인해 코드가 지나치게 복잡해지고, 개별 게임 오브젝트를 관리하기 어려웠습니다. 이 문제를 해결하기 위해 GameObject 클래스를 새롭게 도입했습니다.
GameObject 클래스는 게임 내의 모든 엔티티의 기본이 되는 클래스로, 각 객체의 렌더링과 업데이트 로직을 캡슐화합니다. 이 클래스는 게임 오브젝트가 가지는 기본적인 속성과 동작을 정의하며, 여기에는 기하학적 데이터, 셰이더, 텍스처 및 렌더링 상태가 포함됩니다. 또한, 부모-자식 관계를 통해 계층적인 변환을 적용할 수 있도록 설계했습니다.
GameObject 클래스의 핵심 요소
- 기하 구조와 버퍼: GameObject는 자신의 모델을 구성하는 정점과 인덱스 데이터를 관리합니다. 이를 위해 VertexBuffer와 IndexBuffer 인스턴스를 포함합니다.
- 셰이더와 상태: 각 오브젝트는 자신만의 셰이더 프로그램(VertexShader, PixelShader)과 렌더링 상태(RasterizerState, BlendState)를 가집니다.
- 텍스처와 샘플러: 오브젝트별로 하나 이상의 텍스처(Texture)와 샘플러 상태(SamplerState)를 관리할 수 있습니다.
- 변환 정보: 위치, 회전, 크기 등의 변환 정보를 관리하기 위해 Transform 클래스 인스턴스를 포함합니다. 이를 통해 오브젝트의 월드 변환 행렬을 계산합니다.
#pragma once #include "Component.h" // 기본 컴포넌트 클래스 포함 class GameObject { public: // 생성자: DirectX 디바이스와 컨텍스트를 받아 초기화 GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext); // 소멸자 ~GameObject(); // 업데이트 메서드: 게임 오브젝트의 로직 업데이트를 처리 void Update(); // 렌더 메서드: 게임 오브젝트를 화면에 그림 void Render(shared_ptr<Pipeline> pipeline); private: ComPtr<ID3D11Device> _device; // DirectX 디바이스 // 게임 오브젝트의 기하학적 데이터와 렌더링에 필요한 자원들 shared_ptr<Geometry<VertexTextureData>> _geometry; shared_ptr<VertexBuffer> _vertexBuffer; shared_ptr<IndexBuffer> _indexBuffer; shared_ptr<InputLayout> _inputLayout; // 쉐이더 및 렌더링 상태 shared_ptr<VertexShader> _vertexShader; // 정점 쉐이더 shared_ptr<ResterizerState> _rasterizerState; // 래스터라이저 상태 shared_ptr<PixelShader> _pixelShader; // 픽셀 쉐이더 shared_ptr<Texture> _texture1; // 텍스처 shared_ptr<SamplerState> _samplerState; // 샘플러 상태 shared_ptr<BlendState> _blendState; // 블렌드 상태 // 변환 데이터와 상수 버퍼 TransformData _transformData; // 게임 오브젝트의 변환 데이터 shared_ptr<ConstantBuffer<TransformData>> _constantBuffer; // 상수 버퍼 // 게임 오브젝트의 위치 및 회전 관리를 위한 Transform 컴포넌트 shared_ptr<Transform> _transform; shared_ptr<Transform> _parent; };
#include "pch.h" #include "GameObject.h" // 생성자에서 게임 오브젝트의 초기화를 진행합니다. // 이 과정에서 기하 구조, 버퍼, 쉐이더, 텍스처 등을 설정합니다. GameObject::GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext) : _device(device) { _geometry = make_shared<Geometry<VertexTextureData>>(); GeometryHelper::CreateRectangle(_geometry); // 사각형 모양의 기하 구조 생성 _vertexBuffer = make_shared<VertexBuffer>(_device); _vertexBuffer->Create(_geometry->GetVertices()); // 정점 버퍼 생성 _indexBuffer = make_shared<IndexBuffer>(_device); _indexBuffer->Create(_geometry->GetIndices()); // 인덱스 버퍼 생성 _vertexShader = make_shared<VertexShader>(_device); _vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0"); // 정점 쉐이더 생성 _inputLayout = make_shared<InputLayout>(_device); _inputLayout->Create(VertexTextureData::descs, _vertexShader->GetBlob()); // 입력 레이아웃 생성 _pixelShader = make_shared<PixelShader>(_device); _pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0"); // 픽셀 쉐이더 생성 _rasterizerState = make_shared<ResterizerState>(_device); _rasterizerState->Create(); // 래스터라이저 상태 생성 _blendState = make_shared<BlendState>(_device); _blendState->Create(); // 블렌드 상태 생성 _constantBuffer = make_shared<ConstantBuffer<TransformData>>(_device, deviceContext); _constantBuffer->Create(); // 상수 버퍼 생성 _texture1 = make_shared<Texture>(_device); _texture1->Create(L"UnityLogo.png"); // 텍스처 생성 _samplerState = make_shared<SamplerState>(_device); _samplerState->Create(); // 샘플러 상태 생성 _parent->AddChildren(_transform); // 부모-자식 관계 설정 _transform->SetParent(_parent); }
2. Transform 컴포넌트
유니티 엔진에서 Transform 컴포넌트는 게임 오브젝트의 위치, 회전, 크기를 관리하는 핵심 요소입니다. 이 컴포넌트는 오브젝트의 공간적인 특성을 정의하고, 계층적인 구조를 통해 씬 내에서의 관계를 설정합니다. DirectX 11 프로젝트에서 유니티 스타일의 Transform 컴포넌트를 구현하는 과정은 게임 개발의 유연성과 코드의 재사용성을 높이는 중요한 단계입니다.
Transform 클래스 설계
Transform 클래스는 Component 클래스를 상속받아, 게임 오브젝트의 기본 변환 속성을 구현합니다. 이 클래스는 로컬 및 월드 변환을 관리하며, 계층적인 부모-자식 관계를 통해 복잡한 씬 구조를 간단하게 표현할 수 있도록 설계되었습니다.
#pragma once #include "Component.h" // 컴포넌트 클래스 포함 class Transform : public Component { public: // 생성자와 소멸자 Transform(); ~Transform(); // 컴포넌트 기본 함수들 virtual void Init() override; virtual void Update() override; // 트랜스폼 업데이트 함수 void UpdateTransform(); // 로컬 변환 관련 함수들 Vec3 GetLocalScale() { return _localScale; } void SetLocalScale(const Vec3& localScale) { _localScale = localScale; UpdateTransform(); } Vec3 GetLocalRotation() { return _localRotation; } void SetLocalRotation(const Vec3& localRotation) { _localRotation = localRotation; UpdateTransform(); } Vec3 GetLocalPosition() { return _localPosition; } void SetLocalPosition(const Vec3& localPosition) { _localPosition = localPosition; UpdateTransform(); } // 월드 변환 관련 함수들 Vec3 GetWorldScale() { return _scale; } void SetWorldScale(const Vec3& worldScale); Vec3 GetWorldRotation() { return _rotation; } void SetWorldRotation(const Vec3& worldRotation); Vec3 GetWorldPosition() { return _position; } void SetWorldPosition(const Vec3& worldPosition); // 월드 행렬 반환 Matrix GetWorldMatrix() { return _matWorld; } // 부모-자식 관계 관련 함수들 bool HasParent() { return _parent != nullptr; } shared_ptr<Transform> GetParent() { return _parent; } void SetParent(shared_ptr<Transform> parent) { _parent = parent; } vector<shared_ptr<Transform>> GetChildren() { return _children; } void AddChildren(shared_ptr<Transform> child) { _children.push_back(child); } private: // 로컬 변환 데이터 Vec3 _localScale = { 1,1,1 }; Vec3 _localRotation = { 0,0,0 }; Vec3 _localPosition = { 0,0,0 }; // 로컬 및 월드 행렬 Matrix _matLocal = Matrix::Identity; Matrix _matWorld = Matrix::Identity; // 캐시된 월드 변환 데이터 Vec3 _scale; Vec3 _rotation; Vec3 _position; // 방향 벡터 Vec3 _right; Vec3 _up; Vec3 _look; // 부모와 자식 트랜스폼 shared_ptr<Transform> _parent; vector<shared_ptr<Transform>> _children; };
변환 로직 구현
Transform 클래스는 로컬 변환과 월드 변환을 분리하여 관리합니다. 각 게임 오브젝트는 자신만의 로컬 변환을 가지며, 이는 부모 오브젝트의 변환에 의해 상대적인 월드 변환으로 확장됩니다.
- 로컬 변환: 게임 오브젝트의 개별적인 위치, 회전, 크기를 설정합니다.
- 월드 변환: 계층적 구조를 고려한 최종 변환을 계산합니다. 이는 부모 오브젝트의 변환에 자신의 로컬 변환을 적용하여 얻어집니다.
#include "pch.h" #include "Transform.h" Transform::Transform() { // 생성자에서는 특별한 초기화 로직이 없습니다. } Transform::~Transform() { // 소멸자에서도 특별한 처리가 없습니다. } void Transform::Init() { // 초기화 함수에서도 현재는 특별한 처리가 없습니다. } void Transform::Update() { // 업데이트 함수에서는 현재 특별한 처리가 없습니다. } Vec3 ToEulerAngles(Quaternion q) { Vec3 angles; // roll (x-axis rotation) double sinr_cosp = 2 * (q.w * q.x + q.y * q.z); double cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y); angles.x = std::atan2(sinr_cosp, cosr_cosp); // pitch (y-axis rotation) double sinp = std::sqrt(1 + 2 * (q.w * q.y - q.x * q.z)); double cosp = std::sqrt(1 - 2 * (q.w * q.y - q.x * q.z)); angles.y = 2 * std::atan2(sinp, cosp) - 3.17159f / 2; // yaw (z-axis rotation) double siny_cosp = 2 * (q.w * q.z + q.x * q.y); double cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z); angles.z = std::atan2(siny_cosp, cosy_cosp); return angles; } void Transform::UpdateTransform() { // 로컬 변환 데이터를 바탕으로 로컬 변환 행렬을 계산합니다. _localPosition.x += 0.001f; // 예제로, 매 업데이트마다 x 위치를 조금씩 이동시킵니다. // 스케일, 회전, 이동 행렬을 계산하고, 이를 조합하여 로컬 행렬을 생성합니다. Matrix matScale = Matrix::CreateScale(_localScale / 3); Matrix matRotation = Matrix::CreateRotationX(_localRotation.x) * Matrix::CreateRotationY(_localRotation.y) * Matrix::CreateRotationZ(_localRotation.z); Matrix matTranslation = Matrix::CreateTranslation(_localPosition); _matLocal = matScale * matRotation * matTranslation; // 부모 트랜스폼이 있을 경우, 부모의 월드 행렬과 조합하여 최종 월드 행렬을 계산합니다. if (HasParent()) { _matWorld = _matLocal * _parent->GetWorldMatrix(); } else { _matWorld = _matLocal; } // 월드 행렬을 분해하여 스케일, 회전(쿼터니언), 위치 데이터를 추출하고, 이를 바탕으로 방향 벡터를 계산합니다. Quaternion quat; _matWorld.Decompose(_scale, quat, _position); _rotation = ToEulerAngles(quat); // 쿼터니언을 오일러 각도로 변환합니다. _right = Vec3::TransformNormal(Vec3::Right, _matWorld); // 오른쪽 방향 벡터 _up = Vec3::TransformNormal(Vec3::Up, _matWorld); // 위쪽 방향 벡터 _look = Vec3::TransformNormal(Vec3::Backward, _matWorld); // 앞쪽 방향 벡터 // 자식 트랜스폼들에 대해서도 업데이트를 호출하여 계층적 변환을 적용합니다. for (const shared_ptr<Transform>& child : _children) { child->UpdateTransform(); } } // 월드 좌표계에서의 스케일을 설정하는 메서드입니다. void Transform::SetWorldScale(const Vec3& worldScale) { if (HasParent()) { Vec3 parentScale = _parent->GetWorldScale(); // 부모 스케일을 고려하여 로컬 스케일 계산 Vec3 scale = worldScale; scale.x /= parentScale.x; scale.y /= parentScale.y; scale.z /= parentScale.z; SetLocalScale(scale); } else { SetLocalScale(worldScale); } } // 월드 좌표계에서의 회전을 설정하는 메서드입니다. void Transform::SetWorldRotation(const Vec3& worldRotation) { if (HasParent()) { // 부모의 월드 행렬을 역행렬로 변환하여, 월드 좌표계에서 부모 로컬 좌표계로의 변환 행렬을 얻습니다. Matrix worldToParentLocalMatrix = _parent->GetWorldMatrix().Invert(); // 월드 회전 값을 부모의 로컬 좌표계로 변환합니다. Vec3 rotation; rotation.Transform(worldRotation, worldToParentLocalMatrix); // 계산된 회전 값을 현재 트랜스폼의 로컬 위치로 설정합니다. SetLocalPosition(rotation); } else { SetLocalRotation(worldRotation); } } // 월드 좌표계에서의 위치를 설정하는 메서드입니다. void Transform::SetWorldPosition(const Vec3& worldPosition) { if (HasParent()) { // 부모의 월드 행렬을 역행렬로 변환하여, 월드 좌표계에서 부모 로컬 좌표계로의 변환 행렬을 얻습니다. Matrix worldToParentLocalMatrix = _parent->GetWorldMatrix().Invert(); // 월드 위치 값을 부모의 로컬 좌표계로 변환합니다. Vec3 position; position.Transform(worldPosition, worldToParentLocalMatrix); // 계산된 위치 값을 현재 트랜스폼의 로컬 위치로 설정합니다. } else { SetLocalPosition(worldPosition); } }
3.렌더링 및 업데이트 로직
GameObject의 Update 메서드에서는 게임 오브젝트의 상태를 갱신하며, Render 메서드에서는 현재 게임 오브젝트를 화면에 그립니다. 이 과정에서 Transform 컴포넌트의 현재 상태에 따라 오브젝트의 시각적 표현이 결정됩니다.
// 업데이트 메서드에서는 게임 오브젝트의 위치, 회전 등을 업데이트합니다. void GameObject::Update() { Vec3 pos = _parent->GetWorldPosition(); pos.x += 0.001f; // 예를 들어, 매 업데이트마다 x 위치를 조금씩 이동 _parent->SetWorldPosition(pos); Vec3 rot = _parent->GetWorldRotation(); rot.z += 0.001f; // z축 회전도 조금씩 적용 _parent->SetWorldRotation(rot); _transformData.matWorld = _transform->GetWorldMatrix(); // 월드 행렬 업데이트 _constantBuffer->CopyData(_transformData); // 상수 버퍼에 데이터 복사 } // 렌더 메서드에서는 설정된 파이프라인에 따라 게임 오브젝트를 렌더링합니다. void GameObject::Render(shared_ptr<Pipeline> pipeline) { PipelineInfo info; info.inputLayout = _inputLayout; // 입력 레이아웃 info.vertexShader = _vertexShader; // 정점 쉐이더 info.pixelShader = _pixelShader; // 픽셀 쉐이더 info.resterizerstate = _rasterizerState; // 래스터라이저 상태 info.blendState = _blendState; // 블렌드 상태 pipeline->UpdatePipeline(info); // 파이프라인 정보 업데이트 pipeline->SetVertexBuffer(_vertexBuffer); // 정점 버퍼 설정 pipeline->SetIndexBuffer(_indexBuffer); // 인덱스 버퍼 설정 pipeline->SetConstantBuffer(0, SS_VertexShader, _constantBuffer); // 상수 버퍼 설정 pipeline->SetTexture(0, SS_PixelShader, _texture1); // 텍스처 설정 pipeline->SetSamplerState(0, SS_None, _samplerState); // 샘플러 상태 설정 pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0); // 인덱스를 사용하여 기하 구조를 그림 }
결론
GameObject와 Transform 컴포넌트를 통한 구현 방식은 DirectX 11 프로젝트의 게임 오브젝트 관리를 유연하고 효율적으로 만들어 줍니다. 이를 통해 개발자는 씬의 구조를 더욱 명확하게 정의할 수 있으며, 다양한 렌더링 및 상호작용 기능을 쉽게 구현할 수 있게 됩니다. 이러한 접근 방식은 게임 개발의 복잡성을 관리하고, 코드의 재사용성을 극대화하는 데 중요한 역할을 합니다.
반응형다음글이전글이전 글이 없습니다.댓글