1.Mesh & Material
이제 MeshRenderer에 있는 렌더링관련 변수들을 Mesh에 사용하는 변수인지 Material에서 사용하는 변수인지 구분하여 클래스를 구현해주자
이때 Mesh는 물체의 모양이고 Material은 물체의 재질이라고 보면 된다.
이제 mesh에 해당하는 부분을 클래스로 만들어주자 이 클래스에는 기본 사각형을 만들어주는 함수를 추가해서 테스트할 수 있게 하자.
Mesh.h
#pragma once
#include "ResourceBase.h"
class Mesh : public ResourceBase
{
using Super = ResourceBase;
public:
Mesh(ComPtr<ID3D11Device> device);
virtual ~Mesh();
void CreateDefaultRectangle();
shared_ptr<VertexBuffer> GetVertexBuffer() { return _vertexBuffer; }
shared_ptr<IndexBuffer> GetIndexBuffer() { return _indexBuffer; }
private:
ComPtr<ID3D11Device> _device;
//Mesh
shared_ptr<Geometry<VertexTextureData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
//인덱스버퍼 이거도 Geometry에 포함
shared_ptr<IndexBuffer> _indexBuffer;
};
Mesh.cpp
#include "pch.h"
#include "Mesh.h"
Mesh::Mesh(ComPtr<ID3D11Device> device)
: Super(ResourceType::Mesh),_device(device)
{
}
Mesh::~Mesh()
{
}
void Mesh::CreateDefaultRectangle()
{
//정점정보 - 사각형 만들기
_geometry = make_shared<Geometry<VertexTextureData>>();
GeometryHelper::CreateRectangle(_geometry);
//정점버퍼
_vertexBuffer = make_shared<VertexBuffer>(_device);
_vertexBuffer->Create(_geometry->GetVertices());
//IndexBuffer
_indexBuffer = make_shared<IndexBuffer>(_device);
_indexBuffer->Create(_geometry->GetIndices());
}
이제 MeshRederer 클래스를 수정해주자
수정할 때 MeshRenderer에서 Mesh를 지정하고 가져올 수 도 있게 함수를 만들어주자
MeshRenderer.h
public:
void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
shared_ptr<Mesh> GetMesh() { return _mesh; }
private:
//Mesh
shared_ptr<Mesh> _mesh;
그리고 ResourceManager의 CreateDefaultMesh 함수를 통해 Mesh가 초기화될 수 있도록하자
ResourceManager.cpp
#include "pch.h"
#include "ResourceManager.h"
#include "Texture.h"
#include "Mesh.h"
#include "Shader.h"
#include "Material.h"
#include "Animation.h"
ResourceManager::ResourceManager(ComPtr<ID3D11Device> device)
:_device(device)
{
}
void ResourceManager::Init()
{
CreateDefaultTexture();
CreateDefaultMesh();
CreateDefaultShader();
CreateDefaultMaterial();
CreateDefaultAnimation();
}
void ResourceManager::CreateDefaultTexture()
{
{
auto texture = make_shared<Texture>(_device);
texture->SetName(L"Cat");
texture->Create(L"cat.png");
Add(texture->GetName(), texture);
}
}
void ResourceManager::CreateDefaultMesh()
{
//Mesh
shared_ptr<Mesh> mesh = make_shared<Mesh>(_device);
mesh->SetName(L"Rectangle");
mesh->CreateDefaultRectangle();
Add(mesh->GetName(), mesh);
}
void ResourceManager::CreateDefaultShader()
{
}
void ResourceManager::CreateDefaultMaterial()
{
}
void ResourceManager::CreateDefaultAnimation()
{
}
이제 직접 Mesh를 지정해보자 Mesh의 지정은 우리가 고양이그림을 지정해주는 부분인 SceneManager에서 지정해주면 된다.
SceneManager.cpp
//cat
shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
cat->GetOrAddTransform();
auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
cat->AddComponent(meshRenderer);
//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
//...
auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
meshRenderer->SetMesh(mesh);
scene->AddGameObject(cat);
}
다음은 Material 클래스와 Shader 클래스를 만들어 줄 것이다. Material 클래스에서 쉐이더에 넘겨주는 여러 인자와 shader 변수를 가지고 있도록하고 shader는 렌더링 과정에 있는 inputLayout - vertexShader - PixelShader를 가지오있도록 하자
Material.h
#pragma once
#include "ResourceBase.h"
class Shader;
class Texture;
class Material : public ResourceBase
{
using Super = ResourceBase;
public:
Material();
virtual ~Material();
auto GetShader() { return _shader; }
auto GetTexture() { return _texture; }
void SetShader(shared_ptr<Shader> shader) { _shader = shader; }
void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
private:
//,,
shared_ptr<Shader> _shader;
//쉐이더에 넘겨주는 여러 인자들
shared_ptr<Texture> _texture;
};
Shader.h
#pragma once
#include "ResourceBase.h"
class Shader : public ResourceBase
{
using Super = ResourceBase;
public:
Shader();
virtual ~Shader();
shared_ptr<InputLayout> GetInputLayout() { return _inputLayout; }
shared_ptr<VertexShader> GetVertexShader() { return _vertexShader; }
shared_ptr<PixelShader> GetPixelShader() { return _pixelShader; }
private:
friend class ResourceManager;
//Material
//쉐이더 넘겨줄 때 어떤모양으로 되어있는지
shared_ptr<InputLayout> _inputLayout;
//VS
shared_ptr<VertexShader> _vertexShader;
//PS
shared_ptr<PixelShader> _pixelShader;
//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
};
그리고 이에 맞게 기본 쉐이더와 Material을 초기화해줄 함수를 ResourceManager에서 구현해주자
ResourceManager.cpp
void ResourceManager::CreateDefaultShader()
{
auto vertexShader = make_shared<VertexShader>(_device);
vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");
//인풋레이아웃
/// <summary>
/// 입력이 어떻게 이뤄져있는지
/// </summary>
auto inputLayout = make_shared<InputLayout>(_device);
inputLayout->Create(VertexTextureData::descs, vertexShader->GetBlob());
auto pixelShader = make_shared<PixelShader>(_device);
pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");
//shader
shared_ptr<Shader> shader = make_shared<Shader>();
shader->SetName(L"Default");
shader->_vertexShader = vertexShader;
shader->_inputLayout = inputLayout;
shader->_pixelShader = pixelShader;
Add(shader->GetName(), shader);
}
void ResourceManager::CreateDefaultMaterial()
{
shared_ptr<Material> material = make_shared<Material>();
material->SetName(L"Default");
material->SetShader(Get<Shader>(L"Default"));
material->SetTexture(Get<Texture>(L"Cat"));
Add(material->GetName(), material);
}
MeshRenderer 클래스를 수정하고 변수들을 관리해줄 헬퍼함수를 추가해주자
MeshRenderer.h
#pragma once
#include "Component.h"
#include "Material.h"
#include "Shader.h"
class Mesh;
class Material;
class Shader;
class Texture;
class MeshRenderer : public Component
{
using Super = Component;
public:
MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
virtual ~MeshRenderer();
void SetMaterial(shared_ptr<Material> material) { _material = material; }
void SetShader(shared_ptr<Shader> shader) { _material->SetShader(shader); }
void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
void SetTexture(shared_ptr<Texture> texture) { _material->SetTexture(texture); }
auto GetMaterial() { return _material; }
auto GetVertextShader() { return GetMaterial()->GetShader()->GetVertexShader(); }
auto GetInputLayout() { return GetMaterial()->GetShader()->GetInputLayout(); }
auto GetPixelShader() { return GetMaterial()->GetShader()->GetPixelShader(); }
shared_ptr<Mesh> GetMesh() { return _mesh; }
shared_ptr<Texture> GetTexture() { return GetMaterial()->GetTexture(); }
private:
ComPtr<ID3D11Device> _device;
//이렇게하면 랜더매니저에서 meshRenderer의 변수 protected처럼 쓸수있음
friend class RenderManager;
//Mesh
shared_ptr<Mesh> _mesh;
//Material
shared_ptr<Material> _material;
};
그리고 고양이를 생성하는 SceneManager부분에서 오브젝트에 Material을 붙여주자
//cat
shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
cat->GetOrAddTransform();
auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
cat->AddComponent(meshRenderer);
//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
//...
//Material
auto material = RESOURCES->Get<Material>(L"Default");
meshRenderer->SetMaterial(material);
//Mesh 부분
auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
meshRenderer->SetMesh(mesh);
scene->AddGameObject(cat);
}
그리고 마지막으로 RenderManager에서 파이프라인부분을 수정해주자
RenderManager.cpp
void RenderManager::RenderObjects()
{
for (const shared_ptr<GameObject>& gameObject : _renderObjects)
{
shared_ptr<MeshRenderer> meshRenderer = gameObject->GetMeshRenderer();
if (meshRenderer == nullptr)
continue;
shared_ptr<Transform> transform = gameObject->GetTransform();
if (transform == nullptr)
continue;
//SRT
_transformData.matWorld = transform->GetWorldMatrix();
pushTransformData();
PipelineInfo info;
info.inputLayout = meshRenderer->GetInputLayout();
info.vertexShader = meshRenderer->GetVertextShader();
info.pixelShader = meshRenderer->GetPixelShader();
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
_pipeline->UpdatePipeline(info);
_pipeline->SetVertexBuffer(meshRenderer->GetMesh()->GetVertexBuffer());
_pipeline->SetIndexBuffer(meshRenderer->GetMesh()->GetIndexBuffer());
_pipeline->SetConstantBuffer(0, SS_VertexShader, _cameraBuffer);
_pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);
_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->GetTexture());
_pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
_pipeline->DrawIndexed(meshRenderer->GetMesh()->GetIndexBuffer()->GetCount(), 0, 0);
}
}
이렇게 Mesh와 Shader, Material을 리소스로 관리하여 우리가 중앙에서 관리해서 하나를 공유해서 쓸 수 있다.
2.Animation
이제 애니메이션을 구현해보자
애니메이션은 전체 이미지에서 개별의 이미지를 자른 다음 여러 이미지를 일정 간격으로 연속으로 보여주는 것으로
구현할 수 있다.
지금 우리는 전체에서 부분을 나눌 때 uv 매핑을 통해 영역을 구할 것이다.
일단 애니메이션의 기반이될 Animation 클래스를 만들어주자 이 클래스에서 구조체를 통해 어디서부터 어떻게 얼마나 그릴지를 정해줄 수 있도록 하고 실제 애니메이션을 하게될 텍스처, 애니메이션의 정보인 키프레임과 같은 변수들과 헬퍼 함수들을 만들어주자
Animation.h
#pragma once
#include "ResourceBase.h"
struct Keyframe
{
//어디서 부터 그릴 것인지
Vec2 offset = Vec2{ 0.f,0.f };
//영역의 크기
Vec2 size = Vec2{ 0.f,0.f };
//몇초동안 그릴 것인지
float time = 0.f;
};
class Animation : public ResourceBase
{
using Super = ResourceBase;
public:
Animation();
virtual ~Animation();
virtual void Load(const wstring& path) override;
virtual void Save(const wstring& path) override;
void SetLoop(bool loop) { _loop = loop; }
bool IsLoop() { return _loop; }
void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
shared_ptr<Texture> GetTexture() { return _texture; }
Vec2 GetTextureSize();
const Keyframe& GetKeyframe(int32 index);
int32 GetKeyframeCount();
//원하는 애니메이션 정보 추가
void AddKeyframe(const Keyframe& keyframe);
private:
bool _loop = false;
shared_ptr<Texture> _texture;
vector<Keyframe> _keyframes;
};
Animation.cpp
#include "pch.h"
#include "Animation.h"
#include "Texture.h"
Animation::Animation() : Super(ResourceType::Animation)
{
}
Animation::~Animation()
{
}
void Animation::Load(const wstring& path)
{
}
void Animation::Save(const wstring& path)
{
}
Vec2 Animation::GetTextureSize()
{
return _texture->GetSize();
}
const Keyframe& Animation::GetKeyframe(int32 index)
{
return _keyframes[index];
}
int32 Animation::GetKeyframeCount()
{
return static_cast<int32>(_keyframes.size());
}
void Animation::AddKeyframe(const Keyframe& keyframe)
{
_keyframes.push_back(keyframe);
}
그리고 이 애니메이션을 실제로 재생하게될 컴포넌트 Animator 클래스를 만들어주자.
이 클래스에서는
하나의 애니메이션 프레임이 재생되는 시간, 재생되고 있는 키프레임번호등의 변수를 가지고 있으며 애니메이션을 설정하는 함수를 가지고 있다.
Update 되는 동안 만약 반복이 활성화되어 있다면 하나의 애니메이션을 반복하고 아니라면 하나의 이미지의 지속시간이 끝났다면 다음 이미지로 넘어갈 수 있게 하자
Animator.h
#pragma once
#include "Component.h"
#include "Animation.h"
class Animation;
class Animator : public Component
{
using Super = Component;
public:
Animator();
virtual ~Animator();
void Init();
void Update();
shared_ptr<Animation> GetCurrentAnimation();
const Keyframe& GetCurrentKeyframe();
void SetAnimation(shared_ptr<Animation> animation) { _currentAnimation = animation; }
private:
float _sumTime = 0.f;
//현재 실행 중인 키프레임 번호
int32 _currentKeyFrameIndex = 0;
shared_ptr<Animation> _currentAnimation;
};
Animator.cpp
#include "pch.h"
#include "Animator.h"
#include "Game.h"
#include "TimeManager.h"
Animator::Animator()
: Super(ComponentType::Animator)
{
}
Animator::~Animator()
{
}
void Animator::Init()
{
}
void Animator::Update()
{
shared_ptr<Animation> animation = GetCurrentAnimation();
if (animation == nullptr)
return;
const Keyframe& keyframe = animation->GetKeyframe(_currentKeyFrameIndex);
float deltaTime = TIME->GetDeltaTime();
_sumTime += deltaTime;
//하나의 이미지 지속시간이 끝났다면
if (_sumTime >= keyframe.time)
{
//다음 프레임으로
_currentKeyFrameIndex++;
int32 totalCount = animation->GetKeyframeCount();
//다 재생했다면
if (_currentKeyFrameIndex >= totalCount)
{
if (animation->IsLoop())
_currentKeyFrameIndex = 0;
else
_currentKeyFrameIndex = totalCount - 1;
}
_sumTime = 0.f;
}
}
shared_ptr<Animation> Animator::GetCurrentAnimation()
{
return _currentAnimation;
}
const Keyframe& Animator::GetCurrentKeyframe()
{
return _currentAnimation->GetKeyframe(_currentKeyFrameIndex);
}
이제 ResourceManager에 CreateDefaultAnimation 함수에서 기본 애니메이션을 컴포넌트에 추가하는 부분을 구현해보자
먼저 snake라는 전체이미지를 텍스처로 가져온다음 이걸 잘라서 쓸 수 있도록 하자.
ResourceManager.h
void ResourceManager::CreateDefaultTexture()
{
{
auto texture = make_shared<Texture>(_device);
texture->SetName(L"Cat");
texture->Create(L"cat.png");
Add(texture->GetName(), texture);
}
{
auto texture = make_shared<Texture>(_device);
texture->SetName(L"Snake");
texture->Create(L"Snake.bmp");
Add(texture->GetName(), texture);
}
}
void ResourceManager::CreateDefaultAnimation()
{
shared_ptr<Animation> animation = make_shared<Animation>();
animation->SetName(L"SnakeAnim");
animation->SetTexture(Get<Texture>(L"Snake"));
animation->SetLoop(true);
//각 프레임당 이미지위치정보
animation->AddKeyframe(Keyframe{ Vec2{0.f,0.f},Vec2{100.f,100.f},0.1f });
animation->AddKeyframe(Keyframe{ Vec2{100.f,0.f},Vec2{100.f,100.f},0.1f });
animation->AddKeyframe(Keyframe{ Vec2{200.f,0.f},Vec2{100.f,100.f},0.1f });
animation->AddKeyframe(Keyframe{ Vec2{300.f,0.f},Vec2{100.f,100.f},0.1f });
Add(animation->GetName(), animation);
}
그리고 SceneManager에 오브젝트에 Animator 컴포넌트를 붙여주자
SceneManager.cpp
//cat
shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
cat->GetOrAddTransform();
auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
cat->AddComponent(meshRenderer);
//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
//...
//Material
auto material = RESOURCES->Get<Material>(L"Default");
meshRenderer->SetMaterial(material);
//Mesh 부분
auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
meshRenderer->SetMesh(mesh);
scene->AddGameObject(cat);
}
{
auto animator = make_shared<Animator>();
cat->AddComponent(animator);
auto anim = RESOURCES->Get<Animation>(L"SnakeAnim");
animator->SetAnimation(anim);
}
하지만 이렇게만 해주면 아직 애니메이션이 실행되지 않는데 이것은 우리가 영역을 지정은 해주었지만 이 영역이 적용되는 코드를 아직 만들어주지 않았기 때문이다.
이를 구현하기 위해 RenderHelper 헤더파일에 Animation에 관한 Data를 선언하고 이를 RenderManager에서 GPU로 넘겨줄 수 있게 PushAnimationData 라는 함수를 만들어주자 그리고 오브젝트를 렌더할 때 Animator 컴포넌트가 있는지 검사
하고 있다면 가지고 있는 애니메이션 정보를 GPU로 넘겨주고 아니라면 0값으로 초기화해주도록 하자
RenderHelper.h
//GPU로 넘겨줄때 16바이트로 정렬
struct AnimationData
{
Vec2 spriteOffset;
Vec2 spriteSize;
//전체 사이즈
Vec2 textureSize;
float useAnimation;
//16바이트 채우기위한 더미
float padding;
};
RenderManager.h
public:
void PushAnimationData();
private:
//Animation
AnimationData _animationData;
shared_ptr<ConstantBuffer<AnimationData>> _animationBuffer;
RenderManager.cpp
void RenderManager::Init()
{
_pipeline = make_shared<Pipeline>(_deviceContext);
_cameraBuffer = make_shared<ConstantBuffer<CameraData>>(_device, _deviceContext);
_cameraBuffer->Create();
_transformBuffer = make_shared<ConstantBuffer<TransformData>>(_device, _deviceContext);
_transformBuffer->Create();
_animationBuffer = make_shared<ConstantBuffer<AnimationData>>(_device, _deviceContext);
_animationBuffer->Create();
_rasterizerState = make_shared<RasterizerState>(_device);
_rasterizerState->Create();
_blendState = make_shared<BlendState>(_device);
_blendState->Create();
_samplerState = make_shared<SamplerState>(_device);
_samplerState->Create();
}
void RenderManager::PushAnimationData()
{
_animationBuffer->CopyData(_animationData);
}
void RenderManager::RenderObjects()
{
for (const shared_ptr<GameObject>& gameObject : _renderObjects)
{
shared_ptr<MeshRenderer> meshRenderer = gameObject->GetMeshRenderer();
if (meshRenderer == nullptr)
continue;
shared_ptr<Transform> transform = gameObject->GetTransform();
if (transform == nullptr)
continue;
//SRT
_transformData.matWorld = transform->GetWorldMatrix();
pushTransformData();
//Animation 여부
shared_ptr<Animator> animator = gameObject->GetAnimator();
if (animator)
{
const Keyframe& keyframe = animator->GetCurrentKeyframe();
_animationData.spriteOffset = keyframe.offset;
_animationData.spriteSize = keyframe.size;
_animationData.textureSize = animator->GetCurrentAnimation()->GetTextureSize();
_animationData.useAnimation = 1.f;
PushAnimationData();
_pipeline->SetConstantBuffer(2, SS_VertexShader, _animationBuffer);
_pipeline->SetTexture(0, SS_PixelShader, animator->GetCurrentAnimation()->GetTexture());
}
else
{
_animationData.spriteOffset = Vec2(0.f, 0.f);
_animationData.spriteSize = Vec2(0.f, 0.f);
_animationData.textureSize = Vec2(0.f, 0.f);
_animationData.useAnimation = 0.f;
PushAnimationData();
_pipeline->SetConstantBuffer(2, SS_VertexShader, _animationBuffer);
_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->GetTexture());
}
PipelineInfo info;
info.inputLayout = meshRenderer->GetInputLayout();
info.vertexShader = meshRenderer->GetVertextShader();
info.pixelShader = meshRenderer->GetPixelShader();
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
_pipeline->UpdatePipeline(info);
_pipeline->SetVertexBuffer(meshRenderer->GetMesh()->GetVertexBuffer());
_pipeline->SetIndexBuffer(meshRenderer->GetMesh()->GetIndexBuffer());
_pipeline->SetConstantBuffer(0, SS_VertexShader, _cameraBuffer);
_pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);
//_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->GetTexture());
_pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
_pipeline->DrawIndexed(meshRenderer->GetMesh()->GetIndexBuffer()->GetCount(), 0, 0);
}
}
여기서 이제 입력에 따라 쉐이더에서 uv좌표도 달라져야하는데 이때 선택한 스프라이트 부분만 텍스처에서 사용할 수 있도록 하고 이에 따른 시작점도 계산해줄 계산식을 만들어주면 된다.
//정점 쉐이더- 위치관련 -> 레스터라이저-정점바탕으로 도형만들고 내/외부 판단 및 보간
VS_OUTPUT VS(VS_INPUT input)
{
VS_OUTPUT output;
//World view projection
float4 position = mul(input.position, matWorld);
position = mul(position, matView);
position = mul(position, matProjection);
output.position = position;
output.uv = input.uv;
if(useAnimation==1.f)
{
// 500 -부분 / 1000 전체
output.uv *= spriteSize / textureSize;
output.uv += spriteOffset / textureSize;
}
return output;
}
이렇게 해주면 애니메이션이 출력되게 된다.
만약 여기서 SceneManager에서 오브젝트를 하나추가하고 위치를 조정해주면 아래와 같이 되는 것이다.
SceneManager.cpp
//cat2
{
shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
cat->GetOrAddTransform()->SetPosition(Vec3(-1.f, -1.f, 0.f));
{
cat->GetOrAddTransform();
auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
cat->AddComponent(meshRenderer);
//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
//...
//Material
auto material = RESOURCES->Get<Material>(L"Default");
meshRenderer->SetMaterial(material);
//Mesh 부분
auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
meshRenderer->SetMesh(mesh);
scene->AddGameObject(cat);
}
{
auto animator = make_shared<Animator>();
cat->AddComponent(animator);
auto anim = RESOURCES->Get<Animation>(L"SnakeAnim");
animator->SetAnimation(anim);
}
}
여기서 만약 카메라를 움직이고 싶다면 카메라를 움직여주는 클래스를 하나 만들고 이를 Component로 추가해주면 된다.
CameraMove.h
#pragma once
#include "MonoBehaviour.h"
class CameraMove : public MonoBehaviour
{
public:
virtual void Update() override;
};
CameraMove.cpp
#include "pch.h"
#include "CameraMove.h"
#include "GameObject.h"
void CameraMove::Update()
{
auto pos = GetTransform()->GetPosition();
pos.x += 0.001f;
GetTransform()->SetPosition(pos);
}
SceneManager.cpp
#include "CameraMove.h"
shared_ptr<Scene> SceneManager::LoadTestScene()
{
//툴이 없기때문에 임의로 씬할당 후 제어
shared_ptr<Scene> scene = make_shared<Scene>();
//제어
//Camera
{
shared_ptr<GameObject> camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
camera->GetOrAddTransform();
camera->AddComponent(make_shared<Camera>());
scene->AddGameObject(camera);
}
{
camera->AddComponent(make_shared<CameraMove>());
}
}
}
이렇게 해주면 카메라가 오른쪽으로 움직이는 것을 볼 수 있다.
3.Data
이제 하드코딩을 해주는 것 말고 실제 xml 파일을 가져와서 이를 통해 애니메이션이 실행되도록 해보자
이때 xml 가져오는 것은 tinyxml이라는 외부라이브러리를 가져와서 사용할 것이다.
이 xml을 가져오고 저장하는 것은 Animation 클래스의 Save, Load 함수를 통해 구현하자 일단 애니메이션 정보가 담긴
새로운 xml 파일을 저장하고 이를 통해 Load하는 방식으로 하자.
일단 미리 완성되어있는 xml 파일을 살펴보자
TestAnim.xml
<Animation Name="SnakeAnim" Loop="true" TexturePath="TODO">
<Keyframe OffsetX="0" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="100" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="200" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="300" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
</Animation>
xml 파일을 보면 트리구조로 되어있는 것을 볼 수 있다. 우리가 저장할 때도 이런 방식으로 저장해주어야 한다.
Animation.cpp
void Animation::Load(const wstring& path)
{
tinyxml2::XMLDocument doc;
string pathStr(path.begin(), path.end());
XMLError error = doc.LoadFile(pathStr.c_str());
assert(error == XMLError::XML_SUCCESS);
XMLElement* root = doc.FirstChildElement();
string nameStr = root->Attribute("Name");
_name = wstring(nameStr.begin(), nameStr.end());
_loop = root->BoolAttribute("Loop");
_path = path;
//Load Texture
//Keyframe
XMLElement* node = root->FirstChildElement();
for (; node != nullptr; node = node->NextSiblingElement())
{
Keyframe keyframe;
keyframe.offset.x = node->FloatAttribute("OffsetX");
keyframe.offset.y = node->FloatAttribute("OffsetY");
keyframe.size.x = node->FloatAttribute("SizeX");
keyframe.size.y = node->FloatAttribute("SizeY");
keyframe.time = node->FloatAttribute("Time");
AddKeyframe(keyframe);
}
}
void Animation::Save(const wstring& path)
{
tinyxml2::XMLDocument doc;
//제일 상위노드 생성
XMLElement* root = doc.NewElement("Animation");
doc.LinkEndChild(root);
//wstring -> string
string nameStr(GetName().begin(), GetName().end());
//Value값 넣어주기 Key, Value
root->SetAttribute("Name", nameStr.c_str());
root->SetAttribute("Loop", _loop);
root->SetAttribute("TexturePath", "TODO");
for (const auto& keyframe : _keyframes)
{
XMLElement* node = doc.NewElement("Keyframe");
root->LinkEndChild(node);
node->SetAttribute("OffsetX", keyframe.offset.x);
node->SetAttribute("OffsetY", keyframe.offset.y);
node->SetAttribute("SizeX", keyframe.size.x);
node->SetAttribute("SizeY", keyframe.size.y);
node->SetAttribute("Time", keyframe.time);
}
string pathStr(path.begin(), path.end());
auto result = doc.SaveFile(pathStr.c_str());
assert(result == XMLError::XML_SUCCESS);
}
사용할 때는 기본 애니메이션을 만들어주는 함수가 있는 ResourceManager에서 사용하면 된다.
void ResourceManager::CreateDefaultAnimation()
{
shared_ptr<Animation> animation = make_shared<Animation>();
animation->SetName(L"SnakeAnim");
animation->SetTexture(Get<Texture>(L"Snake"));
animation->SetLoop(true);
//각 프레임당 이미지위치정보
animation->AddKeyframe(Keyframe{ Vec2{0.f,100.f},Vec2{100.f,100.f},0.1f });
animation->AddKeyframe(Keyframe{ Vec2{100.f,100.f},Vec2{100.f,100.f},0.1f });
animation->AddKeyframe(Keyframe{ Vec2{200.f,100.f},Vec2{100.f,100.f},0.1f });
animation->AddKeyframe(Keyframe{ Vec2{300.f,100.f},Vec2{100.f,100.f},0.1f });
Add(animation->GetName(), animation);
// XML + JSON
//외부라이브러리 활용
animation->Save(L"TestAnim.xml");
shared_ptr<Animation> anim2 = make_shared<Animation>();
anim2->Load(L"TestAnim.xml");
}
이렇게 해주면 코드가 있는 경로에 xml 파일이 생성된다.
<Animation Name="SnakeAnim" Loop="true" TexturePath="TODO">
<Keyframe OffsetX="0" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="100" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="200" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="300" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
</Animation>
'게임공부 > Directx11' 카테고리의 다른 글
[Directx11][C++][3D]2. 도형만들기2 (1) | 2024.08.29 |
---|---|
[Directx11][C++][3D]1. 사각형 그리기(Const Buffer,Camera) (0) | 2024.08.27 |
[Directx11][C++]14. 엔진구조-2(Scene,Resource,Render) (0) | 2024.08.20 |
[Directx11][C++]13. 엔진구조-1(MeshRenderer) (0) | 2024.08.17 |
[Directx11][C++]12. 프레임워크 제작4(GameObject,Component) (1) | 2024.08.13 |