인디 게임 강좌

전체보기

모바일 상단 메뉴

본문 페이지

[코코스2D] [Box2D] #02 - 디버그 모드 사용하기

아이콘 내폰젤무거워
조회: 3335
추천: 2
2017-07-02 16:00:29

Document Version : V1.3 - 2017.07.02 with cocos2d-x 3.15.1

Document Version : V1.2 - 2015.06.08 with cocos2d-x 3.6

Document Version : V1.1 - 2014.03.14 with cocos2d-x 3.0beta2

Document Version : V1.0 - 2013.07.10 with cocos2d-x 2.1.4


제 책인 "시작하세요! Cocos2d-x 3.0 프로그래밍" 내용을  3.15.1 버전에 맞게 수정하여 올리고 있습니다.

이 글은 네이버카페 "Cocos2d-x 사용자 모임"에 동시에 게재되고 있습니다.

 

개발환경 : 

  • Windows7
  • Visual Studio Community 2017
  • Cocos2d-x 3.15.1
  • 사용 프로젝트 : proj.win32


 

박스2D 디버그 모드 사용하기


월드 안의 물체인 바디는 바디데프에 속성으로 지정된 스프라이트를 통해 우리 눈에 보이긴 하지만 실제 크기나 모양은 정확히 알 수 없습니다. 여러분이 유저데이터에 넣은 스프라이트를 통해 그 모양과 크기를 상상해보겠지만유저데이터의 스프라이트는 바디의 실제 크기나 모양과는 전혀 상관이 없기 때문입니다.


다음 그림을 보겠습니다. 실제 스프라이트의 크기는 안쪽의 글자가 써진 색이 있는 부분까지이지만 우리가 바디로 설정한 부분은 그 스프라이트보다 조금 큰 빨간색 부분까지입니다. 그래서 다음과 같이 실제 바디의 크기보다 스프라이트가 작다면 이 두 바디의 충돌처리 때 사용자가 보기에 충돌하지 않았음에도 우리 코드는 충돌했다고 판단하게 됩니다. 또한 반대의 경우에는 충돌할 때까지 스프라이트들이 겹치는 현상이 발생하게 됩니다. 





이런 현상을 줄이기 위해 디버그 모드를 통해 위 그림처럼 바디의 모양과 크기를 보면서 스프라이트와의 크기를 맞춰야 합니다. 또한 디버그 모드에서는 눈에 보이지 않던 물리 현상을 볼 수 있으므로 상상만이 아닌 직접 물리적 현상(힘, 회전)을 눈으로 보면서 프로그램을 좀 더 수월하게 작성할 수 있을 것입니다.


커맨드창을 열어 원하는 디렉터리로 이동한 후에, 다음과 같이 cocos 명령어를 이용하여 새로운 프로젝트를 생성합니다. 그리고 앞 예제에서 한 것처럼 프로젝트를 기본형으로 만듭니다.


c:> cocos new Box2dEx02 -p com.study.box02 -l cpp  ↵



그러고 나서 다음의 디렉터리에서 

{Cocos2d-x가 설치된 디렉터리} / tests / cpp-tests / Classes / Box2DTestBed

아래의 파일을 찾아 클래스 그룹에 추가합니다.

■ GLES-Render.h

■ GLES-Render.cpp



맥 환경이라면 파인더에서 해당 파일을 찾아 프로젝트의 Classes 디렉터리 아래에 복사합니다. 그리고 파인더에서 해당 파일을 선택해 XCode에서 프로젝트의 Classes 그룹 아래로 드래그 앤 드롭합니다.


그러면 다음과 같은 화면이 나타나는데, 다음 그림과 같이 체크하고 [Finish]를 선택하면 됩니다. 이때 Add to targets가 체크되지 않은 경우가 있는데 꼭 체크하도록 합니다.






윈도우 환경이라면 해당 파일을 찾아 프로젝트의 Classes 디렉터리 아래에 복사합니다. 그리고 다음과 같이 프로젝트를 선택하고 마우스 오른쪽 버튼을 클릭해 팝업 메뉴를 띄우고 [추가 → 기존 항목]을 선택해 조금 전에 Classes 디렉터리에 복사해 넣은 파일을 선택해 주면 됩니다.





