개요.


먼저 제가 만든 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

게임 전체 클래스 UML을 보기 편하게 쪼개어 보았습니다.

(사정상 UML프로그램이 아닌 마인드맵 프로그램을 사용하였습니다.)

관계는 상속에 관련해서만 표현하였습니다.

각각의 클래스에 자세한 설명은 링크를 걸어 놓았습니다.



GameObject

- GameObject

   게임상에 등장하는 모든 오브젝트의 최상위 클래스 입니다. 

   BondingRadius, Execute 등 모든 오브젝트에 꼭 필요한 인터페이스를 제공합니다.


- Mover (자세히)

  게임 상에서 빈번히 생성되고, 삭제되는 오브젝트들의 베이스 클래스입니다.

   PoolingManager의 메모리 관리를 받는 파생클래스들의 베이스로서 필요한 함수들의 인터페이스를 제공합니다. 


- Player

  플레이어 클래스입니다. 캐릭터의 생명이나 FSM 등을 소유합니다.


- Planet

  행성 클래스입니다. 


- HealthUI (자세히)

- ScoreUI

- MyButton (자세히)

- BonusUI

  UI 클래스들입니다. 




State

- State (자세히)

  게임상의 오브젝트들은 AI상태를 구현하기 위해 State가 제공하는 인터페이스를 상속받아 상태를 정의합니다.

   게임의 AI는 Stage -> (Player, Bullet)의 계층 구조를 가집니다. (Stage의 상태에 따라 Player, Bullet의 상태가 변화합니다.)


- BulletState (자세히)

  Bullet들의 AI 상태입니다. 표기는 BulletState로 했지만 NormalState, BonusTimeState, MagnetItemState 가 존재합니다.

 

- PlayerState (자세히)

  Player의 AI 상태입니다. 역시 표기는 PlayerState로 했지만 NormalState, GiantState 가 존재합니다.


- StageState (자세히)

  현 Stage의 AI 상태입니다. 역시 표기는 StageState로 했지만 NormalStageState, BonusTimeStageState 가 존재합니다.




& Singleton


- UIManager 

  UI를 key값으로 저장하고 관리합니다.


- AudioManager

  기존의 AudioEngine을 랩핑한 클래스입니다. Audio의 동시 플레이 카운트를 제어하기위해 정의하였습니다.


- ItemManager 

  현재 플레이어가 획득한 아이템이 무엇인지 저장하고 타이머를 관리하도록 정의 되어 있습니다.


- ObjectManager (자세히)

  게임상의 모든 오브젝트의 Execute 및 안전한 Remove를 보장하기 위해 정의 되었습니다.


- PoolingManager (자세히)

  게임상에서 오브젝트의 생성과 삭제가 빈번하게 일어나기 때문에 그 리스크를 줄이고 메모리관리를 효율적으로 하기 위해 정의 하였습니다.




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

ObjectManager  (0) 2016.04.12
PoolingManager  (0) 2016.04.12
게임 개요  (0) 2016.04.11
MemoryPooling_2  (0) 2016.03.15
MemoryPooling_1  (0) 2016.03.15

지루한 개요


간단한 설명

사방에서 날아오는 총알을 피하고 별과 코인을 모아 점수를 겨루는 게임


게임 이름 

StarStarStar 입니다.


제작 기간  

6주 정도지만 아직 완성단계는 아닙니다.


개발 인원

프로그래밍, 기획, 그래픽 모두 혼자 작업했습니다. 그래픽은 파워포인트(ppt)와 웹 포토샾(pixlr)으로 작업했습니다.


개발 엔진 

cocos2d-x 3.4 ver


사용 언어

c++

 

개발 환경

visual studio 2013 , eclipse


외부 library

jsoncpp 

- 맵데이터나 패턴 등을 저장하는 용도로 사용했습니다.

- cocos2d-x에 내장 되어 있는 rapidJson을 사용하지 않은 것은 파싱하는 데 불안정했기 때문입니다.

(시행착오 끝에 많은 데이터를 사용하는 것에 적합하지 않다고 판단했습니다.)


보기 불편한 스크린샷 



보기편한 영상 




화질이 구지네요.. 화질구지.. 화질구지..
동영상이 렉이 걸리는데요?
내가 해도 이거 보다는 잘하겠다!! 는 분들을 위해




플레이시 주의 사항

- 캐릭터가 죽는게 슬퍼서 죽지 않도록 설정해 놓았습니다.
더 이상 지겨워서 못하겠다 싶을때 직접 종료버튼을 눌러주세요.
종료버튼은 최대한 작게 만들었습니다.

- 버그를 확인 하셨다면 당황하지 마시고 가까운 이클립스에서 로그를 획득하실 수 있습니다.
디버깅이 취미라면 찾아서 제게 알려주세요.
더이상 로그를 획득하실 수 없습니다.

