개요

별게임을 개발하면서 가장 많이 변경된 두 클래스라고 생각합니다.

그만큼 앞으로도 많이 변경될 것으로 보입니다.

먼저 코드 전에 간단하게 설명하자면 

Bullet은 모든 Bullet 파생클래스들의 Base클래스 입니다.

모든 Bullet 들 에게 공통적으로 필요한 AI 조종행동 이나 이펙트, action 등을 정의합니다.


Shooter 역시 모든 Shooter 파생클래스의 Base클래스 입니다.


두 클래스가 공통적으로 가지고 있는 함수가 있다면 바로 operator new 와 operator delete 함수 입니다.

operator new 함수는 메모리를 실제로 할당하지 않고 PoolingManager에서 받아오기 위해 정의하였고 

operator delete 는 operator new의 특성을 커버할 수 있도록 정의 되었습니다.

( operator new / delete 에 대한 자세한 설명은 아래 포스팅을 참조해주세요. )

[MemoryPooling_1]

[MemoryPooling_2]

[operator new / operator delete]



이제 코드를 보겠습니다.

(가독성을 위해 필요한 부분만 적절히 편집하였습니다.)



코드


operator new

1
2
3
4
5
6
/* poolingManager에서 FreeMemory Block을 하나 가져옴 */
void* CBullet::operator new(size_t sizeconst std::nothrow_t)
{
    // PoolingManager에서 메모리를 할당 받는다.
    return CPoolingManager::Instance()->BulletNew();
}
cs

PoolingManager의 관리를 받기 위한 operator new

create함수에서 새롭게 메모리 생성하는 것이 아니라 미리 생성해 두었던 메모리를 사용합니다.



ReturnToMemoryBlock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 오브젝트의 메모리를 FreeMemoryBlock으로 변환 == 오브젝트 삭제 */
void CBullet::ReturnToMemoryBlock()
{
    /*removeFromParent 의 이유 : 
    이유는 모든 CMover의 파생 객체들은 메모리 블럭에서 메모리를 할당 받는다.
    그로인해 실행 중 addChild시 같은 메모리를 여러번 addChild할 수 있다.
    때문에 메모리 블럭으로 되돌릴때에는 부모관계를 제거하여야한다.
    또 ReferenceCount를 1 낮춰야 하는 이유도 있다.*/
    this->removeFromParent();
    this->removeAllChildren();
    this->setVisible(false);
    this->setAlive(false);    
    CPoolingManager::Instance()->Bullet_ReturnToFreeMemory(this);
}
cs

사용이 끝난 오브젝트의 메모리를 PoolingManager로 돌려보냅니다.

돌려 보내기 전에 현재 오브젝트에 붙어있는 Children들을 해제하고 오브젝트 역시 부모로 부터 관계를 끊습니다.



Rotation

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
/* 회전행렬을 이용하여 오브젝트 회전 및 이동 */
void CBullet::Rotation(float dir, float delta)
{
    // 회전 속도와 방향을 이용하여 각도를 구하고 라디안으로 변환
    float radian = CC_DEGREES_TO_RADIANS(dir * (m_fRotationSpeed * delta));
 
    // 현재의 Direction Vector를 저장한다.
    Vec2 beforeRotation = getPosition() - m_pPlanet->getPosition();
 
    // 거리도 저장
    float length = beforeRotation.length();
 
    /* 회전행렬을 구함 
     * rotate x = ((x_ * cos(angle)) - (y_ * sin(angle)))
     * rotate y = ((x_ * sin(angle)) + (y_ * cos(angle))) */
    m_RotationVec = Vec2(
    (float)((beforeRotation.x * cos(radian)) - (beforeRotation.y * sin(radian))),
    (float)((beforeRotation.x * sin(radian)) + (beforeRotation.y * cos(radian))));
 
    // 노말라이즈
    m_RotationVec.normalize();
    m_RotationVec *= length;
    
    // 기존의 좌표에 새로운 좌표를 더해준다.
    setPosition(m_pPlanet->getPosition() + m_RotationVec);
 
    // 오브젝트 자체도 회전
    setRotation(getRotation() - (dir *( m_fRotationSpeed * delta)));
}
cs

오브젝트의 회전을 정의 합니다. 

회전행렬을 이용하여 회전 후 좌표를 구하고 오브젝트 자체도 회전하여 줍니다.



