안드로이드/cocos2d-x2016. 1. 17. 04:39

지난 시간에

터치를 통해 블록이 정상적으로 선택됨을 확인했다.

이재 블록을 터치->드래그->릴리즈를 통해 블록을 교환하는 기능을 구현할 것이다.

 

터치->드래그->릴리즈 구현하기

CGameLayer.h

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
#ifndef PuzzleGame_GameLayer
#define PuzzleGame_GameLayer
 
#include "Common.h"
#include "GameObject.h"

 

 
class CGameLayer : public cocos2d::Layer {
public:
    static cocos2d::Scene* createScene();
 
    virtual bool init();
 
    void startGame();
 
    bool isAdjacent(int x1, int y1, int x2, int y2);    // 두 블록이 서로 인접한 블록인지
    void swapObjects(int x1, int y1, int x2, int y2);    // 두 블록의 위치를 서로 교환
 
    void onTouchesBegan(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent);
    void onTouchesMoved(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent);
    void onTouchesEnded(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent);
 
    CREATE_FUNC(CGameLayer);
 
protected:
    CGameObject* m_pBoard[COLUMN_COUNT][ROW_COUNT];
    Size m_winSize;
 
    bool m_bTouchStarted;    // 터치가 시작되었는지
    int m_gestureStartBoardX; 
    int m_gestureStartBoardY; // 터치가 시작된 블록이 어느 블록인지
};
 
#endif
cs

 빨간 색으로 표시된 부분이 변경된 부분이다.

 초록색으로 표시된 부분은 추가로 발견된 실수한 부분이다.

 

CGameLayer.cpp

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include "GameLayer.h"
#include "GameObject.h"
#include <stdio.h>
#include <string.h>
 
 
#define COCOS2D_DEBUG 1
USING_NS_CC;
 
cocos2d::Scene* CGameLayer::createScene() {
    auto pScene = Scene::create();
    auto pLayer = CGameLayer::create();
 
    pScene->addChild(pLayer);
 
    return pScene;
}
 
