COCOS2D-X 블록파괴 알고리즘 및 UI

마스터피스·2024년 3월 28일
0

COCOS2D-X

목록 보기
9/11
post-thumbnail
  1. 블록 파괴 알고리즘 및 상태 애니메이션 + UI 미완단계

1) Environment

해더파일)

#ifndef __ENVIRONMENT_H__
#define __ENVIRONMENT_H__

#include "stdafx.h"

enum class GameState {
	PLAYING, PAUSED, WIN, LOSE, BLOCK_MOVING
};


#define BLOCK_HORIZONTAL 7
#define BLOCK_VERTICAL 9
//블록 위치
#define BLOCK_OFFSET Vec2(720/2, 1280/2)
//블록 가로길이
#define BLOCK_WIDTH 80
//블록 세로길이
#define BLOCK_HEIGHT 80

#define BLOCK_VAR 4

#define SWAP(TYPE, A, B){TYPE t = A; A = B; B= t;}

#endif // !__SCEN_INGAME_H__

2) LayerIngameUI

해더파일

#ifndef __LAYER_INGAME_UI_H__
#define __LAYER_INGAME_UI_H__

#include "stdafx.h"
#include "Environment.h"

class LayerIngameUI : public Node{
private:
	long long score;
	char scoreString[128];

public:

	// 화면에 스코어 출력
	Label* lbScore;
	// 일시정지버튼
	Button* btnPause;
	// pause 버튼을 눌렀을때 나오는 배경
	Scale9Sprite* pausePanel;
	// 일시정지 해제
	Button* btnResume;
	// 재시작
	Button* btnRestart;
	// 메인화면으로 가기
	Button* btnHome;
	// 화면 뒷배경 흐릿하게 만들기
	DrawNode* dnCurtain;



	static LayerIngameUI* create();
	virtual bool init() override;

	void setScore(long long score);
	long long getScore();


	void showPausePanel();
	void hidePausePanel();

};

#endif

소스파일)

#include "stdafx.h"
#include "LayerIngameUI.h"

LayerIngameUI* LayerIngameUI::create(){
	auto ret = new LayerIngameUI();
	if (ret && ret->init()) ret->autorelease();
	else CC_SAFE_DELETE(ret);
	return ret;
}

bool LayerIngameUI::init(){
	if (!Node::init()) return false;

	addChild(lbScore = Label::createWithTTF("asdf", "fonts/SDSamliphopangcheTTFBasic.ttf", 48.0f));
	lbScore->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
	lbScore->setPosition(Vec2(30, 1280 - 70));
	

	addChild(btnPause = Button::create("res/btn_pause_normal.png", "res/btn_pause_pressed.png", "res/btn_pause_disabled.png"));
	btnPause->setPosition(Vec2(780 - 70, 1280 - 70));

	const Size PANEL_SIZE(600, 400);
	const float SPACING = 170;
	const float BUTTON_BOTTOM_SAPCING = 100;


	addChild(dnCurtain = DrawNode::create());
	dnCurtain->drawSolidRect(Vec2::ZERO, Vec2(720, 1280), Color4F(0,0,0,0.8));



	//Scale9Sprite 는 가로나 세로로 늘어나도 이미지 그래픽이 깨지지 않게 해준다. 배율을 늘린다.
	addChild(pausePanel = Scale9Sprite::create("res/panel.png"));
	pausePanel->setPosition(Vec2(720 / 2, 1280 / 2));
	// 이미지를 9등분 해서 크기에 맞게 늘려준다.
	pausePanel->setScale9Enabled(true);
	// 가로크기와 세로크기를 지정해서 편리하게 사용가능함.
	pausePanel->setContentSize(Size(600, 400));

	pausePanel->addChild(btnRestart = Button::create("res/btn_restart_normal.png", "res/btn_restart_pressed.png", "res/btn_restart_disabled.png"));
	pausePanel->addChild(btnHome = Button::create("res/btn_home_normal.png", "res/btn_home_pressed.png", "res/btn_home_disabled.png"));
	pausePanel->addChild(btnResume = Button::create("res/btn_play_normal.png", "res/btn_play_pressed.png", "res/btn_play_disabled.png"));

	btnResume->setPosition(Vec2(PANEL_SIZE.width / 2, BUTTON_BOTTOM_SAPCING));
	btnHome->setPosition(Vec2(PANEL_SIZE.width / 2 - SPACING, BUTTON_BOTTOM_SAPCING));
	btnRestart->setPosition(Vec2(PANEL_SIZE.width / 2 + SPACING, BUTTON_BOTTOM_SAPCING));


	setScore(0);
	hidePausePanel();

	return true;
}

