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"