이제 헤더에서는 다음과 같이 색으로 표시된 부분이 디버그 모드를 위해 추가된 부분이므로 이 부분을 기존 코드에 추가하면 됩니다. Box2dEx01 코드를 참조하면서 나머지 부분도 추가하시면 됩니다.


[ HelloWorldScene.h  박스2D 디버그모드 적용 ]

#ifndef __HELLOWORLD_SCENE_H__

#define __HELLOWORLD_SCENE_H__


#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)

#pragma execution_character_set("utf-8")

#endif


#include "cocos2d.h"

#include "Box2D/Box2D.h"

#include <GLES-Render.h>


#define PTM_RATIO 32


using namespace cocos2d;


class HelloWorld : public cocos2d::Scene

{

public:

    static cocos2d::Scene* createScene();

    virtual bool init();

    CREATE_FUNC(HelloWorld);


    Size winSize;

    Texture2D* texture;

    b2World* _world;


    // For debugging

    GLESDebugDraw* m_debugDraw;

    cocos2d::CustomCommand _customCmd;


    bool createBox2dWorld(bool debug);

    ~HelloWorld();

    virtual void draw(cocos2d::Renderer* renderer, const cocos2d::Mat4& transform,

                                  uint32_t flags) override;

    void onDraw(const cocos2d::Mat4& transform, uint32_t flags);


    void onEnter();

    void onExit();

    void tick(float dt);


    bool onTouchBegan(Touch *touch, Event * event);

    void addNewSpriteAtPosition(Vec2 location);

};



cpp 파일에서는 변경할 사항이 많습니다. Box2dEx01에서 작성한 월드를 만드는 부분은 일반적인 경우에는 월드를 만들 때마다 아무 변경 없이 계속 사용돼야 하므로 이 부분을 createBox2dWorld라는 메서드로 만들어 호출하는 식으로 변경했습니다. 또한 월드를 생성할 때 디버그 모드를 적용할 것인지 아니면 디버그 모드 없이 그냥 생성할 것인지도 파라미터를 통해 정할 수 있게 했습니다.


다음은 cpp의 전체 코드입니다.


[ HelloWorldScene.cpp – 박스2D 디버그모드 적용 ]

#include "HelloWorldScene.h"


USING_NS_CC;


SceneHelloWorld::createScene()

{

    return HelloWorld::create();

}


bool HelloWorld::init()

{

    if ( !Scene::init() )

    {

        return false;

    }

    

    // 스프라이트와 겹쳐지면 디버그 모드를 표현할 수 없다.

//    auto wlayer = LayerColor::create(Color4B(255, 255, 255, 255));

//    this->addChild(wlayer);

    /////////////////////////////


    // 윈도우 크기를 구한다.

    winSize = Director::getInstance()->getWinSize();


    // 이미지의 텍스쳐를 구한다.

    texture = Director::getInstance()->getTextureCache()->addImage("SpinningPeas.png");


    // 월드 생성

    if (this->createBox2dWorld(true))

    {

        this->schedule(schedule_selector(HelloWorld::tick));

    }


    return true;

}


bool HelloWorld::createBox2dWorld(bool debug)