void LayerIngameUI::setScore(long long score){
	sprintf(scoreString, "Score : %ld", score);
	lbScore->setString(scoreString);
}

long long LayerIngameUI::getScore(){
	return score;
}

void LayerIngameUI::showPausePanel(){
	pausePanel->setVisible(true);
	dnCurtain->setVisible(true);
}

void LayerIngameUI::hidePausePanel(){
	pausePanel->setVisible(false);
	dnCurtain->setVisible(false);

}

3)SceneIngame

해더파일)

#ifndef __SCENE_INGAME_H__
#define __SCENE_INGAME_H__

#include "stdafx.h"
#include "Environment.h"
#include "LayerIngameUI.h"

class SceneIngame : public Scene {
private:
	GameState state;
	// blockData는 0 값일 경우 비어있는 블록, 0이 아닐 양수값일 경우 블록
	int blockData[BLOCK_VERTICAL][BLOCK_HORIZONTAL];
	
	// blockSprite는 nullptr일 경우 비어있음,
	Sprite* blockSprite[BLOCK_VERTICAL][BLOCK_HORIZONTAL];

	// 유니크 스택을 위한 자료구조
	Vec2 judgeStack[128];
	// 스택에 있는 자료의 수
	int judgeStackCount = 0;
	// 0 이라면 스택에 자료가 없음, 그게 아니라면 자료가 있음.
	int judgeData[BLOCK_VERTICAL][BLOCK_HORIZONTAL];

	void createBlock(int x, int y, int type);
	int getBlockData(int x, int y);
	void setBlockData(int x, int y, int type);
	Sprite* getBlockSprite(int x, int y);
	void setBlockSprite(int x, int y, Sprite* s);
	void destroyBlock(int x, int y);

	//게임의 현재 좌표를 블록의 좌표로만들어주는것
	Vec2 convertGamecoordToBlockCoord(Vec2 gameCoord);
	//블록의 좌표를 게임의 좌표로 바꿔주는 것.
	Vec2 convertBlockcoordToGameCoord(Vec2 blockCoord);

	//아래에서부터 찾아 올라가면서 비어있는 블록을 찾고
	// -1이 리턴에 되면 비어있는 블록이 없다는 뜻이다.
	int findEmptyBlockYIndex(int x,int y);

	//y위치부터 찾아 올라가면서 비어있지 않은 블록을 찾고
	//-1이 리턴이 되면 비어있지 않은 블록이 없다는 뜻이다.
	int findFilledBlockYIndex(int x, int y);

	//블록을 떨어뜨리는 함수
	void dropBlocks(int x);

	void stackPush(Vec2 value);
	Vec2 stackPop();
	void stackEmpty();
	bool stackFind(Vec2 value);
	void judgeMatch(int x, int y);

	LayerIngameUI* ui;


public:
	static SceneIngame* create();
	virtual bool init() override;
	//override 로 알아서 몸체만 만들면 불러준다
	virtual void onEnter() override;

	//UI 만드는 함수
	void initUI();
	//게임을 초기화 하는 함수
	void initGame();
	//게임의 UI 삭제
	void destroyUI();
	// 게임의 스프라이트를 없애는것
	void destroyGame();

	void alignBlockSprite();

	bool onTouchBegan(Touch* t, Event* e);
	void onTouchMoved(Touch* t, Event* e);
	void onTouchEnded(Touch* t, Event* e);

	//게임시작
	void startGame();
	//일시정지
	void pauseGame();
	//이겼을때
	void winGame();

