개요

cocos2dx 자체의 버튼 클래스가 존재하는 것으로 알지만,

원하는 기능인 버튼이 눌린 상태를 체크할 방법과 여러 콜백함수 등록기능을 사용하기 위해 자체 클래스를 만들었습니다.

최대한 사용자가 버튼을 만들어 사용할 때 편리할 수 있도록 구현하였습니다.


코드 및 설명은 아래와 같습니다. 

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


코드

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*--------------------------CMyButton--------------------------
 * strategy pattern
 * texture 혹은 label로 초기화하여 사용하는 Button
 * Lambda혹은 함수포인터, 함수객체를 인자로 받는다.
 * 눌렀을때 체크
 * 누르고 있을 때 체크
 * 끝났을 때 체크 
 * ------------------------------------------------------------*/
 
 
 
 
/* 버튼의 상태 */
enum eMYBUTTON_STATE{
    BEGIN = 0,
    EXECUTE = 1,
    END = 2,
}
 
 
/* 텍스쳐와 함께 버튼 생성 
 * normalImg와 selectedImg를 인자로 전달*/
CMyButton* CMyButton::createWithTexture(
    std::string normalTextureName,                // 선택 전 버튼의 텍스쳐 이름
    std::string selectedTextureName,              // 선택 중 버튼의 텍스쳐 이름
    eMYBUTTON_STATE state,                        // 버튼 상태 (해당 상태일 때 함수 호출됨)
    const std::function<void(void)> &func)        // 람다 혹은 함수포인터 혹은 함수객체 전달(매개 변수는 void)
 
 
/* 문자열과 함께 버튼 생성 */
CMyButton* CMyButton::createWithString(
    std::string normalTextureName,                // 버튼의 텍스쳐 이름
    std::string labelString,                      // 버튼의 label 내용
    int fontSize,                                 // 폰트 사이즈
    eMYBUTTON_STATE state,                        // 버튼 상태 (해당 상태일 때 함수 호출됨)
    const std::function<void(void)> &func)        // 람다 혹은 함수포인터 혹은 함수객체 전달(매개 변수는 void)
 
 
/* 버튼의 함수 포인터 리스트에 함수 포인터 추가 */
void AddState(eMYBUTTON_STATE state,             // 상태 (해당 상태일 때 함수 호출됨)
    const std::function<void(void)> &func)        // 람다 혹은 함수포인터 혹은 함수객체 전달(매개 변수는 void)
 
 
/* 터치된 좌표를 버튼의 좌표로 변환 한 후에 버튼이 터치되었는지 검사 */
bool touchHits(Touch  *touch)
{
    const Rect area(00, m_pNormalTexture->getContentSize().width, 
        m_pNormalTexture->getContentSize().height);
 
    // world to nodespace 좌표 변환
    return area.containsPoint(m_pNormalTexture->convertToNodeSpace(touch->getLocation()));
}
 
 
/* 버튼이 눌렸을 때 BEGIN */
bool onTouchBegan(Touch  *touch, Event  *event)
{
    CC_UNUSED_PARAM(event);
    if (m_pNormalTexture){
 
        // 좌표 변환 후 터치 체크
        m_IsSelect = touchHits(touch);
 
        // 버튼의 좌표에서 진짜로 터치되었을 경우
        if (m_IsSelect){
 
            // BEGIN 상태일때 호출해야할 함수가 있다면 호출
            std::for_each(m_BeginFuncList.begin(), m_BeginFuncList.end(), 
                [](const std::function<void(void)> &func){
                func();
            });
            
            // 선택 시 이미지가 있다면 이미지 교체
            if (m_SelectedTextureName != ""){                    
                m_pNormalTexture->setTexture(m_SelectedTextureName);
            }
            else { // 교체될 이미지가 없다면 크기를 키움                                                
                m_pNormalTexture->setScale(1.1f);
            }
        }
    }
    return m_IsSelect;
}
 
 
/* 버튼에서 떨어졌을 때 END */
void CMyButton::onTouchEnded(Touch  *touch, Event  *event)
{
    CC_UNUSED_PARAM(event);
    if (m_pNormalTexture){
 
        // END 상태일때 호출해야할 함수가 있다면 호출
        std::for_each(m_EndFuncList.begin(), m_EndFuncList.end(), 
            [](const std::function<void(void)> &func){
            func();
        });
 
        // 이미지가 바뀌었었다면 다시 원래대로 바꿈
        if (m_SelectedTextureName != ""){                        
            m_pNormalTexture->setTexture(m_NormalTextureName);
        }
        else// 바뀔이미지가 없다면 크기를 원래대로 바꿈                                                    
            m_pNormalTexture->setScale(1.0f);
        }
 
        // 버튼 눌림 종료
        m_IsSelect = false;
    }
}
 
 
/* 버튼이 눌리고 있는 중일때 EXECUTE */
void Execute(float delta)
{
    // 버튼 눌림 상태이며, EXECUTE 상태일 때 실행되어야 할 함수가 1개 이상일 때
    if (m_IsSelect && m_ExecuteFuncList.size())
    {
        // EXECUTE 상태일때 호출해야할 함수가 있다면 호출
        std::for_each(m_ExecuteFuncList.begin(), m_ExecuteFuncList.end(), 
            [](const std::function<void(void)> &func){
            func();
        });
    }
}
 
 
/* lambda, 함수 포인터를 리스트로 저장
 * 각 상태일 때 호출할 함수를 저장하는 리스트 */