- 삑삑 거리는 사운드가 징그러울 수 있습니다.

- 렉이 걸리는 것은 조만간 업데이트 될 예정이니 너무 당황하지 마세요. 

- 아직까지 1스테이지만 구성되어 있습니다. (보너스타임 1번)


 

앞으로의 기획적 업데이트 예정 사항

- 베리어 아이템 구현,

- 보너스 타임이 너무 무난하여 더 강렬하게 변경

- 펫, 버프아이템, 아티펙트 등

- 스테이지 재 구성 (밸런스 조정)

- 여러가지 캐릭터 및 능력

- 능력( 블랙홀, 코인 폭죽, 자이언트, 달리기, 일정 시간 무적 등)

- 종합점수 및 결과



앞으로의 프로그래밍적 업데이트 예정 사항

- 캐릭터에 붙어있는 FSM을 매번 생성하지 않고 PoolingManager에 위탁

- 게임팝업

- 유저 정보 저장 

- 구글 로그인 및 페이스북 로그인 SDK 부착

- 한번에 큰 패턴을 생성하면 렉걸리는 현상 수정

- 전체 적인 리펙토링 및 아직 계획되지 않은 추가 작업



앞으로의 그래픽적 업데이트 예정 사항

- 라이프 바가 잘안 보이는 듯 해 교체 예정

- 캐릭터를 고양이로 변경하여 카와이함을 추구할 예정

- 행성도 카와이~

- 모든 것이 카와이~



이후의 포스팅은 전부 게임을 개발할때  프로그래밍적인 내용입니다.


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

ObjectManager  (0) 2016.04.12
PoolingManager  (0) 2016.04.12
게임 전체 클래스 UML 및 간단설명  (0) 2016.04.12
MemoryPooling_2  (0) 2016.03.15
MemoryPooling_1  (0) 2016.03.15

친절한 설명이 있는 좋은 글을 봐서 주인장 허락(?)을 구하고 퍼왔다.

출저는 미리 밝혀둔다.

