인디 게임 강좌

전체보기

모바일 상단 메뉴

본문 페이지

[코코스2D] [Box2D] #16 - 힘 주기

아이콘 내폰젤무거워
조회: 3084
2017-07-16 10:08:07

Document Version : V1.3 - 2017.07.16 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에서는 이렇게 충격을 주기 위한 메서드를 다음과 같이 제공합니다. 이런 메서드들을 이용하면 앵그리버드 같은 게임이나 당구 게임, 볼링 게임 등을 마우스 조인트를 이용하는 것보다 정밀하게 만들 수 있습니다.

메서드
 inline void b2Body::ApplyForce(const b2Vec2& force, const b2Vec2& point);

설명
 force 만큼의 힘으로 point 에 힘을 가한다.
 주어진 단위 시간인 1초 동안 force 만큼 힘을 가한다.


메서드
 inline void b2Body::ApplyForceToCenter(const b2Vec2& force);

설명
 force 만큼의 힘을 정중앙에 가한다.


메서드
 inline void b2Body::ApplyTorque(float32 torque);

설명
 파라미터로 들어온 값만큼의 회전력을 준다.
 주어진 단위 시간인 1초 동안 파라미터로 들어온 회전력을 가한다.


메서드
 inline void b2Body::ApplyLinearImpulse(const b2Vec2& impulse, const b2Vec2& point);

설명
 파라미터로 들어온 값만큼의 회전력을 주는데 주어진 단위 시간인 1초 동안
 파라미터로 들어온 회전력을 가하는 것이 아니고 한번에 회전력을 가한다.



이번 예제 프로젝트는 앵그리버드처럼 하나의 바디를 던져 쌓아져 있는 바디를 무너뜨리는 예제를 만들어 보도록 하겠습니다. 화면도 앵그리버드처럼 좀 더 넓게 구성해서 오른쪽 안 보이는 공간에 무너뜨릴 바디들을 쌓아 놓도록 할 것입니다. 마우스 조인트를 이용해서도 바디를 던질 수 있지만 앵그리버드처럼 사용자가 힘을 임의로 조절할 수 있도록 하기 위해서는 이번 절에서 배운 메서드를 이용해서 바디에 힘을 가해야 합니다.


커맨드창을 열어 원하는 디렉터리로 이동한 후에, 다음과 같이 cocos 명령어를 이용하여 새로운 프로젝트를 생성합니다.


c:> cocos new Box2dEx15 -p com.study.box15 -l cpp  ↵


Box2dEx04의 모든 코드를 방금 만든 Box2dEx15에 적용시킵니다.

Box2dEx04의 Classes 폴더의 다음 파일들을 Box2dEx15의 Classes 폴더에 덮어 쓰면 됩니다.


■ HelloWorldScene.h

■ HelloWorldScene.cpp

■ GLES-Render.h

■ GLES-Render.cpp



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

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

아래의 파일을 찾아 리소스 폴더에 추가합니다.


■  blocks.png


Box2dEx15는 Box2dEx04 - 마우스 조인트까지 적용된 상태에서 시작합니다.




헤더 부분에는 힘을 주는 것을 처리하기 위한 변수를 추가하고, 기존의 마우스 조인트를 위한 변수를 제거합니다. 터치 콜백함수 선언도 아래 코드와 같이 move 는 사용하지 않을 것이므로 begin과 end만 남겨둡니다.

[ 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);

    void setBox2dWorld();

    ~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);


    b2Vec2 startPoint;

    b2Vec2 endPoint;

    b2Body* myBall;

    bool bBallTouch;


    b2Body* addNewSprite(Vec2 point, Size size, b2BodyType bodytype,

                                          const char* spriteName, int type);

    b2Body* getBodyAtTab(Point p);

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

    void onTouchEnded(Touch* touch, Event* event);

};


#endif // __HELLOWORLD_SCENE_H__





다음은 힘 주기 프로젝트에서 Box2dEx04와 달라진 코드 부분입니다.

[ HelloWorldScene.cpp  박스2D 힘 주기 ]

#include "HelloWorldScene.h"


SceneHelloWorld::createScene()

{

     생략 : Box2dEx04의 코드와 같음 

}


bool HelloWorld::init()

{

     생략 : Box2dEx04의 코드와 같음 

}


bool HelloWorld::createBox2dWorld(bool debug)

{

     생략 : Box2dEx04의 코드와 같음 

}


void HelloWorld::setBox2dWorld()

