Game Object Component System 설계

2018. 6. 27. 16:29기술/자체엔진

반응형

최대한 유니티 엔진을 사용하던 사용자경험(UX)를 살려 C++로 

Game Object Component System 설계를 적용하였습니다.


유니티 엔진과 언리얼 엔진에 적용되어 있는 설계입니다.

(게임 오브젝트에 구성요소를 추가하며 오브젝트를 만드는 설계 방식)


 C++로 코드를 작성하고 DirectX로 렌더링을 하지만 모든 Component 설계구조가 비슷하기 때문에 코드를 C#으로 변환한다면 유니티에서도 사용할 수 있습니다. 즉, 근본적인 로직이 비슷합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#pragma once
#include "stdafx.h"
 
typedef std::map<CString, Component*> MComponent;
typedef MComponent::value_type MComPair;
typedef MComponent::iterator IterGOCom;
 
typedef std::multimap<CString, GameObject*> MGameObject;
typedef MGameObject::value_type MGOPair;
typedef MGameObject::iterator IterGO;
 
class GameObject
{
public:
    GameObject(CString szName);
    virtual ~GameObject(void);
 
    // 구성요소를 추가합니다.
    void AddComponent(Component* pComponent);
 
    void AddChild(GameObject* pGO);
 
    // 구성요소를 반환합니다.
    Component* GetComponent(const CString& familyID);
    
    // 모든 구성요소를 파괴합니다.
    void CleanUpComponent();
    
    // 이름
    inline CString& Name(){ return m_szName; }
 
    // 게임 오브젝트를 파괴합니다.
    inline void Destroy() { m_bIsDestroy = true; }
 
    // 파괴해야 할 게임 오브젝트인지 여부를 반환합니다.
    inline bool IsDestroy() { return m_bIsDestroy; }
 
    void Awake();
 
    // 업데이트
    void Update();
 
    // 렌더링
    void Render();
 
    // 활성화 여부를 설정합니다.
    void SetActive(bool bActive);
 
    // 활성화 여부를 반환합니다.
    inline bool IsActive() { return m_bIsActive; }
 
    // 부모 노드를 설정합니다.
    void SetParent(GameObject* pParent);
 
    // 부모 노드를 반환합니다.
    inline GameObject* GetParent() { return m_pParent; }
 
    // map에 담겨있는 모든 게임 오브젝트를 파괴합니다.
    static void CleanUpGameObject();
 
    // 게임 오브젝트를 파괴합니다.
    static void Destroy(GameObject* pGO);
 
    // 해당 이름의 게임 오브젝트를 찾습니다.
    static GameObject* Find(CString szName);
 
    // 해당 이름으로 모든 게임 오브젝트를 찾아서 리스트로 반환합니다.
    static list<GameObject*> FindAll(CString szName);
 
public:
    // 최적화 필요
    // public말고 처리방안 필요
    // 관련 함수들과 함께 Scene쪽으로 옮길것, 현재 GameObject 안에 있는 변수들 사용하기 위해 여기에 존재?
    // 해당 씬 마다 mapGameObject가 있어야 할 듯, 현재 모든 씬에 다 들어감
    static std::multimap<CString, GameObject*> m_mapGameObjects; // 중복 키 값 허용을 위해 multimap 사용
    ComTransform* transform;
    bool IsAlwaysRender;    // 항상 렌더링 해야하는 객체
    bool IsInFrustum;        // 프러스텀 안에 있는지 여부
 
private:
    // 이름
    CString m_szName;
 
    // 구성요소
    std::map<CString, Component*> m_Components;
 
    // 부모 노드
    GameObject* m_pParent;
 
    // 월드 행렬
    //Matrix4x4 m_matWorld;
 
    // 활성화 여부
    bool m_bIsActive;
 
    // 파괴될 오브젝트 (예약개념, Component.Update()에서 파괴되면 에러 나므로)
    bool m_bIsDestroy;
    
    // 충돌 박스
    ComCollider* m_pCollider;
 
    list<GameObject*> m_listChild;
};
 
 
cs


생성된 모든 게임 오브젝트는 stl multimap에 저장하여 Key값의 중복을 허용하였습니다.

이름으로 하나의 게임 오브젝트를 검색할 수 있습니다. (multimap.Find() 사용, 성능 OLogN)