[http://sirini.net/grboard2/blog/view/741]

좋은 자료가 많은 것 같아 종종 들러야 겠다.

긴긴 수를 구하는 알고리즘이며, 공부하여 내것으로 만들어야겠다.

아래는 글의 내용이다.



이번에는 아주 기본적인 것을 한 번 고찰해 보겠습니다.
이름하야, 긴 자리수 (혹은 무한 자리수!) 덧셈 뺄셈!! ...입니다. ^^;
굳이 설명이 필요할 것 같지는 않지만, 그래도 한 번 같이 생각해보자는 취지로
배경지식을 이야기 해 보도록 하겠습니다.

C 언어에서 기본적으로 제공하는 데이터 타입 중에
95782023975478592927348 이라는 숫자를 한 번에 담을 수 있는
타입이 무엇일까요? 네, 짱돌 날라오는 군요. ==3=3 (튀엇!!)

없죠.
어이 없어 하시는 것도 충분히 이해가 됩니다.
사실 저는 그 동안 이른 바, 강타입(strong type) 언어를 자주 쓰지 않아서
이런 부분에 특히 무신경 했습니다. 놀랍게도 Python 과 같은 언어에서는
아예 언어 특성상 이런 무한자리수 연산이 가능하도록 설계가 되어 있으니
더더욱 무신경했는지도 모릅니다. (반성중... ㅠ.ㅠ)

어쨌든, 현실세계는 냉정합니다.
95782... 로 시작하는 저런 긴 숫자는 최소한 C언어에서는 감당할 수가 없습니다.
반드시 배열이나 기타 다른 데이터 구조에 담아서 별도의 가공을 거쳐야 하지요.
슬픈 현실 앞에 비통한 마음 금할 길 없지만, 뭐 그래도 어쩌겠습니까.
언어에서 지원하지 않는다면 우회하는 길이라도 만들어 봐야지요. ^^;;

그래서 준비된 것이 이번에 소개해 드릴 긴 자리수 연산 알고리즘입니다.
원리는 단순합니다. 긴 자리수를 4자리 정수를 담을 수 있는 short 배열에다가
담습니다. 그리고 그 4자리씩 덧셈과 뺄셈을 해서 각각 자리올림수와 자리내림수를
처리해 줍니다. 그러니까 다시 말해 긴 자리의 숫자를 한 번에 처리하지 않고
4자리씩 끊어서 결국 4자리 short int 형 정수의 덧셈과 뺄셈을 하는 것입니다.

우선 아래 성공했을 시 나타날 화면을 보시겠습니다. (찬조출현: GNOME 콘솔)

upload image
(출력부분을 보시면 일부로 4자리씩 끊어서 나타난 것을 알 수 있습니다.)

이 전략을 좀 더 고급(?)스럽게 이야기하면 '분할 정복 전략' 이 되겠습니다.
즉, 한 번에 해결할 수 없는 단위의 큰 문제는 한 번에 해결할 수 있는 작은 몇 개의 문제로
나눠서 개별적으로 각개 격파 하는 것입니다. 위에서도 긴 자리수의 숫자를
short int 형으로 담아서 결국에는 4자리수 덧셈 / 뺄셈을 하도록 했습니다.

물론 여기서 만족할 수는 없겠지요.
아래 제가 작성한 코드의 문제점은 외부에서 입력을 받을 때 어떻게 할 것인가 하는 대비가
없다는 점입니다. 또한 수치를 반드시 short int 형 배열에 쪼개서 넣는 게 좋은지,
아니면 다른 타입으로 변환해서 넣는게 더 좋은지에 대해서도 고려를 해보아야 합니다.
더 깊게 생각해 본다면 CPU I/O 카운팅을 통해서 그야말로 마니악한 집요성으로
코드 최적화를 해 볼 수도 있겠습니다.

#include <stdio.h>

/* 이 소스코드는 기존 C 언어에서 지원하지 않는
무한자리수 산수 연산을 예시한다. 기본 데이터타입의 범위를 초과한
데이터의 덧셈과 뺄셈을 처리한다. */

#define LIMIT 32 /* 일단 여기서는 편의상 32자리 수로 제한한다. */
#define NUM ( ((LIMIT-1) / 4) + 1 ) /* 배열 크기다. short 타입의 배열을 쓴다. */

void iAdd(short*, short*, short*); /* 덧셈 */
void iSub(short*, short*, short*); /* 뺄셈 */
void iResult(short*); /* 결과 출력용 */

int main(int args)
{
    // 임의로 큰 수를 4자리로 short 배열에 담아두었다.
    // 실제 활용시에는 이 부분도 자동으로 되겠금 처리하는 게 좋다.
    static short a[NUM+2] = {1522,7849,2969,5432,1562,5092,8504,9562},
            b[NUM+2] = { 592,1949,6040,2947,6029,1156,5892,6782},
            c[NUM+2];
    
    printf("연산대상 A: 1522,7849,2969,5432,1562,5092,8504,9562₩n");
    printf("연산대상 B:  592,1949,6040,2947,6029,1156,5892,6782₩n");
    printf("---------------------------------------------------₩n");
    
    iAdd(a, b, c);
    printf("덧셈결과 +: ");
    iResult(c);
    
    iSub(a, b, c);
    printf("뺄셈결과 -: ");
    iResult(c);
    
    return 0;
}

// 긴 자리 덧셈하기
void iAdd(short a[], short b[], short c[])
{
    short i, cy=0;
    
    // 배열의 뒷자리부터 (그러니까 작은 단위수부터) 한 인덱스씩 계산한다.
    // 한 블럭의 덧셈결과가 10000 을 넘게 되면 자리올림수 처리를 해준다. (cy)
    for (i=NUM-1; i>=0; i--) {
        c[i] = a[i] + b[i] + cy;
        
        // 4자리수끼리 덧셈을 했는데도 10000 을 못넘겼으면 자리올림수는 없다!
        if(c[i] < 10000)
            cy = 0;
        else {
            c[i] = c[i] - 10000;
            cy = 1; // 덧셈해서 10000 을 넘겼다면 자리올림수를 만든다.
            // 위에서 만들어진 자리올림수는 그대로 다음 루프문에서 반영된다.
        }
    }
}

// 긴 자리수 뺄셈하기
void iSub(short a[], short b[], short c[])
{
    short i, br=0;
    
    // 역시 배열의 뒷자리 블럭부터 4자리씩 끊어서 계산한다.
    for (i=NUM-1; i>=0; i--) {
        c[i] = a[i] - b[i] - br;
        
        // 4자리수 뺄셈해서 결과가 0 이거나 혹은 0보다 크면 자리내림수는 없다!
        if (c[i] >= 0)
            br = 0;
        else {
            c[i] = c[i] + 10000; // 0보다 작게 되었다면 10000 을 빌리고
            br = 1; // 대신 그 윗자리수에서 1을 뺀다. 등가교환법칙
        }
    }
}

// 출력용
void iResult(short c[])
{
    short i;
    
    // 저장된 배열을 역시 4자리씩 끊어서 보여준다.
    for (i=0; i<NUM; i++)
        printf("%04d ", c[i]);
    printf("₩n");
}

알록달록한 코드 첫머리를 보고자 하실 분들을 위해... (찬조출연: GEdit)

upload image

개인적으로 파이썬에서는 어떻게 이런 긴자리수 연산을 지원하는지
무척 궁금합니다. 귀도 아저씨가 어떤 기가 막힌 알고리즘을 썼을지... ^^;;

에, 오늘은 좀 싱거웠나요?
사실 저는 이 부분 보면서 '아...! 이렇게 하면 되네...!' ...라는 둥,
뻘 소리를 남발했거든요. -_;;; (저는 처음에 한자리씩 차근차근 움직이면서
덧셈하고 자리올림수 만들고 다시 덧셈하고... 이런 걸 구상했었거든요. ㅠ.ㅠ)

참고로 긴 자리수 곱셈과 나눗셈 역시 여러분들께서 예측하신대로,
4자리수 씩 나눠서 쉽게 할 수 있습니다.
다만 곱셈의 경우는 4자리수씩 곱셈을 하고, 5번째 이상 숫자는 그 앞 블럭의
곱셈결과에 더해주는 과정을 처리해야 합니다. 그러니까 자리올림수가 한 개가
아니고 두 개가 있는 거지요. (직접 한 번 해 보아요~)

나눗셈의 경우는 좀 더 손을 가해야 합니다.
4자리수씩 하는 건 맞는데 4자리 나눗셈을 하고 나서 몫은 그대로 결과에 반영하고,
나머지는 10000배 해서 그 다음 블럭에 더한 후 다시 나누기를 해야 합니다.
그리고 계산 방향도 기존과는 반대방향이구요. (역시 직접 한 번 해 보아요~)

이 긴 자리수 사칙연산은 실제로도 굉장히 유용합니다.
어째서 유용한지, 내일 보여드릴 원주율(pi) 값 구하기편에서 같이 알아보도록 합시다.
(긴 자리수 나누기 함수도 사용합니다. 그리고 pi 값은 소수점 이하 1000 자리까지
슈퍼컴퓨터 뺨치고 달랠 수 있을 정도로 정확하게 구할 수 있습니다. ^^)

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

삼각함수를 이용한 원 그리기  (0) 2016.03.15
자동적에이전트 - Arrive  (0) 2016.02.22
자동적에이전트 - Flee  (0) 2016.02.21
피벗설정에 따른 퀵정렬의 속도  (0) 2016.01.18
자동적에이전트 - Seek  (0) 2016.01.17

코드설명은 부분적으로 설명하고 전체를 살펴보겠다.

동작 원리나 개념은 앞의 [MemoryPooling_1] 포스트를 참고하면 된다.



header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <vector>
 
class CPoolingManager
{
    typedef char* MEMORYBLOCK;
    
public:
    static CPoolingManager* Instance();
    
    void CreateObjectList(size_t count, size_t size);     // size만큼의 char형 포인터를 count만큼 Object리스트에 add
    void* ObjectNew();                                    // pool이 보유한 메모리 리스트가 생성하려는 것보다 적으면 새로 생성
    void ReturnToFreeMemory(void* object);               // Object을 메모리블럭으로 전환 (alive off)
    void ReturnToFreeMemoryAll();                        // 모든 Bullet을 메모리 블럭으로 전환 (alive off)
    void DeleteAllMemory();                               // 모든 메모리를 해제한다. (게임이 종료되거나 Scene이 변경될때 호출)
    
private:
    MEMORYBLOCK NewMemoryBlock(size_t sizeconst;        // size만큼의 메모리 블럭을 생성한다.
    CPoolingManager();
    ~CPoolingManager();                                   // 메모리 블럭을 해제한다.
    
private:
    std::vector<MEMORYBLOCK> m_ObjectList;
    int m_ObjectSize;
};
cs

 음.. 뭔가 무지 간단해 보인다.

주석을 달아 놓긴했지만 일단 하나씩 보자.



CPP

가장 먼저 


MEMORYBLOCK

이 MemoryPooling클래스는  MEMORYBLOC이라는 타입을 사용한다. 단순하게 char*이고 

여기에 원하는 만큼의 메모리를 동적할당해서 블럭형태로 만든다.

바로 NewMemoryBlock(size_t size)함수가 size만큼의 메모리 블럭을 만드는 역할을 한다.

내부는 아래와 같다.

1
2
3
4
5
6
CPoolingManager::MEMORYBLOCK CPoolingManager::NewMemoryBlock(size_t sizeconst
{
    MEMORYBLOCK block = new char[size + 1];         // memory alive를 위한 1바이트 추가 생성
    memset(block, 0size + 1);                       // memory 초기화 및 memory alive = false
    return block;             
}
cs

메모리 블럭을 사용 중인지 알아내기 위해 블럭의 원래 사이즈보다 1크게 할당한다.

또 memset을 이용하여 전체를 초기화한다. 이때 마지막 부분은 false로 되어 사용중이 아니라는 것을 체크한다.


이렇게 만든 메모리블럭들은 CreateObjectList(size_t count, size_t size)함수 안에서 리스트에 등록된다. 



CreateObjectList

1
2
3
4
5
6
7
8
9
10
11
void CPoolingManager::CreateObjectList(size_t count, size_t size)
{
    m_ObjectSize = size;
    m_ObjectList.reserve(count);
    while (count--)
    {
        MEMORYBLOCK memBlock = NewMemoryBlock(m_ObejctSize);        // 하나의 크기가 size만큼의 메모리 블럭을
        m_ObejctList.emplace_back(memBlock);                     // count만큼 생성한다.
        CObjectManager::Instance()->AddObject(memBlock);
    }
}
cs

다른 건 신경쓰지말고 중간에 ObjectManager를 보자.

메모리를 어딘가에 등록하고 있다. 

클래스 이름처럼 싱글톤으로 구현된 오브젝트 매니저 클래스이다.

ObjectManager에서는 각각의 블럭포인터를 리스트에 가지고 있다가 모든 object의 Execute를 하기 위해 사용한다.



ObjectNew

사실 제일 할말이 많은 부분이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void* CPoolingManager::ObjectNew()
{
    for (auto object : m_ObejctList)
    {
        if (false == object[m_ObjectSize]) {     // 메모리가 Free상태면
            object[m_ObjectSize] = true;         // 메모리를 사용 상태로 전환 후 반환
            return object;
        }
    }
 
    CCLOG("OBJECT LIST OVERFLOWED");
    MEMORYBLOCK memBlock = NewMemoryBlock(m_ObjectSize);
    m_ObejctList.emplace_back(memBlock);
    CObjectManager::Instance()->AddObject(memBlock);
    memBlock[m_ObjectSize] = true;               // 메모리를 사용 중 상태로 전환
    
    return memBlock;
}
cs

이 함수는 object가 생성될 때,  다시 말해 new 할때 object의 operator new에서 호출한다. 

(operator new에 대해서는 [operator new / operator delete] 포스트를 참고하자.)

상식적으로 이 함수가 수행해야 할 일은 원래의 new가 수행해야할 메모리 할당 이다.

함수의 첫부분을 보면 m_ObjectList를 순회하면서 각각의 object중 현재 사용중이지 않은 메모리를 찾아서 반환해준다. 

반환할 때에는 어떤 포인터가 들어있을지 모르기 때문에 void*로 반환하며 사용중 플래그를 true로 바꾼다.

만약 Object리스트의 모든 object들이 사용중 상태라면 메모리 블록을 하나 만들어서 기존 리스트에 추가한다.

( 이런일은 보통 생각했던것보다 많은 object를 생성하게되면 일어나는 상황이다. )



ReturnToFreeMemory

원래라면 메모리를 사용하고 나서 릭을 보지 않으려면 delete를 수행해야 할 것이다. 

하지만 PoolManager를 사용하고 있는 프로그램에서는 delete를 하지 않고 사용가능 상태로만 만들어준다.

이유는 앞 포스트에서도 설명했듯이 메모리풀링을 하기 위해서이다.

1
2
3
4
void CPoolingManager::ReturnToFreeMemory(void* object)
{
    memset(object, 0, m_ObjectSize + 1);                 // memory 초기화 및 memory alive = false
}
cs



DeleteAllMemory

모든 메모리를 실제로 해제하는 함수이다. 게임을 종료하거나 Scene이 바뀌어서 더이상 메모리를 사용하지 않을때 릭을 방지하기 위해 호출한다.

게임을 종료하기전에는 무조건 호출해야한다. 현재는 Scene이 하나이기 때문에 PoolManager클래스의 소멸자에서 호출한다.

1
2
3
4
5
6
7
8
void CPoolingManager::DeleteAllMemory()
{
    for (auto obejct : m_ObjectList)
    {
        delete []obejct;
    }
    m_ObjectList.clear();
}
cs


이상 포스트를 마친다.

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

ObjectManager  (0) 2016.04.12
PoolingManager  (0) 2016.04.12
게임 전체 클래스 UML 및 간단설명  (0) 2016.04.12
게임 개요  (0) 2016.04.11
MemoryPooling_1  (0) 2016.03.15

개인 프로젝트를 진행하다가 원을 그려야할 일이 생겨서 삼각함수를 이용하였다. 

후에 누군가에게 도움이 될 수 있으면 좋겠다는 생각으로 한번 정리해본다.




설명

원을 그리고 싶다는 것은 위 그림에서 θ가 0일때부터 360일때 까지의 점 p를 알고 싶다는 말과 같다.

각 앵글값일때의 점p는 위에서 나온 공식

sin(θ) = y / d   ==  y = sin(θ) * d  

cos(θ) = x / d   ==  x = cos(θ) * d

으로 구할 수있다.

다만 구해진 원은 d값을 반지름으로 하는 원일 것이다. 

또 C++에서 구현할때에는 앵글값을 0 ~ 360 까지 사용하는 것이 아니라 라디안으로 변환한 값을 사용해야한다.


코드는 아래와 같다.

1
2
3
4
5
6
7
8
for (int angle = 0; angle <= 360; angle++)
{
    // getPosition을 더한 것은 현재 오브젝트 위치에서 그리기 위함이다.
    int x = (cos(CC_DEGREES_TO_RADIANS(angle)) * m_fBoundingRadius) + getPosition().x;
    int y = (sin(CC_DEGREES_TO_RADIANS(angle)) * m_fBoundingRadius) + getPosition().y;
    // 구한 점과 오브젝트 포지션의 점을 이어주는 선을 그린다.
    DrawDebugLine(getPosition(), Point(x, y));
}
cs


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

큰 수 구하기  (0) 2016.03.15
자동적에이전트 - Arrive  (0) 2016.02.22
자동적에이전트 - Flee  (0) 2016.02.21
피벗설정에 따른 퀵정렬의 속도  (0) 2016.01.18
자동적에이전트 - Seek  (0) 2016.01.17

개인적으로 프로젝트를 진행하면서 새롭게 알게된 내용을 정리하려고 한다.

일단은 프로젝트 탭에서 작성하긴 하는데.. 어디에 들어갈지 애매하긴하다. 

언젠가 쓸 operator new,  operator delete에 대한 내용은 어느 탭에 들어가야 하지..

뭐 아무튼 그때가서 생각하기로 하겠다.


MemoryPooling

게임상에서는 많은 오브젝트들이 생성 혹은 삭제된다. 

많은 몬스터가 나오는 게임에서 

몬스터가 만들어질때 new, 죽을때 delete를 하게되면 게임성능 및 속도에 많은 영향을 미치게된다.

특히 탄막슈팅 게임의 경우, 각 총알마다 생성, 삭제를 하게된다면 끔찍한 결과를 보게될 것이다. 

이런경우 MemoryPooling 개념을 도입하면 성능 향상에 큰 도움이 될 것이다.


개념은 간단하다.

1. 게임을 시작하면 메모리 컨테이너에 필요한 만큼의 메모리를 미리 할당 해 놓는다. 초기의 모든 메모리는 Free상태이다.

2. 게임 플레이 중 오브젝트를 생성할 상황이 오면 메모리를 새로 할당하는 것이 아니라 

미리 생성해 놓은 메모리 중 Free메모리를 이용하여 오브젝트를 초기화한다.

4. 사용중인 메모리는 non_Free상태가 된다.

3. 사용 후 오브젝트를 삭제할 때 메모리를 해제하는 것이 아니라 Free메모리로 전환 시킨다.

즉, 게임 플레이중 메모리를 동적할당 하지 않는것이 핵심이다.

좀더 쉽게 그림으로 표현하면 아래와 같다.



장점

1. 위에서 언급했던 것처럼 플레이 중 생성과 삭제의 오버헤드에서 자유로울 수 있다.

2. 메모리 단편화를 막을 수 있다. (메모리 단편화에 대한 포스트는 따로 진행하겠다.)


단점

단순히 new와 delete를 했던 구조보다는 조금 복잡해질 수 있다. 

(물론 ObjectPooling사용자입장에서는 느끼지 못하도록 캡슐화를 해야한다.)


결과 

MemoryPooling을 이용하여 구현한 탄막과 그렇지 않은 탄막의 성능 및 속도 차이 


동영상

1. MemoryPooling사용안함

https://drive.google.com/file/d/0Bzy8i8T-Oo5kSTB3TDFhZU5rT3c/view?usp=sharing


2. MemoryPooling사용

https://drive.google.com/file/d/0Bzy8i8T-Oo5kcktSMjdockNISkk/view?usp=sharing


두 영상은 가운데 존재하는 원과 충돌하면 없어지는 탄막들이다. 

영상을 찍기 시작한 처음부분과 끝나는 부분을 제외하더라도

MemoryPooing개념을 도입한 이후 눈에 띄게 부드러워진 것을 확인 할 수 있었다.


코드에 관한 내용은 [MemoryPooling_2] 포스트에서 계속 이어나가겠다.

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

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

new와 malloc의 차이점 포스트가 아니니 아래의 내용은 안읽고 바로 operator new에 대해서 읽어도 무방하다.

operator new와 malloc의 차이점은 아래와 같다.

1. new는 생성자를 호출하지만 

 - malloc은 생성자를 호출하지 않는다.

2. new는 호출한 클래스 타입의 포인터를 반환하지만 

- malloc은 void*로 반환한다.

3. new로 동적할당을 하면 delete로 해제해 주어야 하지만

- malloc은 free로 해제 해야한다.

4. 추가로 delete는 소멸자를 호출하고 

- free는 호출하지 않는다.


자, 그럼 이제 왜 operator new를 설명하는데 위 개념이 필요한지 알아보자.


operator new

new가 하는일은 위에서도 설명했듯이 

1. 메모리를 할당하고 

2. 생성자를 호출하여 초기화 해준다.

위 두 가지 일은 사용자가 아무리 바꾸려해도 바뀌지 않는 불변의 법칙이다. 

사용자가 바꿀 수 있는 것이라고는 new에서 수행하는 첫번째 일인 '메모리를 할당하는 방법'이다. (두번째는 이후에 설명하겠다.) 

다시말해 new함수에서 메모리를 할당하기위해 malloc과 같은 기능의 어떤 함수를 호출하는데 

 어떤 함수를 오버로딩하여 변화를 줄수 있다는 의미이다.

그 함수의 이름이 operator new인 것이다. 

operator new를 오버로딩하게 되면 new에서 호출하는 operator new가 바로 내가 오버로딩한 함수가 되는 것이다. 



얼마나 할일이 없으면 저런것까지 오버로딩을 하지? 라는 생각을 하고 있다면 큰 오산이다.

위에서 말했듯이 메모리를 할당하는 방법을 오버로딩한다는 것은 다음과 같은 것을 할 수 있다는 이야기이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CObject* CObject::create()
{
    // 여기서 operator new가 호출됨
    CObject* pRet = (CObject*)new(std::nothrow)CObject();
    if (pRet && pRet->init())
    {
        return pRet;
    }
    else
    {
        delete pRet;
        pRet = NULL;
        return NULL;
    }
}
 
void* CObject::operator new(size_t sizeconst std::nothrow_t)
{
    // PoolingManager에서 메모리를 할당 받는다.
    return CPoolingManager::Instance()->ObjectNew();
}
cs

어떤 Object클래스의 한 부분이다. 

이 클래스는 생성자와 소멸자를 private으로 은닉화 시켜놓고 create으로만 객체를 생성할 수 있다. 

(이렇게 하면 생성시 꼭 해야할 일들을 수행할 수 있다.  )

create함수를 보면 new를 하고 있는데 이때 new안에서 호출되는 operator new함수는 내가 오버로딩한 것이다. 

때문에 실제 메모리는 할당되지 않으며 내가 오버로딩한 함수를 호출하는데, 

내가 오버로딩해 놓은 함수를 보니 어딘가에서 메모리를 얻어오는 것을 알 수 있다.(이부분에 대해서는 [MemoryPooling_2] 포스트를 참고하자)

위와 같은 방식으로 메모리를 조금더 효율적으로 할당할 수 있게 된다.(마찬가지로 무엇이 효율적인지에 대해서는 [MemoryPooling_1] 포스트를 참고하자)


operator delete

기본적인 원리는 앞에서 설명한 것과 같다. 단지 메모리를 해제하는 방식을 operator delete로 오버로딩하여 변경할 수 있다는 것이 다를 뿐이다.

1
2
3
4
5
void* CObject::operator delete(void* p)
{
    // PoolingManager에서 메모리를 메모리 블럭으로 되돌린다.
    return CPoolingManager::Instance()->ObjectDelete(p);
}
cs

PoolingManager의 ObjectDelete를 호출하는데 이 함수안에서는 메모리를 해제하는 것이 아니라 Free상태로 놓는다.

(Free상태의 메모리는 다시 create함수를 이용하여 사용상태로 전환될 수 있는 휴면상태 메모리이다.)


생성자 호출

위와 같은 방식으로 메모리를 초기화 하면 말 그대로 순수 메모리를 얻게될 것이다. 

마치 malloc으로 얻은 메모리처럼 말이다.

위에서는 마치 일반적인 operator new가 메모리도 할당하고 생성자도 알아서(?) 호출해주는 것처럼 말했지만 

사실 메모리 할당과 생성자 호출은 별개의 문제이다.

일반적으로 operator new를 호출 한다고 해서 생성자까지 호출해주지는 않는다.

위의 방식처럼 사용하기 위해서는 생성자가 호출되어야 하는데 말이다. (메모리 블럭을 객체로사용하는 방식) 

이것을 가능하게 해주는 것이 바로 위치지정(placement) operator new라는 것이다. 

쉽게 operator new의 확장판이라 인자로 전달되는 포인터 타입의 생성자를 호출해준다고 이해하면되겠다.


이렇게 생성자 호출이 끝나야만 해당 메모리는 제대로 객체로써 이용할 수 있게된다.

'All > C++' 카테고리의 다른 글

자주 사용하는 STL function  (0) 2016.11.28
메모리 누수 검사  (0) 2015.12.29
가변인자 로그 출력함수  (0) 2015.12.29
RValue와 LValue  (2) 2015.12.28
문장줄이기  (0) 2015.12.28

Arrive는 찾기와 동일한 로직을 사용한다.

다른점이라 한다면 목표지점에 도착할 때 속도를 감속시켜 멈추는 로직이 추가된다는 것이다.

감속을 위한 계산법으로는 이미 사용하고 있는 Speed 값을 거리로 나누는 방법을 사용하였다.

공식은 다음과 같다.

Speed = Dist / ( Deceleration * 0.3 ) ( 거리 / (감속정도 * 0.3) )
DeceleratedVelocity = TargetVelocity * Speed / Dist  감속된속력 = 원하는속력 * 속도 / 거리 )

위의 공식으로 생각해보면 속도와 거리가 줄어들수록 감속된 속력을 얻을 수 있다. 

원리는 거리가 줄어들면 Speed가 줄어들고 Speed와 거리가 줄어들면 결국 멈추도록 계산된다.

이렇게 얻은 감속된 속력에서 현재속도를 빼면 찾기때와 마찬가지로 조종힘 (D)을 얻을 수 있다. 

D = DV - 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
// 매개변수 1. 목표지점, 2. 감속정도
Vector2D SteeringBehavior::Arrive(Vector2D TargetPos, Deceleration deceleration)
{
    Vector2D TargetVelocity = TargetPos - m_pVehicle->Pos();
 
    // 거리구하기
    double dist = TargetVelocity.Length();
 
    // 거리가 0보다 크면 이동
    if (dist > 0)
    {
        // 속도 감속의 미세한 조종을 위한 상수
        const double DecelerationTweaker = 0.3;
 
        // 속도 = 거리 / (감속정도 * 미세조종상수)
        // 거리가 줄어들수록 속도가 줄어든다. (분자가 작아질수록 값은 작아진다.)
        double speed = dist / ((double)deceleration * DecelerationTweaker);
 
        // 계산된 속도가 최대속도를 넘는지 확인한다.
        speed = min(speed, m_pVehicle->MaxSpeed());
 
        // 감속된 속력 = 원하는 속력 * 속도 / 거리 
        Vector2D DeceleratedVelocity = TargetVelocity * speed / dist;
 
        // 조종힘 = 감속된 속력 - 현재속력
        return (DeceleratedVelocity - m_pVehicle->Velocity());
    }
 
    return Vector2D(00);
}
cs


+ 후에 동영상을 올릴 수 있으면 추가하겠다.

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

큰 수 구하기  (0) 2016.03.15
삼각함수를 이용한 원 그리기  (0) 2016.03.15
자동적에이전트 - Flee  (0) 2016.02.21
피벗설정에 따른 퀵정렬의 속도  (0) 2016.01.18
자동적에이전트 - Seek  (0) 2016.01.17

Flee는 Seek의 반대성격을 가진다.

즉 찾기의 반대인 달아나기이다.

찾기가 에이전트를 목표하는 위치로 향하게 하는 조종힘을 만들어 냈다면

반대로 달아나기는 목표에서 멀어지는 조종힘을 만들어 내면 될 것이다.

원리는 아래와 같다.


찾기 때와 유일한 차이점은 원하는 속도(T)를 찾기에서는 Goal - Pos를 통하여 구했다는점과 달아나기에서는 Pos - Goal을 통해 구했다는 점이다.

찾기때와 마찬가지로 점차적으로 회전하도록 하기 위해 조종힘(D)을 구한다. D = T - C

코드는 아래와 같다.


코드

1
2
3
4
5
6
7
8
9
Vector2D SteeringBehaviors::Flee(Vector2D GoalPos){
    // TargetVelocity = 원하는 속도    
    // 원하는 속도 구하는 공식 : T = (P - G) * speed
    // 순수 방향만 얻기 위해 정규화 후 speed를 곱한다.
    Vector2D TargetVelocity = Vec2DNormalize(GoalPos - m_pVehicle->Pos()) * m_pVehicle->MaxSpeed();
    // return value는 조종힘   
    // 조종힘 구하는 공식 : D = T - C
    return (TargetVelocity - m_pVehicle->Velocity());
}
cs


+ 후에 동영상을 올릴 수 있으면 추가하겠다.

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

삼각함수를 이용한 원 그리기  (0) 2016.03.15
자동적에이전트 - Arrive  (0) 2016.02.22
피벗설정에 따른 퀵정렬의 속도  (0) 2016.01.18
자동적에이전트 - Seek  (0) 2016.01.17
QuickSort  (0) 2016.01.07

+ Recent posts