R_BezierWithScale

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
/* 일정 위치로 베지어 곡선을 그리며 이동한 후 커지면서 FadeOut */
void CBullet::R_BezierWithScale(
Vec2 targetPos, 
Vec2 controlPoint_1,
Vec2 controlPoint_2, 
float time, float scale)
 
{
    // 더이상 Execute 하지 않는다.
    setAlive(false);
 
    // 베지어 곡선 생성
    ccBezierConfig bezier;
    bezier.controlPoint_1 = Vec2(controlPoint_1);
    bezier.controlPoint_2 = Vec2(controlPoint_2);
    bezier.endPosition = Vec2(targetPos);
 
    // 베지어 액션 및 다른 액션 순서대로 실행
    auto bezierTo1 = BezierTo::create(time, bezier);
    this->setZOrder(101);
    auto action = Sequence::create(
        bezierTo1,
        ScaleBy::create(0.5f, scale),
 
        // 두 액션이 끝난후 스케줄을 걸어 오브젝트 삭제
        CallFunc::create([&](){
        this->scheduleOnce([=](float dt){
            this->ReturnToMemoryBlock();
        }, 1.0f, "BezierWithScale");
 
    }), nullptr);
    this->runAction(action);
 
    // texture 페이드 아웃
    auto textureAction = FadeOut::create(2.0f);
    m_pTexture->runAction(textureAction);
}
 
cs

오브젝트의 특정 action을 정의합니다. R_은 ReturnToMemoryBlock을 호출한다는 약자입니다.


'All > Project' 카테고리의 다른 글

별게임의 상태트리  (0) 2016.04.12
AI_ FSM / State  (0) 2016.04.12
ObjectManager  (0) 2016.04.12
PoolingManager  (0) 2016.04.12
게임 전체 클래스 UML 및 간단설명  (0) 2016.04.12

개요

PoolingManager에서 생성한 MemorBlock을 게임 오브젝트로서 관리하기 위해 CObejctManager를 만들었습니다.

자세한 설명은 아래 코드와 함께하겠습니다.

(역시 가독성을 위해 적당히 편집하였습니다.)


코드

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
/*------------------------ObjectManager--------------------------
*
* singleton class
* CMover를 상속받는 모든 클래스를 Execute및 Remove하는 함수이다.
* RemoveAllBullet()함수는 Mover->Delete()함수를 호출한다.
* Delete()에서 removeFromParent()와 operator delete를 호출하여 소멸자를 호출하기 위함
*
*---------------------------- ----------------------------------*/
 
 
 
 
/* PoolingManager에서 CreateBulletList할 때  
생성된 메모리를 관리하기 위해 이곳에 넣는다. */
void AddBullet(void* bullet){
    m_BulletList.emplace_back(static_cast<CBullet*>(bullet));
}
 
 
/* Bullet을 create할 때 이 리스트에 넣는다. */
void Auto_ReturnToMemoryBlock(CBullet* bullet){
    m_ReturnToMemoryBlockList.emplace_back(bullet);
}
 
 
/* 게임 종료 시점에 호출된다. */
void RemoveAllObject(){
    for (auto bullet : m_BulletList)
    {
        if (bullet->HasPointer()) 
            bullet->Delete();
    }
}
 
 
/* Alive 상태의 오브젝트를 Execute한다. */
void Execute(float delta){
    // 지워진 오브젝트는 returnToMemoryBlock을 호출한다.
    ReturnToMemoryBlockEveryFrame();
 
    // 오브젝트의 Execute를 호
    for (auto bullet : m_BulletList)
    {
        if (bullet->IsAlive()) {
            bullet->Execute(delta);
        }
    }
}
 
 
//callback
/* LeftButton RightButton 누르면 호출된다. */
void RotationObject(int dir){
    for (auto bullet : m_BulletList)
    {
        if (bullet->IsAlive()) {
            bullet->Rotation(dir);
        }
    }
}
 
 
/* ReturnToMemoryBlockEveryFrame()
 * 지운 오브젝트들의 ReturnToMemoryBlock을 즉시 호출하면
 * 같은 프레임에서 생성한 오브젝트들이 앞에서 Free로 만든 메모리를 참조할 수 있으므로
 * 한프레임 늦게 메모리 블럭으로 되돌리기 위해서. */