{

    // 월드 생성 시작 ---------------------------------------------------------


    // 중력의 방향을 결정한다.

    b2Vec2 gravity = b2Vec2(0.0f, -30.0f);


    _world = new b2World(gravity);

    _world->SetAllowSleeping(true);

    _world->SetContinuousPhysics(true);


    // 디버그 드로잉 설정

    if (debug) {

        // 적색 : 현재 물리 운동을 하는 것

        // 회색 : 현재 물리 운동량이 없는 것

        m_debugDraw = new GLESDebugDraw(PTM_RATIO);

        _world->SetDebugDraw(m_debugDraw);


        uint32 flags = 0;

        flags += b2Draw::e_shapeBit;

        //flags += b2Draw::e_jointBit;

        //flags += b2Draw::e_aabbBit;

        //flags += b2Draw::e_pairBit;

        //flags += b2Draw::e_centerOfMassBit;

        m_debugDraw->SetFlags(flags);

    }


    b2BodyDef groundBodyDef;

    groundBodyDef.position.Set(00);

    b2Body *groundBody = _world->CreateBody(&groundBodyDef);


    b2EdgeShape groundEdge;

    b2FixtureDef boxShapeDef;

    boxShapeDef.shape = &groundEdge;


    // 아래쪽.

    groundEdge.Set(b2Vec2(00), b2Vec2(winSize.width / PTM_RATIO0));

    groundBody->CreateFixture(&boxShapeDef);

    // 왼쪽

    groundEdge.Set(b2Vec2(00), b2Vec2(0winSize.height / PTM_RATIO));

    groundBody->CreateFixture(&boxShapeDef);

    // 위쪽

    groundEdge.Set(b2Vec2(0winSize.height / PTM_RATIO),

                               b2Vec2(winSize.width / PTM_RATIOwinSize.height / PTM_RATIO));

    groundBody->CreateFixture(&boxShapeDef);

    // 오른쪽

    groundEdge.Set(b2Vec2(winSize.width / PTM_RATIOwinSize.height / PTM_RATIO),

                               b2Vec2(winSize.width / PTM_RATIO0));

    groundBody->CreateFixture(&boxShapeDef);


    // 월드 생성 끝   ---------------------------------------------------------


    return true;

}


HelloWorld::~HelloWorld()

{

    delete _world;

    _world = nullptr;

}


void HelloWorld::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)

{

    Scene::draw(renderer, transform, flags);


    _customCmd.init(_globalZOrder, transform, flags);

    _customCmd.func = CC_CALLBACK_0(HelloWorld::onDrawthis, transform, flags);

    renderer->addCommand(&_customCmd);

}


void HelloWorld::onDraw(const Mat4 &transform, uint32_t flags)

{

    Director* director = Director::getInstance();

    CCASSERT(nullptr != director, "Director is null when seting matrix stack");

    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);


    GL::enableVertexAttribs(cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION);

    _world->DrawDebugData();

    CHECK_GL_ERROR_DEBUG();


    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

}


void HelloWorld::onEnter()

{

    Scene::onEnter();


    // 싱글터치모드로 터치리스너 등록

    auto listener = EventListenerTouchOneByOne::create();

    listener->setSwallowTouches(true);

    listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBeganthis);


    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

}


void HelloWorld::onExit()

{

    Scene::onExit();

}


void HelloWorld::tick(float dt)

{

    int velocityIterations = 8;

    int positionIterations = 3;


    // Step : 물리 세계를 시뮬레이션한다.

    _world->Step(dt, velocityIterations, positionIterations);


    // 만들어진 객체 만큼 루프를 돌리면서 바디에 붙인 스프라이트를 여기서 제어한다.

    for (b2Body *b = _world->GetBodyList(); b; b = b->GetNext())

    {

        if (b->GetUserData() != nullptr) {

            Sprite* spriteData = (Sprite *)b->GetUserData();


            spriteData->setPosition(Vec2(b->GetPosition().x * PTM_RATIO,

                                                                b->GetPosition().y * PTM_RATIO));

            spriteData->setRotation(-1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));

        }

    }

}


bool HelloWorld::onTouchBegan(Touch *touch, Event * event)

{

    auto touchPoint = touch->getLocation();


    // 터치된 지점에 새로운 물리 객체의 바디와 해당 스프라이트를 추가한다.

    addNewSpriteAtPosition(touchPoint);


    return true;

}


void HelloWorld::addNewSpriteAtPosition(Vec2 location)