bool CGameLayer::init() {
    if (!Layer::init()) { return false; }
 
    // 배경 이미지 출력
    cocos2d::Sprite* pBackgroundSprite = cocos2d::Sprite::create("background.png");
    pBackgroundSprite->setPosition(cocos2d::CCPointZero);
    pBackgroundSprite->setAnchorPoint(ccp((float)0, (float)0));
    addChild(pBackgroundSprite);
 
    
    // 터치 초기화
    EventListenerTouchAllAtOnce* listener = EventListenerTouchAllAtOnce::create();
    listener->onTouchesBegan = CC_CALLBACK_2(CGameLayer::onTouchesBegan, this);
    listener->onTouchesMoved= CC_CALLBACK_2(CGameLayer::onTouchesMoved, this);
    listener->onTouchesEnded = CC_CALLBACK_2(CGameLayer::onTouchesEnded, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
 
 
    m_winSize = Director::sharedDirector()->getWinSize();
    startGame();
    return true;
}
 
void CGameLayer::startGame() {
    srand(time(NULL));
    for (int x = 0; x < COLUMN_COUNT; x++) {
        for (int y = 0; y < ROW_COUNT; y++) {
            if (x == COLUMN_COUNT - 1 && y % 2 != 0continue;
 
            int type = rand() % TYPE_COUNT;
 
            Sprite* pGameObjectBack = Sprite::create("blockBack.png");
            CGameObject* pGameObject = CGameObject::Create(type);
            m_pBoard[x][y] = pGameObject;
 
            
            // debug --------------------------------------------------------------
            // 각 블록의 좌표를 출력
            std::string str, str2, str3;
            str = std::to_string(x);
            str2 = ",";
            str3 = std::to_string(y);
            str.append(str2);
            str.append(str3);
            auto label = LabelTTF::create(str, "Arial"50);
            Point pos; pos.set(Common::ComputeXY(x, y));
            label->setPosition(pos);
            // end of debug -------------------------------------------------------
            
 
            pGameObjectBack->setAnchorPoint(ccp(0.5f, 0.5f));
            pGameObjectBack->setPosition(Common::ComputeXY(x, y));
            pGameObject->setAnchorPoint(ccp(0.5f, 0.5f));
            pGameObject->setPosition(Common::ComputeXY(x, y));
            
 
            addChild(pGameObjectBack, 1);
            addChild(pGameObject, 2);
            addChild(label, 3);
        }
    }
    m_bTouchStarted = false;
 
}
 
 
// [터치 관련 ]
// ==============================================================================================================
void CGameLayer::onTouchesBegan(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    if (!m_bTouchStarted) {
        Touch* pTouch = (Touch*)pTouches.back();
        Point point = pTouch->getLocation();
 
        m_gestureStartBoardX = Common::ComputeBoardX(point);
        m_gestureStartBoardY = Common::ComputeBoardY(point);
 
        m_bTouchStarted = true;
    }
}
 
void CGameLayer::onTouchesMoved(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    if (m_bTouchStarted) {
        Touch* pTouch = (Touch*)pTouches.back();
        Point point = pTouch->getLocation();
    }
}
 
void CGameLayer::onTouchesEnded(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    if (m_bTouchStarted) {
        Touch* pTouch = (Touch*)pTouches.back();
        Point point = pTouch->getLocation();
 
        m_bTouchStarted = false;
 
        int boardX = Common::ComputeBoardX(point);
        int boardY = Common::ComputeBoardY(point);
 
        if (m_gestureStartBoardX != boardX || m_gestureStartBoardY != boardY)
        if (isAdjacent(m_gestureStartBoardX, m_gestureStartBoardY, boardX, boardY))
            swapObjects(m_gestureStartBoardX, m_gestureStartBoardY, boardX, boardY);
    }
}
 
// 두 블록이 서로 인접한 블록인지 검사
bool CGameLayer::isAdjacent(int x1, int y1, int x2, int y2) {
    if (y1 % 2 == 0) {
        if ((x1==x2) && (y1 - 2 == y2 || y1 - 1 == y2 || y1 + 1 == y2 || y1 + 2 == y2))
            return true;
        if ((x1 - 1 == x2) && (y1 - 1 == y2 || y1 + 1 == y2))
            return true;
    }
    else {
        if ((x1 == x2) && (y1 - 2 == y2 || y1 - 1 == y2 || y1 + 1 == y2 || y1 + 2 == y2))
            return true;
        if ((x1 + 1 == x2) && (y1 - 1 == y2 || y1 + 1 == y2))
            return true;
    }
    return false;
}
 
// 두 블록의 위치를 서로 교환
void CGameLayer::swapObjects(int x1, int y1, int x2, int y2) {
    CGameObject* pTemp = m_pBoard[x1][y1];
    m_pBoard[x1][y1] = m_pBoard[x2][y2];
    m_pBoard[x2][y2] = pTemp;
 
    m_pBoard[x1][y1]->setPosition(Common::ComputeXY(x1, y1));
    m_pBoard[x2][y2]->setPosition(Common::ComputeXY(x2, y2));
}
// ==============================================================================================================
cs

 빨간 색으로 표시된 부분이 추가된 부분이다.

 

80행 - '터치가 시작되었는지' 변수 초기화

87~97행 - 터치가 시작되었을 때 호출되는 이벤트 함수

              터치가 시작되었는지와 터치가 시작된 위치를 기억하게 한다.

106~120행 - 터치가 종료되었을 때호출되는 이벤트 함수

                 터치가 종료되면

                 해당 위치의 블록이 처음 블록과 인접한 블록인지를 검사하고

                 맞다면 두 블록의 위치를 바꾼다.

 

123~137행 - 두 블록이 인접한 블록인지를 검사하는데

                 블록을 그릴 때와 마찬가지로 y가 홀수일 때와 짝수일 때를 구분지어서 구현한다.

위 사진을 분석해보면

 y1이 짝수인 2,2를 기준으로 인접한 여섯블록의 공통점을 보면

x1==x2일 때는 y1과 y2가 1~2의 차이가 나고

x1-1==x2일 때는 y1과 y2가 1 차이난다는 것을 알 수 있다.

 마찬가지로 y1이 홀수일 때를 기준으로 3,3을 보면

x1==x2일 때는  y1과 y2가 1~2의 차이가 나고

x1+1==x2일 때는 y1과 y2가 1 차이난다는 것을 알 수 있다.

 

 이렇게 분석한 값을 바탕으로 구현한 것이다.

 

139~140행 - 두 블록의 위치를 바꾸는 함수로

                 일반적인 swap함수처럼 temp변수를 활용해 배열의 두 값을 변경하고

                 두 블록의 화면 상 위치에 적용한다.

 

 

Posted by gharlic
안드로이드/cocos2d-x2016. 1. 17. 03:34

지난 게시물에 이어서

터치를 사용해 오브젝트를 제어할 것이다.

먼저 퍼즐 블록이 터치를 통해 제대로 선택되는지 확인해야 한다.

 

블록 선택하기

CGameLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
 
void CGameLayer::onTouchesBegan(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    Touch* pTouch = (Touch*)pTouches.back();
 
    Point point = pTouch->getLocation();
 
    int boardX = Common::ComputeBoardX(point);
    int boardY = Common::ComputeBoardY(point);
 
    CCSprite* pCGameObject = m_pBoard[boardX][boardY];
    pCGameObject->setVisible(!pCGameObject->isVisible());
}
cs

 빨간 색으로 표시한 부분이 추가된 부분이다.

7,8행 - Common에 추가할 ComputeBoardX, Y함수를 통해

          터치한 좌표에 위치한 블록이 어느 블록인지 2차원 배열 좌표값을 얻어온다.

         ex) 500,500 터치 -> m_pBoard[2][3] ( 이 예시는 실제와 다름 )

10,11행 - 선택된 블록을 켜고 끈다 (보였다 안보였다 한다)

 

Common.cpp

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
// 화면좌표 to 내부좌표
int Common::ComputeBoardX(Point point) {
    for (int x = 0; x < COLUMN_COUNT; x++) {
        for (int y = 0; y < ROW_COUNT; y++) {
            Point hexagonPoint;
            hexagonPoint.set(ComputeXY(x, y));
            if (isInHexagon(hexagonPoint, point))
                return x;
        }
    }
    return 0;
}
int Common::ComputeBoardY(Point point) {
    for (int x = 0; x < COLUMN_COUNT; x++) {
        for (int y = 0; y < ROW_COUNT; y++) {
            Point hexagonPoint;
            hexagonPoint.set(ComputeXY((float)x, (float)y));
            if (isInHexagon(hexagonPoint, point))
                return y;
        }
    }
    return 0;
}
 
// 임의의 점이 두 점을 지나는 직선보다 아래에 위치하는지 검사
bool Common::isUnderLine(Point lines1, Point lines2, Point point) {
    float m = (lines2.y - lines1.y) / (lines2.x - lines1.x);    // 기울기
    //float n = lines1.y - m*lines1.x;                            // y절편
    float n = (lines2.x*lines1.y - lines1.x*lines2.y) / (lines2.x - lines1.x);
 
    return point.y <= (m*point.x + n);
}
 
// 임의의 점이 육각형 블록 내부에 위치하는지 검사
bool Common::isInHexagon(Point point, Point anyPoint) {
    Point p1, p2, p3, p4, p5, p6;
    p1.setPoint(point.x - (float)(OBJECT_WIDTH / 4), point.y + (float)(OBJECT_HEIGHT) / 2);
    p2.setPoint(point.x + (float)(OBJECT_WIDTH / 4), point.y + (float)(OBJECT_HEIGHT) / 2);
    p3.setPoint(point.x - (float)(OBJECT_WIDTH / 2), point.y);
    p4.setPoint(point.x + (float)(OBJECT_WIDTH / 2), point.y);
    p5.setPoint(point.x - (float)(OBJECT_WIDTH / 4), point.y - (float)(OBJECT_HEIGHT) / 2);
    p6.setPoint(point.x + (float)(OBJECT_WIDTH / 4), point.y - (float)(OBJECT_HEIGHT) / 2);
 
    if (!isUnderLine(p1, p2, anyPoint)) return false;
    if (!isUnderLine(p2, p4, anyPoint)) return false;
    if ( isUnderLine(p4, p6, anyPoint)) return false;
    if ( isUnderLine(p6, p5, anyPoint)) return false;
    if ( isUnderLine(p5, p3, anyPoint)) return false;
    if (!isUnderLine(p3, p1, anyPoint)) return false;
    return true;
}
cs

 터치된 좌표가 육각형 테두리 안에 속하는 지를 검사해야 하는데

참고하고 있는 책의 예시는 사각형 퍼즐이라 간단하게

배열좌표 to 화면좌표 함수의 역함수를 만들어

터치된 좌표를 통해 배열의 좌표를 역추적할 수가 있는데

 

 여기선 육각형이라서 서로 좌표가 겹치는 부분 때문에 다른 방식을 취해야 한다.

 

원리부터 얘기하면

 우리가 육각형테두리를 그릴 때 육각형의 중심을 0,0으로 정했던 것을 기억할 것이다.

육각형 하나만 놓고 봤을 때 터치된 지점을 임의의 점으로 보고

육각형을 2차원 좌표에 위치하는 도형으로 생각한다.

 

대충 이런 느낌이다.

6각형은 6개의 선분으로 이루어져 있고

6개의 선분은 6개의 꼭짓점을 이용해 직선 방정식으로 표현할 수 있다.

이 6개의 직선 사이에 임의의 점이 위치하면

육각형을 클릭한 것으로 보는 것이다.

 

해설들어간다

26~32행 - 2점 사이를 지나는 직선의 방정식을 구하는 함수이다.

              y = ax + b의 형태로 표현하는데

              주어진 두점 x1,y1, x2,y2를 계산해 직선의 방정식을 구하고

              임의의 점 x3,y3를 대입했을 때 y3가 방정식의 해보다 작거나 같으면

              임의의 점이 직선과 겹치거나 혹은 직선 보다 아래쪽에 있다는 것을 계산한다.

              겹치는 경우를 포함하지 않으면 정확하게 육각형의 테두리를 터치했을 때가 처리가 안되기 때문이다.

 

35~42행 - 육각형의 중심을 0,0으로 잡았으므로 도형의 가로, 세로 길이를 알고 있기 때문에

              6개의 꼭짓점을 구할 수 있다. x = { -가로길이/2, -가로길이/4, +가로길이/4, +가로길이/2 } 가 있고

              y =  { +세로길이/2, 0, -세로길이/2 }가 있다.

              왼쪽 위 꼭짓점부터 z자를 그리며 점 1,2,3,4,5,6의 좌표를 지정했다.

 

44~50행 - 26행의 isUnderLine() 함수를 활용해 임의의 점이

              육각형의 내부에 있는지를 검사한다.

 

1~23행 - 이렇게 만들어진 isInHexagon() 함수를 사용해

            모든 육각형에 대해서 터치된 지점이 해당 육각형에 포함되는 지를 검사하고

            해당하는 육각형의 배열 좌표를 반환한다.

 

 

 터치된 지점의 블록이 잘 켜고 꺼짐을 확인했다.

 

Posted by gharlic
안드로이드/cocos2d-x2016. 1. 17. 02:53

일단 터치기능을 세팅해보자

 터치 기능으로 무얼하는지는 나중에 생각하고 일단 터치부터 세팅한다.

CGameLayer.h

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
#ifndef PuzzleGame_GameLayer
#define PuzzleGame_GameLayer
 
#include "Common.h"
 
class CGameLayer : public cocos2d::Layer {
public:
    static cocos2d::Scene* createScene();
 
    virtual bool init();
 
    void startGame();
 
    void onTouchesBegan(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent);
    void onTouchesMoved(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent);
    void onTouchesEnded(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent);
 
    CREATE_FUNC(CGameLayer);
 
protected:
    Sprite* m_pBoard[COLUMN_COUNT][ROW_COUNT];
    Size m_winSize;
};
 
#endif
cs

 

CGameLayer.cpp

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
#include "GameLayer.h"
#include "GameObject.h"
#include <stdio.h>
#include <string.h>
 
 
#define COCOS2D_DEBUG 1
USING_NS_CC;
 
cocos2d::Scene* CGameLayer::createScene() {
    auto pScene = Scene::create();
    auto pLayer = CGameLayer::create();
 
    pScene->addChild(pLayer);
 
    return pScene;
}
 
bool CGameLayer::init() {
    if (!Layer::init()) { return false; }
 
    // 배경 이미지 출력
    cocos2d::Sprite* pBackgroundSprite = cocos2d::Sprite::create("background.png");
    pBackgroundSprite->setPosition(cocos2d::CCPointZero);
    pBackgroundSprite->setAnchorPoint(ccp((float)0, (float)0));
    addChild(pBackgroundSprite);
 
    
    // 터치 초기화
    EventListenerTouchAllAtOnce* listener = EventListenerTouchAllAtOnce::create();
    listener->onTouchesBegan = CC_CALLBACK_2(CGameLayer::onTouchesBegan, this);
    listener->onTouchesMoved= CC_CALLBACK_2(CGameLayer::onTouchesMoved, this);
    listener->onTouchesEnded = CC_CALLBACK_2(CGameLayer::onTouchesEnded, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
 
 
    m_winSize = Director::sharedDirector()->getWinSize();
    startGame();
    return true;
}
 
void CGameLayer::startGame() {
    srand(time(NULL));
    for (int x = 0; x < COLUMN_COUNT; x++) {
        for (int y = 0; y < ROW_COUNT; y++) {
            if (x == COLUMN_COUNT - 1 && y % 2 != 0continue;
 
            int type = rand() % TYPE_COUNT;
 
            Sprite* pGameObjectBack = Sprite::create("blockBack.png");
            CGameObject* pGameObject = CGameObject::Create(type);
            m_pBoard[x][y] = pGameObject;
 
            
            // debug --------------------------------------------------------------
            // 각 블록의 좌표를 출력
            std::string str, str2, str3;
            str = std::to_string(x);
            str2 = ",";
            str3 = std::to_string(y);
            str.append(str2);
            str.append(str3);
            auto label = LabelTTF::create(str, "Arial"50);
            Point pos; pos.set(Common::ComputeXY((float)x, (float)y));
            label->setPosition(pos);
            // end of debug -------------------------------------------------------
            
 
            pGameObjectBack->setAnchorPoint(ccp(0.5f, 0.5f));
            pGameObjectBack->setPosition(Common::ComputeXY((float)x, (float)y));
            pGameObject->setAnchorPoint(ccp(0.5f, 0.5f));
            pGameObject->setPosition(Common::ComputeXY((float)x, (float)y));
            
 
            addChild(pGameObjectBack, 1);
            addChild(pGameObject, 2);
            addChild(label, 3);
        }
    }
}
 
void CGameLayer::onTouchesBegan(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    Touch* pTouch = (Touch*)pTouches.back();
 
    Point point = pTouch->getLocation();
 
    CCLog("Point = %f, %f", point.x, point.y);
}
 
void CGameLayer::onTouchesMoved(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    Touch* pTouch = (Touch*)pTouches.back();
 
    Point point = pTouch->getLocation();
}
 
void CGameLayer::onTouchesEnded(const std::vector<cocos2d::Touch*>&pTouches, cocos2d::Event* pEvent) {
    Touch* pTouch = (Touch*)pTouches.back();
 
    Point point = pTouch->getLocation();
}
cs

빨간 색으로 표시된 부분이 변경된 부분이다.

터치 이벤트 함수에서 CCLog() 함수를 통해 터치된 좌표를 디버깅할 수 있다.

Windows에서 Log를 찍어보려면 따로 세팅해줘야하고 번거로우니

Eclips로 넘어가서 찍어보고 온다.

 

 

 

 

Eclipse에서 CatLog로 터치 디버깅하기

Eclipse를 켠 후 잠시 기다려줘야한다. Cpp파일을 연동하는데에 시간이 걸리는 모양이다.

갑자기 디버깅부터 하면 이클립스가 놀래서 작업중지가 될 수 있으니

조심스럽게 Ctrl+B를 눌러서 빌드한 후

본인의 안드로이드 휴대폰을 연결한 후

Package Explorer -> 프로젝트 우 클릭 -> Run As -> Android Application을 선택해

직접 터치해본다.

 

하단의 LogCat에 여러가지 Log가 찍히는데

Saved Filters에서 본인이 프로젝트를 생성할 때 사용했던 패키지명을 선택하면

cocos2d-x에 관한 Log만 받아볼 수 있다.

또한 상단의 검색창에 "Point : "를 검색해

우리가 출력한 좌표 Log값을 확인할 수 있다.

 

 

 

 

다음 게시물에서 계속...

 

Posted by gharlic