void ReturnToMemoryBlockEveryFrame()
{
    for (auto bullet : m_ReturnToMemoryBlockList)
    {
        if (!bullet->IsAlive()) {
            CPoolingManager::Instance()->Bullet_ReturnToFreeMemory(bullet);
        }
    }
}    
 
 
/* 생성된 메모리를 Object로 캐스팅하여 담을 리스트 */
std::vector<CBullet*> m_BulletList;
 
 
/* Alive가 false인 오브젝트들을 MemoryBlock으로 변경*/
std::vector<CBullet*> m_ReturnToMemoryBlockList;
 
cs

CObjectManager 클래스의 주요 함수 및 동작원리 설명

1. AddBullet을 호출하여 메모리 블럭을 Bullet으로써 관리하도록 합니다.

2. Bullet의 Create()함수에서 Auto_ReturnToMemoryBlock()을 호출하여 m_ReturnToMemoryBlockList에 등록합니다.

3.  m_ReturnToMemoryBlockList에 등록된 오브젝트 중 Alive가 false인 것은 다음 프레임에서 메모리블럭으로 되돌아갑니다.



사용 예

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
/* 사용 예 Auto_ReturnToMemoryBlock()에 포인터 등록  */
 
/* operator new */
void* CNormalBullet::operator new(size_t sizeconst std::nothrow_t)
{
    // PoolingManager에서 메모리를 할당 받는다.
    return CPoolingManager::Instance()->BulletNew();
}
 
 
CNormalBullet* CNormalBullet::create(
    std::string textureName,        //bullet 이미지
    float boundingRadius,           //bullet 충돌 범위
    float angle,                    //bullet 초기 각도 
    float speed)                    //bullet 초기 속도
{
    // operator new 호출 후 CNormalBullet 생성자로 초기화
    CNormalBullet* pRet = 
        (CNormalBullet*)new(std::nothrow)CNormalBullet(
        textureName, boundingRadius, angle, speed);    
 
    // nothrow로 인해 초기화 되지 않으면 nullptr반환됨
    if (pRet && pRet->init())
    {
        // ObjectManager의 Auto_ReturnToMemoryBlock()에 등록
        pRet->Auto_ReturnToMemoryBlock();
        return pRet;
    }
    else
    {
        delete pRet;
        pRet = NULL;
        return NULL;
    }
}
cs

1. pRet->Auto_ReturnToMemoryBlock()에서는 ObjectManager의 Auto_ReturnToMemoryBlock()를 this를 인자로 전달하며 호출합니다.

2. ObjectManager의 Auto_ReturnToMemoryBlock()은 m_ReturnToMemoryBlockList 에 전달 받은 포인터를 등록합니다.


사용 예

1
2
3
4
5
/* 사용 예 RemoveAllObject 호출 */
CGameScene::~CGameScene()
{
    CObjectManager::Instance()->RemoveAllObject();
}
cs

1. Scene변경 및 종료 시 RemoveAllObject()를 호출합니다.

'All > Project' 카테고리의 다른 글

AI_ FSM / State  (0) 2016.04.12
Shooter / Bullet  (0) 2016.04.12
PoolingManager  (0) 2016.04.12
게임 전체 클래스 UML 및 간단설명  (0) 2016.04.12
게임 개요  (0) 2016.04.11

개요.


먼저 제가 만든 poolingManger는 아래 두 책에 나오는 내용을 기반으로 만들어 졌다는 것을 미리 밝힙니다.

               


앞에서도 이야기 했듯이 

별 게임은 별, 총알, 아이템 등이 매우 많이 만들어 지고 삭제됩니다..

때문에 게임 개발을 하는 것에 있어서 가장 먼저 고려했던 것이.. 

1. 생성과 삭제의 리스크

2. 메모리 단편화

위 두 가지 이유로 인한 게임 성능 저하 였습니다.

게임이 렉이 걸리고 느리면 게임을 하는 입장에서는 매우 답답하며 화가나는 것은 당연한 사실이니까요.

그래서 고민을 하다가 PoolingManager를 만들게 된 것입니다.



아래는 PoolingManager의 코드 및 간단한 설명입니다. 


(그리고 MemoryPooling에 대해 자세한 설명을 보고싶으시면 아래 포스트들을 참조해 주세요. )

[MemoryPooling_1]

[MemoryPooling_2]

[operator new / operator delete]



먼저 완성된 코드는 아래와 같습니다.

(가독성을 위해 적당히 편집하였습니다.)