이름으로 여러 게임 오브젝트를 찾을 수 있습니다. list<GameObject*> 반환


AABB충돌 처리

ComCollider 를 컴포넌트로 추가하면 자동으로 충돌 처리 되며 Component의 OnTriggerEnter 함수가 호출됩니다.

ComCollider 컴포넌트를 수정하여 구충돌이나 OBB 충돌을 추가하면됩니다.

Component가 생성후 Awake() 호출 됩니다. (씬이 시작하기 전에 일괄적으로 Awake()를 호출해 주는데 Update()에서 생성되는 게임 오브젝트들도 있어서 Awake()가 호출 된 컴포넌트는 재호출 되지 않게 처리해 주었습니다.)



모든 게임오브젝트는 객체 생성 패턴 Factory를 사용하였습니다. (필요시 하위 팩토리 클래스를 만들어서 분할)



ComRenderCubePN : Component


정점(Vertex)과 노멀(Normal) 정보를 사용하여 렌더링 하는 컴포넌트 입니다.


셰이더

정점 셰이더(Vertex Shader)를 사용하여 WorldMatrix, Camera View Matrix, Camera Proj Matrix를 계산해 주며

픽셀 셰이더(Pixel Shader)를 사용하여 빛방향 벡터와 노멀벡터의 각도값으로 픽셀들을 계산해주는 셰이더가 적용되었습니다.


셰이더 코드 작성은 렌더몽키 사용


셰이더를 사용한 렌더링 외각선 추출

pass0에서는 텍스쳐를 사용해서 렌더링

pass1에서 정점 위치 + 노멀 * 2 해준다음 빨간색으로 렌더링 해주고 시계반대방향 컬링(CCW)


기초 게임 구현 (비행 슈팅)


1. 캐릭터 이동

2. 캐릭터를 향해 따라오는 적, 일정 거리 이상 되면 따라오다가 멈춤

3. 캐릭터 위치에서 미사일 발사

4. 미사일 사정 범위 넘어가면 파괴됨 -> 재사용으로 수정

5. 캐릭터와 적 충돌 시 적 파괴됨 

6. 미사일과 적 충돌 시 적 파괴됨 (적은 메모리 해제, 스테이지 다시 시작을 위해 재사용으로 수정해야 할거 같음)



7. 레벨에 따른 비행기 외형변화 (추후 애니메이션 업데이트 되면 비행기 변신 기능 같은거 추가 가능)

8. 레벨에 따른 미사일 공격 범위 증가와 상,하 45도 미사일 추가

9. 레벨에 따른 이동속도 증가

10. 스킬 추가 우측으로 30도씩 회전하면서 0.2초 간격으로 발사되는 미사일

11. 레벨에 따른 스킬 변경 우측으로 15도씩 회전하면서 0.1초 간격으로 발사되는 미사일

12. 레벨에 따른 스킬 변경 왼쪽으로 위쪽으로도 회전되면서 발사됨

13. 리팩토링 하여서 적 비행기가 플레이어의 기본 미사일 발사 스킬을 사용

14. 리팩토링 하여서 적 비행기가 플레이어의 스킬 사용 가능하게 할 수 있음

15. 미사일은 사정거리까지만 발사되고 객체를 메모리 해제 하지 않고 자료구조에 담아서 재사용

16. 미사일은 렘버트 셰이더 사용해서 정점과 픽셀 계산

17. 미사일을 발사한 주 객체(적 비행기)가 파괴(메모리 해제) 되어도 미사일은 계속 존재

18. 대화 튜토리얼 UI 추가, 레벨에 따른 변화를 NPC가 설명을 해줌

19. 플레이 타임은 현재 4분으로 HP 구현과 데미지 파이프라인을 추가해서 벨런싱 조절하여 플레이 타임 조절 가능

20. 엔진 기능 업데이트 카메라에 들어오지 않는 게임 오브젝트는 렌더링에서 제외(Frustum Culling)

21. 엔진 기능 업데이트 게임 오브젝트들을 모두 렌더링 한 후 UI 게임 오브젝트를 렌더링, 파티클 같은 게임 오브젝트가 추가 되었을 때 Z축이 먼 오브젝트를 먼저 렌더링

22. 유연한 설계로 설계 수정 가능. 모든 코드는 재사용 가능하게 구성요소(Component)로 구현, 다른 게임 RPG같은데에서도 사용 가능. 더 완벽한(Completed) 코드를 위해 코드 유지보수(리팩토링) 필요.