{

    // 스프라이트를 파라미터로 넘어온 위치에 만든다.

//    Sprite* pSprite = Sprite::createWithTexture(texture, Rect(0, 0, 37, 37));

//    pSprite->setPosition(Vec2(location.x, location.y));

//    this->addChild(pSprite);


    // 바디데프 만들고 속성들을 지정한다.

    b2BodyDef bodyDef;

    bodyDef.type = b2_dynamicBody;

    bodyDef.position.Set(location.x / PTM_RATIO, location.y / PTM_RATIO);


    // 보통은 유저데이터에 스프라이트를 연결하는데

    // 여기서는 아무 데이타를 넣지 않고 디버그 드로잉만 수행한다.

//    bodyDef.userData = pSprite;

    bodyDef.userData = nullptr;


    // 월드에 바디데프의 정보로 바디를 만든다.

    b2Body* body = _world->CreateBody(&bodyDef);


    // 바디에 적용할 물리 속성용 바디의 모양을 만든다. 여기서는 원을 만든다.

    b2CircleShape circle;

    // 바디의 크기 지정 - 원의 경우엔 반지름

    circle.m_radius = 0.5f;


    b2FixtureDef fixtureDef;

    // 모양

    fixtureDef.shape = &circle;

    // 밀도

    fixtureDef.density = 1.0f;

    // 마찰력

    fixtureDef.friction = 0.2f;

    // 반발력

    fixtureDef.restitution = 0.6f;


    body->CreateFixture(&fixtureDef);

}



디버그 모드는 다음 코드에서 지정합니다. init에서 호출된 createBox2dWorld 메서드의 파라미터로 들어온 debug 변수의 값이 true이면 디버그 모드를 시작하고 지정한 값에 의해 화면에 물리 객체의 다양한 디버깅 화면을 그려줍니다. 다음은 물리 객체의 모양만 그리도록 값을 지정한 것입니다.


    // 디버그 드로잉 설정

    if (debug) {

            // 적색 : 현재 물리 운동을 하는 것

            // 회색 : 현재 물리 운동량이 없는 것

            m_debugDraw = new GLESDebugDraw(PTM_RATIO);

            _world->SetDebugDraw(m_debugDraw);


            uint32 flags = 0;

            flags += b2Draw::e_shapeBit;

            //flags += b2Draw::e_jointBit;

            //flags += b2Draw::e_aabbBit;

            //flags += b2Draw::e_pairBit;

            //flags += b2Draw::e_centerOfMassBit;

            m_debugDraw->SetFlags(flags);

    }


위와 같이 설정하면 물리 객체에 대한 실제 그림은 다음 메서드에서 그려집니다.


void HelloWorld::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)

{

    Scene::draw(renderer, transform, flags);


    _customCmd.init(_globalZOrder, transform, flags);

    _customCmd.func = CC_CALLBACK_0(HelloWorld::onDrawthis, transform, flags);

    renderer->addCommand(&_customCmd);

}


void HelloWorld::onDraw(const Mat4 &transform, uint32_t flags)

{

    Director* director = Director::getInstance();

    CCASSERT(nullptr != director, "Director is null when seting matrix stack");

    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);


    GL::enableVertexAttribs(cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION);

    _world->DrawDebugData();

    CHECK_GL_ERROR_DEBUG();


    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

}


이 부분이 예전 버전과 바뀐 부분이라, 기존의 draw 메서드가 안된다고 인터넷에서 많이들 검색하셨을 겁니다. 위 코드대로 사용하시면 됩니다.


이제 주석에 적혀 있듯이 해당 바디에 물리적인 운동량이 남아 있으면 붉은 색으로 바디를 표시하고, 해당 바디에 더 이상의 물리적인 운동량이 없다면 회색으로 바디를 그려줍니다.


이번 예제는 일부러 바디데프의 유저속성에 스프라이트를 넣지 않고 만들었기 때문에 예제를 실행하면 다음과 같이 바디의 모양만 보일 것입니다. 그리고 멈춰선 바디는 운동량이 없기 때문에 회색으로 표시되고, 현재 탄성 반발력에 의해 통통 튕기고 있는 바디는 붉은 색으로 표시됩니다.








※ 원 안에 그어진 선은 회전을 알아보기 위해서 그려진 디버그 모드용 선입니다. 


Lv28 내폰젤무거워

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징