인디 게임 강좌

전체보기

모바일 상단 메뉴

본문 페이지

[코코스2D] [Box2D] #21 - One Way Platform 간단 구현

아이콘 내폰젤무거워
댓글: 1 개
조회: 4221
2017-07-30 21:12:20

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

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


개발환경 : 

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


One Way Platform

우리가 많은 2D 게임에서 볼 수 있는 기능 중에 다음과 같은 기능이 있습니다.

캐릭터가 platform 아래에서 점프를 할 수 있고 이 platform 을 통과 할 수도 있습니다. 그리고 캐릭터는 그 platform 위에 사뿐히 착지하게 됩니다. 이 말은 여태까지 살펴본 물리 엔진의 관점에서 다시 말하면 platform은 물리 객체인데 한쪽은 충돌이 일어나고 한쪽은 충돌이 안 일어난다는 말입니다.

  • 사각형( platform ) 위에서 아래로 충돌 : 충돌이 일어남
  • 사각형( platform ) 아래서 위로 충돌 : 충돌이 일어나지 않음

박스2D는 아니지만 칩멍크 사이트에 보면 이 말을 설명하는 그림이 다음과 같이 있습니다.



무슨 기능인지 이해가 가시나요?

이런 기능을 “one way platform” 이라고 부릅니다. 2D 게임에서 자주 요구되어지는 기능 중 하나입니다.

one way platforms
one sided platforms
one way walls
다 같은 말입니다.

이런 기능이 게임에서 어떻게 사용되는지 다음 동영상을 보면 이해가 더 잘 갈 겁니다.





박스2D에서 이런 기능을 구현하기 위한 방법은 조금 복잡합니다.
라이브러리를 고치는 방법을 많이 사용하곤 하는데요...
다음의 링크에서 내용을 보실 수 있습니다.

영어라서 곤란하셨나요? 저희 카페에 Jungssi 님이 설명해 놓은 글이 있습니다.



그러나 저는 간단한 약식 방법을 사용해 보도록 하겠습니다.
일단 이 현상이 일어날 때의 조건을 보면 다음과 같이 두 가지입니다.
  • 두 바디가 충돌한다.
  • 하나는 위에 있고 하나는 아래 쪽에 있다.
이 조건을 이용하여 간단하게 구현을 하면 다음과 같은 코드로 구현이 가능합니다.
A가 캐릭터이고 B가 플랫폼일 때, 리스너에서 충돌을 감지했을 때 A가 B보다 아래에 있다면 충돌을
무시하면 됩니다.


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


c:> cocos new Box2dEx20 -p com.study.box20 -l cpp  ↵


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

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


■ HelloWorldScene.h

■ HelloWorldScene.cpp

■ GLES-Render.h

■ GLES-Render.cpp



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

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

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


■  blocks.png


Box2dEx20은 Box2dEx19 - Click Velocity 까지 적용된 상태에서 시작합니다.




헤더 부분에는 터치를 처리할 메서드의 선언을 추가합니다.

[ HelloWorldScene.h  박스2D One Way Platform ]

#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 b2ContactListener

{

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


    b2Body* myBall;

    float  playerVelocity;

    bool   playerIsFlying;


    void addNewSpriteAtPosition(Vec2 location);

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

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


    // 19.Box2dEx19 - ClickVelocity 에 추가

    void BeginContact(b2Contact* contact);

    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);

    void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);

    void EndContact(b2Contact* contact);

};


#endif // __HELLOWORLD_SCENE_H__



다음은 One Way Platform 프로젝트에서 Box2dEx19와 달라진 코드 부분입니다.
충돌 리스너를 등록하고 충돌을 처리하는 메서드를 정의합니다. 이 때 높이에 따라 충돌인지 아닌지를 결정하는 코드를 추가해 주면 됩니다.

[ HelloWorldScene.cpp  박스2D One Way Platform ]

#include "HelloWorldScene.h"


SceneHelloWorld::createScene()

{

     생략 : Box2dEx19의 코드와 같음 

}


bool HelloWorld::init()