	void loseGame();
};

#endif // !__SCEN_INGAME_H__

소스파일)

#include "stdafx.h"
#include "SceneIngame.h"



void SceneIngame::createBlock(int x, int y, int type) {
    auto cache = Director::getInstance()->getTextureCache();
    //Rect 는 이미지를 자르는 기준은 좌상단이 기준이다.
    // 게임엔진만 좌하단을 기준으로 잡는다.
    auto spr = Sprite::createWithTexture(
        cache->getTextureForKey("res/match3_tiles_px.png"), 
        Rect(0 + (type*40), 0, 40, 40));
    spr->setScale(2);
    addChild(spr);
    setBlockData(x, y, type);
    setBlockSprite(x, y, spr);

}

int SceneIngame::getBlockData(int x, int y) {
    return blockData[y][x];
}

void SceneIngame::setBlockData(int x, int y, int type) {
    blockData[y][x] = type;

}

Sprite* SceneIngame::getBlockSprite(int x, int y) {
    return blockSprite[y][x];
}

void SceneIngame::setBlockSprite(int x, int y, Sprite* s) {
    blockSprite[y][x] = s;
}

void SceneIngame::destroyBlock(int x, int y) {
    if(blockData[y][x] != 0) {
        state = GameState::BLOCK_MOVING;

        blockSprite[y][x]->runAction(Sequence::create(
            FadeOut::create(0.125f),
            FadeIn::create(0.125f),
            FadeOut::create(0.125f),
            FadeIn::create(0.125f),
            FadeOut::create(0.125f),
            Spawn::create(ScaleTo::create(0.125f, 0.0), FadeOut::create(0.125f), nullptr),
            RemoveSelf::create(),
            nullptr));

        blockSprite[y][x] = nullptr;
        blockData[y][x] = 0;

        this->runAction(Sequence::create(
            DelayTime::create(0.625f),
            CallFunc::create([=]() {dropBlocks(x);}),
            nullptr
            ));
    }
}

//좌표변환 중요함.
Vec2 SceneIngame::convertGamecoordToBlockCoord(Vec2 gameCoord){
    Vec2 blockOrigin = BLOCK_OFFSET 
        - Vec2((BLOCK_HORIZONTAL * BLOCK_WIDTH)/2, (BLOCK_VERTICAL*BLOCK_HEIGHT)/2)
        + Vec2(BLOCK_WIDTH, BLOCK_HEIGHT)/2;
    Vec2 delta = gameCoord - blockOrigin;
    //인티저로 형변환 하면서 반올림하는 게 0.5를 더하는 것이다.
    Vec2 pos = Vec2((int)(delta.x / BLOCK_WIDTH + 0.5), (int)(delta.y / BLOCK_HEIGHT + 0.5));

    return pos;
}

Vec2 SceneIngame::convertBlockcoordToGameCoord(Vec2 blockCoord){
    Vec2 blockOrigin = BLOCK_OFFSET 
        - Vec2((BLOCK_HORIZONTAL * BLOCK_WIDTH) / 2, (BLOCK_VERTICAL * BLOCK_HEIGHT) / 2) 
        + Vec2(BLOCK_WIDTH, BLOCK_HEIGHT) / 2;

    return blockOrigin + Vec2(BLOCK_WIDTH * blockCoord.x, BLOCK_HEIGHT * blockCoord.y);
    
}


int SceneIngame::findEmptyBlockYIndex(int x, int y){
    for (int i = y; i < BLOCK_VERTICAL;i++) {
        if (getBlockData(x, i) == 0) return i;
    }
    return -1;
}

int SceneIngame::findFilledBlockYIndex(int x, int y){
    for (int i = y;i < BLOCK_VERTICAL; i++) {
        if (getBlockData(x, i) != 0) return i;
    }
    return -1;
}