std::vector<std::function<void(void)>> m_BeginFuncList;
std::vector<std::function<void(void)>> m_ExecuteFuncList;
std::vector<std::function<void(void)>> m_EndFuncList;
cs

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

1. 전략 디자인 패턴의 변형으로 버튼 생성시 함수 포인터를 전달합니다.

2. 때문에 사용자는 버튼을 눌렀을 때의 상황에 대해 부담없이 정의할 수 있습니다.

3. 각 버튼은 Begin, Execute, End의 상태를 가질 수 있습니다.

4. 각 상태에 맞는 함수를 생성시 초기화 혹은 AddState 함수로 추가할 수 있습니다. 


사용 예

1
2
3
4
5
auto leftButton = CMyButton::createWithTexture(
        "leftButton_1.png",
        "leftButton_2.png",
        EXECUTE,
        std::bind(&CObjectManager::RotationObject, CObjectManager::Instance(), -1)); 
cs

1. 눌리기 전엔 leftButton_1.png, 눌린 상태엔 leftButton_2.png의 이미지를 보이는 버튼을 생성합니다.

2. 버튼이 눌린 상태(EXECUTE)일 때 CObjectManager::Instance()의 RotationObject(int dir) 함수를 호출합니다.

3. std::bind : 함수 호출 시 전달하는 매개변수를 -1로 고정하고, 호출하는 인스턴스는 CObjectManager::Instance()로써 bind합니다.



귀차니즘을 위한 2줄 요약

1. Button에는 Begin, Execute, End 3 가지의 상태가 존재한다.

2. Button을 create할때 각 상태에 호출될 함수를 넣을 수 있다.


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

매주 일요일 랭킹 초기화  (0) 2017.04.05
UI_ HealthBarUI  (0) 2016.04.12
별게임의 상태트리  (0) 2016.04.12
AI_ FSM / State  (0) 2016.04.12
Shooter / Bullet  (0) 2016.04.12

앞선 FSM 및 State를 이용한 별게임의 상태 트리는 다음과 같습니다.





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

UI_ HealthBarUI  (0) 2016.04.12
UI_ MyButton  (0) 2016.04.12
AI_ FSM / State  (0) 2016.04.12
Shooter / Bullet  (0) 2016.04.12
ObjectManager  (0) 2016.04.12

개요

별게임에 나오는 모든 오브젝트는 다음과 같은 상태를 가질 수 있습니다.

1. 자석 아이템을 먹었을때의 상태

2. 거인 아이템을 먹었을때의 상태

3. 코인으로 변화시키는 아이템을 먹었을 때의 상태

4. 별로 변화시키는 아이템을 먹었을 때의 상태

위 4개의 상태는 공존할 수 있습니다.(3번 4번은 서로 공존할 수 없다.)

즉, 플레이어가 거인인 상태에서 자석을 먹고 모든 bullet이 코인으로 변화되는 아이템을 먹을 수 있다는 것입니다.


위와 같은 복잡한 상황을 구현하기 위해 FSM과 State를 도입하였습니다.

먼저 FSM(stateMachine)의 코드는 다음과 같습니다.

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


FSM 코드

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
/*------------------------CStateMachine------------------------
 * Template 클래스
 * FSM사용을 원하는 클래스에서 정의하여 사용할 수 있다.
 * 사용자는 States를 가지고 있어야 한다.
 * Global, Current, Previous 상태를 가지고 있다.
--------------------------------------------------------------*/
 
 
 
 
/* template으로 정의됨 */
template <class T>
 
 
/* 생성 시 FSM을 사용하고자하는 객체의 This를 넘겨 받는다. */
CStateMachine(T* Owner)
 
 
/* 현재 상태를 전달받은 상태로 변경한다. 
 * 현재 상태는 이전상태에 저장한다. 
 * 현재상태의 Exit함수를 호출하며 새로운 생태의 Enter함수를 호출한다. */