{

    if ( !Scene::init() )

    {

        return false;

    }

    

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


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

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


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

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


    // 월드 생성

    if (this->createBox2dWorld(true))

    {

        //월드에 충돌함수 클래스를 리스너에 추가함

        _world->SetContactListener((b2ContactListener *)this);


        this->setBox2dWorld();

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

    }


    return true;

}


bool HelloWorld::createBox2dWorld(bool debug)

{

     생략 : Box2dEx19의 코드와 같음 

}


void HelloWorld::setBox2dWorld()

{

    playerIsFlying = false;


    // staticBody 스프라이트를 추가한다.

    Sprite* pSprite1 = Sprite::createWithTexture(textureRect(006464));

    pSprite1->setPosition(Vec2(winSize.width / 2winSize.height / 2));

    this->addChild(pSprite1);


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

    b2BodyDef bodyDef1;

    bodyDef1.type = b2_staticBody;

    bodyDef1.position.Set(winSize.width / 2 / PTM_RATIO,

                                        winSize.height / 2 / PTM_RATIO);

    bodyDef1.userData = pSprite1;


    b2Body* body1 = _world->CreateBody(&bodyDef1);


    // 바디에 적용할 물리 속성용 바디의 모양을 만든다.

    b2PolygonShape staticBox;

    // 바디의 크기 지정 - 상자의 크기에서 가운데 위치를 지정한다.

    staticBox.SetAsBox((pSprite1->getContentSize().width / 2) / PTM_RATIO,

                                     (pSprite1->getContentSize().height / 2) / PTM_RATIO);


    b2FixtureDef fixtureDef1;

    fixtureDef1.shape = &staticBox;

    fixtureDef1.density = 1.0f;


    body1->CreateFixture(&fixtureDef1);

    //-------------------------------------------------------------------------------


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

    addNewSpriteAtPosition(Vec2(240100));

}


HelloWorld::~HelloWorld()

{

     생략 : Box2dEx19의 코드와 같음 

}


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

{

     생략 : Box2dEx19의 코드와 같음 

}


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

{

     생략 : Box2dEx19의 코드와 같음 

}


void HelloWorld::onEnter()

{

     생략 : Box2dEx19의 코드와 같음 

}


void HelloWorld::onExit()

{

     생략 : Box2dEx19의 코드와 같음 

}


void HelloWorld::tick(float dt)

{

     생략 : Box2dEx19의 코드와 같음 

}


void HelloWorld::addNewSpriteAtPosition(Vec2 location)

{

     생략 : Box2dEx19의 코드와 같음 

}


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

{

     생략 : Box2dEx19의 코드와 같음 

}


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

{

     생략 : Box2dEx19의 코드와 같음 

}


void HelloWorld::BeginContact(b2Contact* contact)

{

}


void HelloWorld::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)

{

    b2Fixture* fixtureA = contact->GetFixtureA();

    b2Fixture* fixtureB = contact->GetFixtureB();


    b2Body* bodyA = fixtureA->GetBody();

    b2Body* bodyB = fixtureB->GetBody();


    if (bodyA->GetUserData() == nullptr || bodyB->GetUserData() == nullptr) {

        return;

    }


    Sprite* spr1 = (Sprite *)bodyA->GetUserData();

    Sprite* spr2 = (Sprite *)bodyB->GetUserData();


    float pos1 = spr1->getPosition().y + (spr1->getContentSize().height / 2);

    //float pos2 = spr2->getPosition().y - (spr2->getContentSize().height / 2);

    float pos2 = spr2->getPosition().y;


    if (pos2 < pos1)

    {

        contact->SetEnabled(false);

    }

}


void HelloWorld::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)

{

}


void HelloWorld::EndContact(b2Contact* contact)

{

    contact->SetEnabled(true);

}



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





마우스로 클릭을 유지하고 있으면 원이 네모를 뚫고 위로 올라가게 됩니다. 올라갈 때는 충돌이 일어나지 않았지만 내려올때는 충돌이 일어나기 때문에 내려오지 않고 있음을 볼 수 있습니다.




간단하게 One Way Platform 이 구현되었습니다.











Lv28 내폰젤무거워

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징