void SceneIngame::dropBlocks(int x){
    bool isDrop = false;
    for (int i = 0; i < BLOCK_VERTICAL; i++) {

        int empty_y = findEmptyBlockYIndex(x, i);
        if (empty_y == -1) continue;
        int filled_y = findFilledBlockYIndex(x, empty_y + 1);
        if (filled_y == -1) {
            createBlock(x, empty_y, rand() % BLOCK_VAR + 1);
            blockSprite[empty_y][x]->setPosition(convertBlockcoordToGameCoord(Vec2(x,BLOCK_VERTICAL + 1)));
            blockSprite[empty_y][x]->runAction(MoveTo::create(0.125f, convertBlockcoordToGameCoord(Vec2(x, empty_y))));
            continue;
        }
        {
            int a = getBlockData(x, empty_y);
            int b = getBlockData(x, filled_y);
            SWAP(int, a, b);
            setBlockData(x, empty_y, a);
            setBlockData(x, filled_y, b);
        } {
            Sprite* a = getBlockSprite(x, empty_y);
            Sprite* b = getBlockSprite(x, filled_y);
            SWAP(Sprite*, a, b);
            setBlockSprite(x, empty_y, a);
            setBlockSprite(x, filled_y, b);

            a->stopAllActions();
            a->runAction(MoveTo::create(0.125f, convertBlockcoordToGameCoord(Vec2(x, empty_y))));

        }
        isDrop = true;

        

    }
    if (isDrop) {
        for (int i = 0; i < BLOCK_VERTICAL; i++) {
            judgeMatch(x, i);
        }
    }
    else {
        state = GameState::PLAYING;
    }

    //alignBlockSprite();
    
}

void SceneIngame::stackPush(Vec2 value){
    if (judgeData[(int)value.y][(int)value.x] != 0) return;
    judgeStack[judgeStackCount++] = value;
    judgeData[(int)value.y][ (int)value.x ] = 1;

}

Vec2 SceneIngame::stackPop(){
    auto ret = judgeStack[--judgeStackCount];
    judgeData[(int)ret.y][(int)ret.x] = 0;
    return ret;
}

void SceneIngame::stackEmpty(){
    judgeStackCount = 0;
    for (int i = 0; i < BLOCK_HORIZONTAL; i++) {
        for (int k = 0; k < BLOCK_VERTICAL; k++) {
            judgeData[k][i] = 0;
        }
    }

}

bool SceneIngame::stackFind(Vec2 value){
    return judgeData[(int)value.y][(int)value.x] == 1;

}

void SceneIngame::judgeMatch(int x, int y){
    int blockData = getBlockData(x, y);

    if (blockData == 0) return;

    stackPush(Vec2(x, y));
    int push_cnt = 0;
    for (int i = 0; i < 4; i++) {
        int next_x = x;
        int next_y = y;
        int inc_x;
        int inc_y;

        switch (i) {
        case 0: inc_x = 1; inc_y = 0; push_cnt = 0; break;
        case 1: inc_x = -1; inc_y = 0; break;
        case 2: inc_x = 0; inc_y = 1; push_cnt = 0; break;
        case 3: inc_x = 0; inc_y = -1; break;

        }
        while (true) {
            next_x += inc_x;
            next_y += inc_y;
            if (next_x < 0 || next_x >= BLOCK_HORIZONTAL) break;
            if (next_y < 0 || next_y >= BLOCK_VERTICAL) break;
            if (getBlockData(next_x, next_y) == blockData) {
                stackPush(Vec2(next_x, next_y));
                push_cnt++;
            }
            else break;
        }

        if (i % 2 == 0) continue;
        if (push_cnt < 2) {
            for (int k = 0; k < push_cnt;k++) {
                stackPop();
            }
        }
    }
    if (judgeStackCount > 1) {
        while (judgeStackCount > 0) {
            Vec2 p = stackPop();
            destroyBlock(p.x, p.y);
        }
    }
    else {
        state = GameState::PLAYING;
    }
    stackEmpty();
}




SceneIngame* SceneIngame::create() {
    auto ret = new SceneIngame();
    if (ret && ret->init()) ret->autorelease();
    else CC_SAFE_DELETE(ret);
    return ret;
}