template <class T>
void CStateMachine<T>::ChangeState(CState<T>* pNewState)
{
    if (m_pCurrentState != nullptr)
        m_pCurrentState->Exit(m_pOwner);
    m_pPreviousState = m_pCurrentState;
    m_pCurrentState = pNewState;
    m_pCurrentState->Enter(m_pOwner);
}
 
 
/* 현재 상태와 Global상태를 Execute */
template <class T>
void CStateMachine<T>::Execute(float delta) const
{
    if (m_pGlobalState != nullptr)
        m_pGlobalState->Execute(m_pOwner, delta);
    if (m_pCurrentState != nullptr)
        m_pCurrentState->Execute(m_pOwner, delta);
}
 
 
/* FSM을 사용하고자 하는 주체 */
T* m_pOwner;
 
 
/* Previous 상태는 직전의 상태를 보관한다. 
 * Current에 들어있는 상태를 실행한다.
 * Global은 어떠한 상황에서도 가장 먼저 반영된다. */
CState<T>*            m_pPreviousState;
CState<T>*            m_pCurrentState;
CState<T>*            m_pGlobalState;
cs

CStateMachine<T> 클래스의 주요 함수 및 동작원리 설명

1. 유한 상태 머신 디자인 패턴으로 구현되어 있습니다.

2. Generic Programming 을 위한 Template으로 정의 되어있습니다. 

3. ChangeState에서 이전 상태의 후 처리 함수를 호출하고 새로운 상태의 전처리 상태를 호출하면서 변경합니다.

4. 오브젝트의 AI를 상태머신으로 관리하고 싶을때 Component로 정의하면됩니다. 


FSM 사용 예 ( Player의 FSM ) 

1
CStateMachine<CPlayer>* m_FSM;
cs

1. 플레이어의 AI를 FSM으로 관리하기 위해 정의

2. Player::Execute에서 FSM의 Execute 실행

3. Player의 AI상태를 실행 및 변경 합니다.



아래는 위 FSM 이 관리하는 State 추상클래스입니다.


State 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*---------------------------CState----------------------------
 * Template 클래스
 * FSM을 소유하는 클래스의 상태를 정의하는 추상클래스
 * State의 인터페이스만을 제공한다.
 * 각 함수는 모두 FSM의 주체 인스턴스를 인자로 받는다.
--------------------------------------------------------------*/
 
 
 
 
/* template으로 정의됨 */
template <class T>
 
 
/* Enter()은 상태가 Execute를 실행하기 전에 한번만 호출된다. */
virtual void Enter(T* pOwner) = 0;
 
 
/* Execute()은 상태의 메인 루프이다. */
virtual void Execute(T* pOwner, float delta) = 0;
 
 
/* Exit()은 상태가 종료 혹은 변경될 때 한번만 호출된다. */
virtual void Exit(T* pOwner) = 0;
cs

1. 위의 FSM이 관리하는 State들의 인터페이스 클래스입니다.(추상클래스)

2. 어떤 오브젝트이든 상태를 가질 수 있도록 template으로 정의하였습니다.

3. 전처리를 위한 Enter, 반복 실행을 위한 Execute, 후처리를 위한 Exit으로 구성되어 있습니다.


사용 예(Player의 상태 중 1개)

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
 
class CPlayerNormal : public CState<CPlayer>{
 
public:
    static CPlayerNormal* Instance();
    
    /* 상태 전 처리 */
    void Enter(CPlayer* player)
    {}    
 
    void Execute(CPlayer* player, float delta)
    {
        /* 현재 플레이어가 거인 아이템을 먹었다면 */
        if(eITEM_FLAG_giant & CItemManager::Instance()->getCurrentItem())
        {
            /* 거인 아이템을 먹은 상태로 상태 변경 */
            player->getFSM()->ChangeState(CPlayerGiant::Instance());    
        }
    }
 
    /* 상태 후 처리 */
    void Exit(CPlayer* player)
    {}
 
private:        
    CPlayerNormal(){}    
    virtual ~CPlayerNormal(){}
}
cs

1. CPlayerNormal은 플레이어 클래스의 상태 중 하나입니다.

2. 주체로써 Player의 인스턴스를 전달합니다 



귀차니즘을 위한 3줄 요약

1. 게임의 상태표현을 위해 FSM구현

2. Stage, Player, Bullet은 상태를 가진다.

3. 각각의 상태는 Ente, Execute, Exit의 상태를 가진다.

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

UI_ MyButton  (0) 2016.04.12
별게임의 상태트리  (0) 2016.04.12
Shooter / Bullet  (0) 2016.04.12
ObjectManager  (0) 2016.04.12
PoolingManager  (0) 2016.04.12

+ Recent posts