23. 버그는 없는데 QA와 테스트를 거쳐 테스트 후 발견된 버그 수정 필요.

24. 각종 메쉬 오브젝트들 수정 및 추가, 시나리오 및 스테이지 추가(무한으로 가능하게 설계), BM요소 구성, 게임 완성도 향상 필요.


24. 불 모양의 파티클 추가 (직접 만들었습니다. ^^) 파티클과 다른 오브젝트 렌더링 순서가 잘 맞는걸 확인할 수 있습니다.


다음은 Picking입니다.

마우스 왼쪽 버튼 클릭시 Ray에 충돌된 모든 구체를 빨강색으로 재질을 변경합니다.

마우스 오른쪽 버튼 클릭시 가장 가까운 구체를 파랑색으로 재질을 변경합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
include "stdafx.h"
#include "ComTestPicking.h"
#include "ComMeshSphere.h"
 
ComTestPicking::ComTestPicking(CString szName) : 
    Component(szName)
{
}
 
 
ComTestPicking::~ComTestPicking()
{
}
 
void ComTestPicking::Awake()
{
}
 
void ComTestPicking::Update()
{
    if (Input::ButtonDown(VK_LBUTTON))
    {
        Mouse* pMouse = Input::GetInstance()-&gt;m_pMouse;
        D3DXVECTOR3 mousePos = Input::GetInstance()-&gt;m_pMouse-&gt;GetPosition();
        
        list&lt;GameObject*&gt; listSpheres = GameObject::FindAll("Sphere");
 
        for (auto &amp; p : listSpheres)
        {
            ComMeshSphere* pComMeshSphere = (ComMeshSphere*)p-&gt;GetComponent("ComMeshSphere");
 
            Ray ray = Ray::RayAtWorldSpace(mousePos.x, mousePos.y);
            bool picked = ray.CalcIntersectSphere(&amp;pComMeshSphere-&gt;m_boundingSphere);
 
            if (picked == true)
            {
                pComMeshSphere-&gt;m_material = DXUtil::RED_MTRL;
            }
            else
                pComMeshSphere-&gt;m_material = DXUtil::WHITE_MTRL;
        }
    }
 
    if (Input::ButtonDown(VK_RBUTTON))
    {
        D3DXVECTOR3 mousePos = Input::GetInstance()-&gt;m_pMouse-&gt;GetPosition();
 
        list&lt;GameObject*&gt; listSpheres = GameObject::FindAll("Sphere");
 
        // 가장 가까운 거리의 오브젝트를 갱신하여 BLUE_MTRL로 설정해준다.
        float fNearDistance = 0.f;
        float fMostNearDistance = 0.f;
        ComMeshSphere* pMostNearSphere = NULL;
 
        for (auto &amp; p : listSpheres)
        {
            ComMeshSphere* pComMeshSphere = (ComMeshSphere*)p-&gt;GetComponent("ComMeshSphere");
 
            Ray ray = Ray::RayAtWorldSpace(mousePos.x, mousePos.y);
            bool picked = ray.CalcIntersectSphere(&amp;pComMeshSphere-&gt;m_boundingSphere, &amp;fNearDistance);
 
            if (picked == true)
            {
                // fMostNearDistance == 0은 첫번 째 실행 될때
                if (fMostNearDistance == 0 || fNearDistance &lt;= fMostNearDistance)
                {
                    fMostNearDistance = fNearDistance;
                    pMostNearSphere = pComMeshSphere;
                }
                // 중복 체크 방지
                pComMeshSphere-&gt;m_material = DXUtil::WHITE_MTRL;
            }
            else
                pComMeshSphere-&gt;m_material = DXUtil::WHITE_MTRL;
        }
 
        if (pMostNearSphere)
            pMostNearSphere-&gt;m_material = DXUtil::BLUE_MTRL;
    }
}
 
void ComTestPicking::Render()
{
}
 
cs
반응형

'기술 > 자체엔진' 카테고리의 다른 글

셰이더 (Shader)  (0) 2018.07.19
벡터의 외적  (0) 2018.06.29
MaxExporter ASE DirectX  (0) 2018.06.27
3D 수학 기본 (WinAPI, DirectX 렌더링)  (0) 2018.06.12