bool SceneIngame::init() {
    if (!Scene::init()) return false;

    srand(time(0));

    //Director 은 전체 관리자
    //이미지를 이미지 관리자에서 빼와서 사용 가능
    // 자동으로 이미지를 해주는걸 수동으로 가져와 자르려 한다.
    Director::getInstance()->getTextureCache()->addImage("res/match3_tiles_px.png");

    auto touch = EventListenerTouchOneByOne::create();
    touch->onTouchBegan = std::bind(&SceneIngame::onTouchBegan, this, std::placeholders::_1, std::placeholders::_2);
    touch->onTouchMoved = std::bind(&SceneIngame::onTouchMoved, this, std::placeholders::_1, std::placeholders::_2);
    touch->onTouchEnded = std::bind(&SceneIngame::onTouchEnded, this, std::placeholders::_1, std::placeholders::_2);
    touch->onTouchCancelled = touch->onTouchEnded;

    getEventDispatcher()->addEventListenerWithSceneGraphPriority(touch, this);
    return true;
}


void SceneIngame::onEnter() {
    Scene::onEnter();

    
    this->initUI();
    this->initGame();
    this->startGame();

}




void SceneIngame::initUI() {
    addChild(ui = LayerIngameUI::create());
    ui->setLocalZOrder(1);

    ui->btnPause->addClickEventListener([=](Ref* r) {
        if (state == GameState::PLAYING) {
            ui->showPausePanel();
            state = GameState::PAUSED;
        }
       
    });

    ui->btnResume->addClickEventListener([=](Ref* r) {
        if (state == GameState::PAUSED) {
            ui->hidePausePanel();
            state = GameState::PLAYING;
        }
     });

    ui->btnRestart->addClickEventListener([=](Ref* r) {
        if (state == GameState::PAUSED) {
            // TODO: 게임 재시작
        }

     });

    ui->btnHome->addClickEventListener([=](Ref* r) {
        if (state == GameState::PAUSED) {
            // TODO : 게임 일시정지
        }

     });
}

void SceneIngame::initGame() {
    for (int i = 0; i < BLOCK_HORIZONTAL; i++) {
        for (int k = 0; k < BLOCK_VERTICAL; k++) {
            createBlock(i, k, rand()%BLOCK_VAR + 1);
        }
    }
    this->alignBlockSprite();
}

void SceneIngame::destroyUI() {
}

void SceneIngame::destroyGame() {
}

void SceneIngame::alignBlockSprite(){
    for (int i = 0; i < BLOCK_HORIZONTAL; i++) {
        for (int k = 0; k < BLOCK_VERTICAL; k++) {
            auto s = getBlockSprite(i, k);
            if (s != nullptr) s->setPosition(convertBlockcoordToGameCoord(Vec2(i, k)));
        }
    }
}


bool SceneIngame::onTouchBegan(Touch* t, Event* e){
    Vec2 p = convertGamecoordToBlockCoord(t->getLocation());

    if (state == GameState::PLAYING) {

        if (p.x >= BLOCK_HORIZONTAL || p.x < 0) return true;
        if (p.y >= BLOCK_VERTICAL || p.y < 0) return true;

        CCLOG("%f, %f", p.x, p.y);
        destroyBlock(p.x, p.y);

    }

    return true;
}


void SceneIngame::onTouchMoved(Touch* t, Event* e){
}


void SceneIngame::onTouchEnded(Touch* t, Event* e){
}

void SceneIngame::startGame() {
}

void SceneIngame::pauseGame() {
}

void SceneIngame::winGame() {
}

void SceneIngame::loseGame() {
}


4)stdafx

해더파일)

#ifndef __STDAFX_H__
#define __STDAFX_H__

#include "cocos2d.h"
#include "ui/CocosGUI.h"

using namespace cocos2d;
using namespace cocos2d::ui;


#endif

소스파일)

#include "stdafx.h"

  1. 미완단계 : restart 와 일시정지 구현 안한것 TODO 리스트 수정해야함.
  2. 블록파괴 알고리즘 오류가 조금씩 있음.
profile
코딩 일지

0개의 댓글