코드

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
/*-----------------------CPoolingManager------------------------
 *
 * singleton pattern
 * 동작 사이클은 아래와 같다.
 *
 * 1. CreateBulletList() - 원하는 만큼의 메모리 블럭 생성 및 리스트에 추가
 * 2. BulletNew() - 생성하고자 하는 오브젝트로 초기화(operator new, 생성자 호출)
 * 3. 사용
 * 4. ReturnToFreeMemory() - 메모리 반환
 * 5. 사용할 때 마다 2번부터 반복 
 * 6. 게임 종료시 메모리 해제
 *
----------------------------------------------------------------*/
 
 
 
/* 메모리 블럭 타입 정의    */
typedef char* MEMORYBLOCK;
 
 
/* size만큼의 메모리 블럭을 생성한다. */
MEMORYBLOCK NewMemoryBlock(size_t sizeconst
{
    /* memoryAlive flag를 위한 1바이트 추가 생성 */
    MEMORYBLOCK block = new char[size + 1];
 
    /* memory 초기화 및 memoryAlive = false */
    memset(block, 0size + 1);                
    return block;
}
 
 
/* size만큼의 char형 포인터를 count만큼 Bullet리스트에 add */
void CreateBulletList(size_t count, size_t size)
{
    m_BulletSize = size;
    m_BulletList.reserve(m_BulletSize);
    while (count--)
    {
        /* 하나의 크기가 size만큼의 메모리 블럭을 count만큼 생성한다. */
        MEMORYBLOCK memBlock = NewMemoryBlock(m_BulletSize);    
        m_BulletList.emplace_back(memBlock);
 
        /* 오브젝트 매니저 리스트에 추가한다. */
        CObjectManager::Instance()->AddBullet(memBlock);
    }
}
 
 
/* pool이 보유한 메모리 리스트가 생성하려는 것보다 적으면 새로 생성 */
void* BulletNew()
{
    for (auto bullet : m_BulletList)
    {
        /* 메모리가 Free(false)상태면 메모리를 사용 중 상태(true)로 전환 후 반환 */
        if (false == bullet[m_BulletSize]) {    
            bullet[m_BulletSize] = true;        
            return bullet;
        }
    }
 
    /* 모든 메모리가 사용 중 상태라면 새롭게 하나 생성 */
    CCLOG("BULLET LIST OVERFLOWED");
    MEMORYBLOCK memBlock = NewMemoryBlock(m_BulletSize);
    m_BulletList.emplace_back(memBlock);
 
    /* 오브젝트 매니저 리스트에 추가한다. */
    CObjectManager::Instance()->AddBullet(memBlock);
 
    /* 메모리를 사용 중 상태로 전환 */
    memBlock[m_BulletSize] = true;    
 
    return memBlock;
}        
 
    
/* Bullet을 메모리블럭으로 전환 (alive off) */
void Bullet_ReturnToFreeMemory(void* bullet)
{
    static_cast<char*>(bullet)[m_BulletSize] = false;
}        
 
 
/* 모든 메모리를 해제한다. (게임이 종료되거나 Scene이 변경될때 호출) */
void DeleteAllMemory()
{
    for (auto bullet : m_BulletList)
    {
        delete[] bullet;
        bullet = nullptr;
    }
    m_BulletList.clear();
}                            
 
 
/* 메모리 블럭 리스트 */
std::vector<MEMORYBLOCK> m_BulletList;
 
 
/* 메모리 블럭 하나 당 크기 */
int m_BulletSize;
cs

CPoolingManager 클래스의 주요 함수 및 동작원리 설명


1. memoryPooling기능을 수행하는 singleton 클래스 입니다.

2. 게임을 시작할 때 원하는 사이즈의 메모리 블럭을 원하는 개수만큼 미리 생성해 놓습니다.

3. 사용자는 필요할 때 해당 메모리들을 사용할 수 있습니다. 

4. MemoryPooling의 관리를 받고 싶다면 해당 클래스의 operator new에서 PoolingManager의 New를 호출해주면 됩니다. 

6. 그러면 미리 생성된 메모리 중 Free상태의 메모리를 할당 받을 수 있습니다. 



사용 예

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
/* 사용 예 1 객체 초기화 */
 
/* operator new */
void* CNormalBullet::operator new(size_t sizeconst std::nothrow_t)
{
    // PoolingManager에서 메모리를 할당 받는다.
    return CPoolingManager::Instance()->BulletNew();
}
 
 
CNormalBullet* CNormalBullet::create(
    std::string textureName,        //bullet 이미지
    float boundingRadius,           //bullet 충돌 범위
    float angle,                    //bullet 초기 각도 
    float speed)                    //bullet 초기 속도
{
    // operator new 호출 후 CNormalBullet 생성자로 초기화
    CNormalBullet* pRet = 
        (CNormalBullet*)new(std::nothrow)CNormalBullet(
        textureName, boundingRadius, angle, speed);    
 
    // nothrow로 인해 초기화 되지 않으면 nullptr반환됨
    if (pRet && pRet->init())
    {
        // ObjectManager의 Auto_ReturnToMemoryBlock()에 등록
        pRet->Auto_ReturnToMemoryBlock();
        return pRet;
    }
    else
    {
        delete pRet;
        pRet = NULL;
        return NULL;
    }
}
cs

사용 예 (객체 초기화)

1. NormalBullet의 create에서 호출하는 new는 operator new 로 poolingManager의 New함수를 호출합니다.

2. PoolingManager의 New함수는 미리 생성된 Free메모리를 반환합니다.

3. 반환된 MemoryBlock은 CNormalBullet 생성자로 초기화 되어 사용합니다. 


1
2
3
4
5
6
7
8
9
10
11
/* 사용 예 2 메모리 반환 */
    
void CNormalBullet::ReturnToMemoryBlock()
{
    this->removeFromParent();
    this->removeAllChildren();
    this->setVisible(false);
    this->setAlive(false);    
}
 
cs

사용 예 (메모리 반환)

1. 사용한 메모리를 Free메모리로 반환하려면 setAlive(false) 하면 됩니다.

2. alive가 false인 오브젝트는 다음 프레임에서 Free메모리로 전환됩니다.

3. this->removeFromParent();

    this->removeAllChildren();

    this->setVisible(false);

    this->setAlive(false);

4. 위 일련의 작업들은 현재 오브젝트에 AddChild되어 있는 메모리들을 해제해주는 것과 부모로부터 현재 메모리를 단절시키는 작업을 진행합니다. 



EXTRA : 

처음엔 addChild되어야만 object로써 제대로된 구실을 하는 cocos2dx의 특성을 고려하여 

createBulletList할 때 모든 NormalBullet 등의 최상위 클래스인 CBullet으로 생성한 후 addChild까지 했었습니다.

게임 중 addChild하는 비용까지 없애자는 야심찬 계획이었지만 다음과 같은 이유로 뻘짓이 되었습니다.


1. bullet으로 생성자를 호출하여 초기화 하면 이후 normalBullet으로써 사용하지 못한다.

    (normalBullet의 생성자를 호출한 것이 아니기 때문에 다형성 불가.)


2. 그래서 처음에 Bullet으로 초기화 후 사용할 때 NormalBullet으로 다시 초기화 해보았는데 

    이때는 Ref생성자 호출 과정에서 RefCount가 다시 1이 되기 때문에 이전의 addChild 한 것이 소용없어졌습니다.

    (그리고 생성자를 두 번 호출하는 말도 안되는 문제가 있었죠.)


3.  Bullet으로 생성한 메모리를 NormalBullet으로 사용하려면 두 클래스의 크기가 같아야 했습니다.


위와 같은 일이 있었기에 지금의 모습인 Char* 형으로 모든 Bullet보다 적당히 큰 메모리 블럭을 만들어 놓고 

사용할 때 operator new를 이용하여 NormalBullet이든 SpecificBullet이든 초기화하여 사용할 수 있도록 변경하였습니다.

다형성을 이용할 수 있게된 것이죠.


현재 구조를 보면 사용할 때마다 addChild및 RemoveFromParent를 하고 있습니다.

같은 메모리를 반복해서 사용하니까 당연하게 해주어야 할 일이긴 한데.. 뭔가찜찜한 상태라 차후에 리펙토링을 고려하고 있습니다.



귀차니즘을 위한 3줄 요약

1. PoolingManager구현 

2. 별게임에 적용

3. 게임성능 UP



'All > Project' 카테고리의 다른 글

Shooter / Bullet  (0) 2016.04.12
ObjectManager  (0) 2016.04.12
게임 전체 클래스 UML 및 간단설명  (0) 2016.04.12
게임 개요  (0) 2016.04.11
MemoryPooling_2  (0) 2016.03.15

+ Recent posts