{    

    Sprite* bg1 = Sprite::create("background1.png");

    bg1->setPosition(Vec2(00));

    bg1->setAnchorPoint(Vec2(00));

    this->addChild(bg1);


    myBall = this->addNewSprite(Vec2(2550), Size(5050), b2_dynamicBody"test"0);

    Sprite* myBallSprite = (Sprite *)myBall->GetUserData();

    Rect myBoundary = Rect(00winSize.width * 2winSize.height);

    // 손쉬운 화면 스크롤링 기법인 Follow 액션을 사용해 넓은 화면을 이동시킨다.

    // 바디를 던지면 해당 바디에 카메라가 붙어 있으므로 자동으로 화면이 스크롤된다.

    this->runAction(Follow::create(myBallSprite, myBoundary));


    // 오른쪽 구석에 쌓아 놓을 바디들의 벡터값을 구성하고 스프라이트를 추가한다.

    float start = winSize.width * 2 - 130;


    struct BLOCK {

        Vec2 point;

        Size  size;

    };


    int const numBlocks = 6;

    struct BLOCK blocks[numBlocks] =

    {

        Vec2(start, 50), Size(10100) },

        Vec2(start + 5050), Size(10100) },

        Vec2(start + 25100 + 5), Size(20010) },

        Vec2(start, 120 + 50), Size(10100) },

        Vec2(start + 50120 + 50), Size(10100) },

        Vec2(start + 25220 + 5), Size(20010) }

    };


    for (int i = 0; i<numBlocks; i++)

    {

        this->addNewSprite(blocks[i].point, blocks[i].sizeb2_dynamicBody"test"0);

    }

}


HelloWorld::~HelloWorld()

{

     생략 : Box2dEx04의 코드와 같음 

}


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

{

     생략 : Box2dEx04의 코드와 같음 

}


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

{

     생략 : Box2dEx04의 코드와 같음 

}


void HelloWorld::onEnter()

{

    Scene::onEnter();


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

    auto listener = EventListenerTouchOneByOne::create();

    listener->setSwallowTouches(true);

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

    listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEndedthis);


    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

}


void HelloWorld::onExit()

{

     생략 : Box2dEx04의 코드와 같음 

}


void HelloWorld::tick(float dt)

{

     생략 : Box2dEx04의 코드와 같음 

}


b2BodyHelloWorld::addNewSprite(Vec2 point, Size size, b2BodyType bodytype,

                             const char* spriteName, int type)

{

     생략 : Box2dEx04의 코드와 같음 

}


b2BodyHelloWorld::getBodyAtTab(Vec2 p)

{

     생략 : Box2dEx04의 코드와 같음 

}


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

{

    Vec2 touchPoint = touch->getLocation();

    Vec2 touchPoint2 = Node::convertToNodeSpace(touchPoint);

    //log("nodeSpace..%f", touchPoint2.x);


    b2Body* tBall = this->getBodyAtTab(touchPoint2);


    bBallTouch = false;


    if (tBall == myBall)

    {

        log("touch start..");

        bBallTouch = true;

        startPoint = b2Vec2(touchPoint2.x / PTM_RATIO, touchPoint2.y / PTM_RATIO);

    }


    return true;

}


void HelloWorld::onTouchEnded(Touch* touch, Event* event)

{

    Vec2 touchPoint = touch->getLocation();

    Vec2 touchPoint2 = Node::convertToNodeSpace(touchPoint);


    if (myBall && bBallTouch)

    {

        log("touch end..");

        endPoint = b2Vec2(touchPoint2.x / PTM_RATIO, touchPoint2.y / PTM_RATIO);

        b2Vec2 force = endPoint - startPoint;


        force.x *= 250.0f;

        force.y *= 250.0f;


        myBall->ApplyForceToCenter(force, true);


        bBallTouch = false;

    }

}




힘을 주는 코드는 다음의 한줄입니다.
ApplyForceToCenter는 우리가 축구공을 차는 것처럼 축구공에 외부에서 힘을 주는 것이고, 힘의 전달에 의해 바디가 가속도가 붙으면서 운동(이동)을 하게 됩니다.

myBall->ApplyForceToCenter(force, true);


앞의 강좌 [바디의 종류]에서도 다음과 같이 움직임을 준 코드가 있었습니다.
SetLinearVelocity는 '너의 현재 속도는 몇이야' 하고 지정해 주는 겁니다. SetLinearVelocity는 현실 세계에서는 없는 동작입니다. 갑자기 속도(움직임)가 생기기 때문입니다.

// 오른쪽으로 이동

b->SetLinearVelocity(b2Vec2(1.0f0));



코드를 완성했으면 실행시켜 봅니다.





화면을 크게 만들었기 때문에 오른쪽에 쌓아 놓은 바디들은 처음에는 보이지 않습니다. 조심스럽게 바디를 마우스로 잡아서 힘을 주어 옮겨 보면 다음과 같이 오른쪽에 쌓아 놓은 바디들을 볼 수 있습니다.
배경그림이 있으면 디버그 모드를 확인할 수 없으므로 오른쪽 부분에는 배경 그림을 넣지 않아서 검은색 바탕입니다.




이제 처음의 위치에서 바디를 마우스를 이용해서 집어 던지듯이 힘을 주면 앵그리버드의 장면처럼 오른쪽 구석에 쌓아 놓은 바디들에 부딪쳐서 무너뜨리는 모습을 볼 수 있습니다.




자연스럽게 던져지고 무너지는 모습을 구현하기 위한 적당한 질량과 힘을 찾는 것은 각자의 몫입니다.
마우스 조인트 부분을 빼고 힘을 주는 부분만 구현하면 훨씬 짧은 코드가 되었을 겁니다.






Lv28 내폰젤무거워

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징