[C++] cocos2d-x를 활용한 드래곤 플라이트 게임 만들기 (3)
2021. 5. 21. 07:57
안녕하세요. 라이트코드(Light Code) 입니다.
이번 글에서는 cocos2d-x 라는 오픈 소스 2D 게임엔진으로 1,2탄에서의 게임을 진짜 게임처럼 다듬어 마무리하는 작업을 하도록 하겠습니다. 1탄에서는 5개의 버튼이 왼쪽 상단에 있었고 그 버튼을 클릭하면 각 버튼에 해당하는 모션에 대한 액션이 실행되었고, 2탄에서는 게임에 생명력을 넣어 직접 방향키로 조작하면서 적을 제거하는 식의 게임을 제작하였습니다. 이번에는 1,2탄의 게임과는 다르게 게임의 완성도를 진짜 게임처럼 구성하여 마무리하는 형태로 마지막 cocos2d-x 드래곤 플라이트 게임을 마무리 짓도록 하겠습니다.
(1,2탄에 관한 소스코드를 이해하지 못하신분들은 [이전단계] 클릭하여 소스코드를 참고하시길 바랍니다)
<설명>
Play/Quit 버튼이 있고 [play] 버튼을 누르게 되면 적들이 나타난다. 이번에는 두 종류의 적이 나타나게 되는데 일반 작은 몸집의 적은 불꽃미사일을 한대만 맞으면 사라지지만 빨간 큰 몸집의 적은 불꽃미사일 5대를 맞아야 화면에서 사라진다. 다만 이번 게임에서는 파란색 동그란 아이템이 제공되게 되는데 이 아이템을 획득할 경우 빨간 큰 몸집의 적도 한방에 사라진다. 목숨은 전게임과는 다르게 난이도가 올라갔기때문에 3개의 목숨이 제공되고 게임 왼쪽 상단에는 목숨의 [Life] 수치를 나타내고 오른쪽 상단에는 게임 내의 스코어를 기록할 수 있는 기록저장수치가 제공된다. 만약 3개의 목숨을 쓸 경우에는 전 게임과 마찬가지로 [Game Over] 이라는 메세지와 함께 처음화면으로 돌아간다. [Quit] 버튼을 누르게 되면 프로그램은 정상적으로 종료되게 된다.
(스코어를 게임 종료 후에도 기록 할 수 있게 오른쪽 상단의 [현재점수/기록점수] 칸의 기록점수에는 영원히 점수기록이 유지된다)
<ProjectOneScene.h>
#ifndef __GAME_SCENE_H__
#define __GAME_SCENE_H__
#define TAG_GAME_LB_PLAYER_LIFE 7
#define TAG_WINDOW_SIZE 50
#define TAG_PLAYER_MOVE 1
#define TAG_PLAYER_END 2
#define TAG_DIE_HP 0
#define TAG_BASIC_MISSILE 1
#define TAG_ITEM_MISSILE 5
#define TAG_SPRITE_PLAYER 1000
#define TAG_LABEL_SCORE 1001
#define TAG_LABEL_HIGHSCORE 1002
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
using namespace CocosDenshion;
enum TAG_GAME {
TAG_GAME_PLAYER_DIE = 0, TAG_GAME_PLAYER_FULL = 3
};
class Enemy:public Sprite {
public: CREATE_FUNC(Enemy);
int type;
int hp;
};
class Player:public Sprite {
public: CREATE_FUNC(Player);
int hp;
};
class GameScene:public cocos2d::Scene {
public: static Scene* createScene();
Vector<Sprite*> missiles, items;
Vector<Enemy*> enemies;
Vector<Player*> player;
virtual bool init();
int gScore, gScoreHigh, gPlayerHP;
bool pCheck; // 플레이어가 죽었는지 체크하는
변수 bool gCheck; // 게임이 끝났고 게임오버를
체크하는 변수 bool isGetItem; // 아이템 적용여부
bool playerLive;
void startGame();
void initBackground();
void initPlayer();
void setGameover();
void gameEnd();
void gameReset();
void stopGameOver();
void checkEnemy();
void checkItem();
void checkPlayer();
void initScore(); // 현재점수,최고점수,플레이어생명 레이블함수
void UpdateLife(); // 플레이어의 생명수정
void addScore(int add); // 점수추가
void update(float delta);
void setAttack(float delta);
void setEnemy(float delta);
void setItem(float delta);
void getItem(float delta);
void resetAttack(Ref* sender);
void resetEnemy(Ref* sender);
void resetItem(Ref* sender);
void resetBoom(Ref *sender);
bool onTouchBegan(Touch* touch, Event* unused_event);
CREATE_FUNC(GameScene);
};
#endif
<ProjectOneScene.cpp>
#include "GameScene.h"
#include "MenuScene.h"
#define TAG_ENEMY_DAMAGE 1
Scene * GameScene::createScene() {
return GameScene::create();
}
bool GameScene::init() {
if (!Scene::init()) {
return false;
}
SimpleAudioEngine::getInstance() -> playBackgroundMusic("Sounds/background.mp3", true);
auto listener = EventListenerTouchOneByOne::create();
listener -> onTouchBegan = CC_CALLBACK_2(GameScene::onTouchBegan, this);
Director::getInstance() -> getEventDispatcher() -> addEventListenerWithSceneGraphPriority(listener, this);
Size winSize = Director::getInstance() -> getVisibleSize();
startGame();
initBackground();
initPlayer();
initScore();
stopGameOver();
return true;
}
bool GameScene::onTouchBegan(Touch * touch, Event * unused_event) // 게임에서의
왼쪽,
오른쪽 터치이벤트 등록 및 플레이어 죽었을 경우 리플레이스로 전화면이동을 지정하는
함수 {
if (gCheck) {
auto scene = TransitionPageTurn::create(3.0, MenuScene::createScene(), false);
Director::getInstance() -> replaceScene(scene);
return true;
} else if (pCheck) {
return true;
}
Label * gameover = (Label *)this -> getChildByTag(TAG_PLAYER_END);
Sprite * dragon = (Sprite *)this -> getChildByTag(gPlayerHP);
if (dragon -> getActionByTag(TAG_PLAYER_MOVE)) {
dragon -> stopActionByTag(TAG_PLAYER_MOVE);
}
Vec2 pos = dragon -> getPosition();
Vec2 location = touch -> getLocation();
float distance = sqrtf((location.x - pos.x) * (location.x - pos.x));
auto action = MoveTo::create(distance / 400, Vec2(location.x, pos.y));
action -> setTag(TAG_PLAYER_MOVE);
dragon -> runAction(action);
return true;
}
void GameScene::startGame() { // 게임초기화 함수
if (Director::getInstance() -> isPaused() == false) {
missiles.clear();
enemies.clear();
items.clear();
} else {
((Sprite *)getChildByTag(gPlayerHP)) -> setTag(TAG_GAME_PLAYER_FULL);
gPlayerHP = TAG_GAME_PLAYER_FULL;
}
pCheck = false;
gCheck = false;
isGetItem = false;
playerLive = true;
gScore = 0; // 게임의 시작스코어
gScoreHigh = UserDefault::getInstance() -> getIntegerForKey("HIGH_SCORE", 0);
}
void GameScene::initBackground() { // 게임 스크롤 단일배경설정 함수
auto gLayer = Layer::create();
this -> addChild(gLayer);
auto background = Sprite::create("images/GameImage.png");
background -> setAnchorPoint(Point::ZERO);
gLayer -> addChild(background);
auto action_0 = MoveBy::create(15.0, Point(0, -900));
auto action_1 = Place::create(Point::ZERO);
auto action_2 = Sequence::create(action_0, action_1, NULL);
auto result = RepeatForever::create(action_2);
gLayer -> runAction(result);
}
void GameScene::initPlayer() // 플레이어 스프라이트 애니메이션 함수
{
SpriteFrameCache::getInstance() -> addSpriteFramesWithFile("dragon.plist");
auto dragon = Sprite::createWithSpriteFrameName("dragon_01.png");
gPlayerHP = TAG_GAME_PLAYER_FULL;
dragon -> setScale(2.0 f);
dragon -> setPosition(Vec2(300, 100));
dragon -> setTag(gPlayerHP);
this -> addChild(dragon, 1);
auto animation = Animation::create();
animation -> setDelayPerUnit(0.1);
for (int i = 0; i < 5; i ++) {
auto frame = SpriteFrameCache::getInstance() -> getSpriteFrameByName(StringUtils::format("dragon_%02d.png
", i + 1));
animation -> addSpriteFrame(frame);
}
auto animate = Animate::create(animation);
dragon -> runAction(RepeatForever::create(animate));
this -> schedule(schedule_selector(GameScene::setItem), 10.0 f);
this -> schedule(schedule_selector(GameScene::setAttack), 0.5 f);
this -> schedule(schedule_selector(GameScene::setEnemy), 0.5 f);
this -> scheduleUpdate();
}
void GameScene::setAttack(float delta) // 플레이어의 발사공격 스프라이트 애니메이션 지정 함수
{
Sprite * dragon = (Sprite *)this -> getChildByTag(gPlayerHP);
Sprite * missile;
if (isGetItem) {
SpriteFrameCache::getInstance() -> addSpriteFramesWithFile("upMissile.plist");
// 여러개의 png파일을 하나로합쳐서 캐시하여 저장
missile = Sprite::createWithSpriteFrameName("upMissile_01.png");
missile -> setPosition(dragon -> getPosition() + Vec2(0, 20));
missile -> setScale(2.0);
missile -> setTag(TAG_ITEM_MISSILE);
this -> addChild(missile, 1);
auto animation = Animation::create();
animation -> setDelayPerUnit(0.1);
for (int i = 0; i < 4; i ++) {
auto frame = SpriteFrameCache::getInstance() -> getSpriteFrameByName(StringUtils::format("upMissile_%02d.p
ng ", i + 1));
animation -> addSpriteFrame(frame);
}
auto animate = Animate::create(animation);
missile -> runAction(RepeatForever::create(animate));
}
else {
SpriteFrameCache::getInstance() -> addSpriteFramesWithFile("missile.plist");
// 여러개의 png파일을 하나로합쳐서 캐시하여 저장
missile = Sprite::createWithSpriteFrameName("missile_01.png");
missile -> setPosition(dragon -> getPosition() + Vec2(0, 20));
missile -> setTag(TAG_BASIC_MISSILE);
this -> addChild(missile, 1);
auto animation = Animation::create();
animation -> setDelayPerUnit(0.1);
for (int i = 0; i < 4; i ++) {
auto frame = SpriteFrameCache::getInstance() -> getSpriteFrameByName(StringUtils::format("missile_%02d.png", i + 1));
animation -> addSpriteFrame(frame);
}
auto animate = Animate::create(animation);
missile -> runAction(RepeatForever::create(animate));
}
missiles.pushBack(missile);
missile -> runAction(Sequence::create(MoveBy::create(2.0 f, Vec2(0, 900)), CallFuncN::create(CC_CALLBACK_1(GameScene::resetAttack, this)), NULL));
}
void GameScene::setEnemy(float delta) { // 적의 스프라이트 애니메이션 지정 함수
float x = rand() % (600 - TAG_WINDOW_SIZE * 2) + TAG_WINDOW_SIZE;
auto enemy = Enemy::create();
float speed; // 적의 하강 속도
if (rand() % 100 < 20) { // 20% 확률로 2단계 적 등장
enemy -> type = 1;
enemy -> hp = 5;
enemy -> setScale(2.5 f);
enemy -> setColor(Color3B::RED);
speed = 8.0 f;
} else { // 80% 확률로 1단계 적 등장
enemy -> type = 2;
enemy -> hp = 1;
enemy -> setScale(2.0 f);
speed = 10.0 f;
}
SpriteFrameCache::getInstance() -> addSpriteFramesWithFile("attack.plist"); // 여러개의
png파일을 하나로합쳐서 캐시하여 저장 enemy - > Sprite::createWithSpriteFrameName("attack_01.png");
enemy -> setPosition(Vec2(x, 930));
this -> addChild(enemy);
enemies.pushBack(enemy);
auto animation = Animation::create();
animation -> setDelayPerUnit(0.3);
for (int i = 0; i < 5; i ++) {
auto frame = SpriteFrameCache::getInstance() -> getSpriteFrameByName(StringUtils::format("attack_%02d.png", i + 1));
animation -> addSpriteFrame(frame);
}
auto animate = Animate::create(animation);
enemy -> runAction(RepeatForever::create(animate));
enemy -> runAction(Sequence::create(MoveTo::create(speed, Vec2(300, -1000)), CallFuncN::create(CC_CALLBACK_1(GameScene::resetEnemy, this)), NULL));
}
void GameScene::setItem(float delta) { // 아이템 생성에 대한 애니메이션 지정 함수
float x = rand() % (600 - TAG_WINDOW_SIZE * 2) + TAG_WINDOW_SIZE;
SpriteFrameCache::getInstance() -> addSpriteFramesWithFile("item.plist"); // 여러개의
png파일을 하나로합쳐서 캐시하여 저장 auto item = Sprite::createWithSpriteFrameName("item_01.png");
item -> setPosition(Vec2(x, 900 + 100));
item -> setScale(1.5);
this -> addChild(item, 1);
auto animation = Animation::create();
animation -> setDelayPerUnit(0.1);
for (int i = 0; i < 3; i ++) {
auto frame = SpriteFrameCache::getInstance() -> getSpriteFrameByName(StringUtils::format("item_%02d.png", i + 1));
animation -> addSpriteFrame(frame);
}
auto animate = Animate::create(animation);
item -> runAction(RepeatForever::create(animate));
items.pushBack(item);
item -> runAction(Sequence::create(MoveBy::create(4.0 f, Vec2(0, -1000)), CallFuncN::create(CC_CALLBACK_1(GameScene::resetItem, this)), NULL));
}
void GameScene::checkItem() {
auto removeSpr = Sprite::create();
Sprite * player = (Sprite *)this -> getChildByTag(gPlayerHP);
Rect rectPlayer = Rect(player -> getPositionX() - 10, player -> getPositionY() - 20, 20, 40);
// 플레이어의 충돌박스 설정
for (Sprite * sprItem : items) { // 아이템을 먹고 적용효과를
나타내기위해 이치문활용
Rect rectItem = sprItem -> getBoundingBox();
if (rectPlayer.intersectsRect(rectItem)) {
removeSpr = sprItem;
}
}
if (items.contains(removeSpr)) {
items.eraseObject(removeSpr);
resetItem(removeSpr);
isGetItem = true;
this -> scheduleOnce(schedule_selector(GameScene::getItem), 5.0);
// 먹고난후 5초간 효과유지
}
}
void GameScene::checkEnemy() {
auto removeMissile = Sprite::create();
auto removeEnemy = Enemy::create();
Vector < Sprite *> removeMissiles;
for (Sprite * missile : missiles) {
Rect rectMissile = missile -> getBoundingBox(); // /미사일
for (Enemy * enemys : enemies) {
Rect rectEnemy = enemys -> getBoundingBox(); // 적
if (rectMissile.intersectsRect(rectEnemy)) { // 만약
미사일이랑 적이랑 만났다면
int attack = missile -> getTag();
// 미사일 태그값
removeMissile = missile;
removeMissiles.pushBack(missile);
enemys -> hp -= attack;
// 적의 hp가 1씩감소
if (enemys -> type == 1 && enemys -> hp <= 0) { // 타입1적은 체력이 5이므로 0이 됬을경우 점수3000점을 추가
removeEnemy = enemys;
addScore(3000);
} else if (enemys -> type == 2 && enemys -> hp <= 0) { // 타입2적은 체쳑이 1이므로 0이 됬을경우 점수1000점을 추가
removeEnemy = enemys;
addScore(1000);
}
}
}
if (enemies.contains(removeEnemy)) {
SimpleAudioEngine::getInstance() -> playEffect("Sounds/boom.wav");
auto particle = ParticleSystemQuad::create("explosion.plist");
particle -> setPosition(removeEnemy -> getPosition());
particle -> setScale(0.6);
this -> addChild(particle);
auto action = Sequence::create(DelayTime::create(1.0), CallFuncN::create(CC_CALLBACK_1(GameScene::resetBoom, this)), NULL);
particle -> runAction(action);
resetEnemy(removeEnemy);
break;
}
}
for (Sprite * sprite : removeMissiles) {
if (missiles.contains(sprite)) {
resetAttack(sprite);
}
}
}
void GameScene::checkPlayer() { // 적이랑 플레이어랑 만났을때 처리하는 함수resetEnemy
Sprite *removeMissile;
Enemy * removeEnemise;
auto user = (Sprite *)this -> getChildByTag(gPlayerHP);
Rect rectPlayer = Rect(user -> getPositionX() - 10, user -> getPositionY() - 20, 20, 40);
// 플레이어의 충돌박스 설정
for (Enemy * enemys : enemies) {
Rect rectEnemy = enemys -> getBoundingBox(); // 적
if (playerLive && rectPlayer.intersectsRect(rectEnemy)) // 만약 플레이어랑
적이랑 부딪쳤다면 {
removeEnemise = enemys;
gPlayerHP -= TAG_ENEMY_DAMAGE; // HP는
3 이며 태그값1 이라 1 씩감소 user - > setTag(gPlayerHP); // 태그값 재지정
UpdateLife(); // 업로드함수 호출
gameReset();
if (user -> getTag() == TAG_GAME::TAG_GAME_PLAYER_DIE)
// 만약 HP가 0이라면 게임끝
{
playerLive = false;
gameEnd();
return;
}
}
}
if (enemies.contains(removeEnemise)) {
SimpleAudioEngine::getInstance() -> playEffect("Sounds/boom.wav");
auto particle = ParticleSystemQuad::create("explosion.plist");
particle -> setPosition(user -> getPosition());
particle -> setScale(1.8);
this -> addChild(particle, 2);
particle -> runAction(Sequence::create(DelayTime::create(1.0 f), RemoveSelf::create(), NULL));
resetEnemy(removeEnemise);
}
}
void GameScene::update(float delta) {
if (pCheck)
return;
checkItem();
checkEnemy();
checkPlayer();
}
void GameScene::stopGameOver() // 게임 끝났을경우의 GAME OVER에 관한메세지 글씨 생성 함수
{
auto gameover = Label::createWithSystemFont("GAME OVER", "Hobo Std", 60);
gameover -> setPosition(Vec2(300, 450));
gameover -> setTextColor(Color4B::RED);
gameover -> setScale(0.0 f);
gameover -> setVisible(false);
gameover -> setTag(TAG_PLAYER_END);
this -> addChild(gameover, 1);
}
void GameScene::setGameover() { // 게임이 끝났을 경우에 대한 메세지창의액션 적용 함수
gCheck = true; // 유저가 살아있을경우 false 지만 충돌해서 죽었으니 true 변경
auto gameover = (Label *)this -> getChildByTag(TAG_PLAYER_END);
gameover -> runAction(Sequence::create(Show::create(), Spawn::create(ScaleTo::create(1.0 f, 1.0 f), RotateBy::create(1.0 f, 360 * 1), NULL), NULL));
}
void GameScene::gameEnd() // 게임이 끝났으니 그에 대한 메시지 출력 및 스케줄 해제 함수
{
pCheck = true;
setGameover();
auto user = (Sprite *)this -> getChildByTag(gPlayerHP);
SimpleAudioEngine::getInstance() -> playEffect("Sounds/boom.wav");
auto particle = ParticleSystemQuad::create("explosion.plist");
particle -> setPosition(user -> getPosition());
particle -> setScale(1.8);
this -> addChild(particle, 2);
particle -> runAction(Sequence::create(DelayTime::create(1.0 f), RemoveSelf::create(), NULL));
for (Enemy * enemys : enemies) {
this -> removeChild(enemys);
}
for (Sprite * missile : missiles) {
this -> removeChild(missile);
}
this -> unschedule(schedule_selector(GameScene::setAttack)); // 메소드 스케줄 해제
this -> unschedule(schedule_selector(GameScene::setEnemy));
}
void GameScene::gameReset() {
for (Enemy * enemys : enemies) {
enemys -> stopAllActions();
enemys -> setPosition(Vec2(-100, -100));
this -> removeChild(enemys);
}
for (Sprite * missile : missiles) {
this -> removeChild(missile);
}
}
void GameScene::initScore() // 플레이어의 생명과 현재점수와 최고점수를 나타내기위해 사용하는 함수
{
auto labelScore = Label::createWithSystemFont("", "", 18);
labelScore -> setPosition(Vec2(460, 880));
labelScore -> setColor(Color3B::WHITE);
labelScore -> setTag(TAG_LABEL_SCORE);
this -> addChild(labelScore, 1);
auto labelHigh = Label::createWithSystemFont("", "", 18);
labelHigh -> setPosition(Vec2(560, 880));
labelHigh -> setColor(Color3B::WHITE);
labelHigh -> setTag(TAG_LABEL_HIGHSCORE);
this -> addChild(labelHigh, 1);
addScore(0);
// Player Life
auto life = Label::createWithSystemFont("", " ", 18);
life -> setPosition(Vec2(60, 880));
life -> setColor(Color3B::WHITE);
life -> setTag(TAG_GAME_LB_PLAYER_LIFE);
this -> addChild(life, 1);
UpdateLife();
}
void GameScene::addScore(int add) {
gScore += add;
if (gScore > gScoreHigh) {
gScoreHigh = gScore;
UserDefault::getInstance() -> setIntegerForKey("HIGH_SCORE", gScoreHigh);
UserDefault::getInstance() -> flush();
}
auto labelScore = (Label *)this -> getChildByTag(TAG_LABEL_SCORE);
labelScore -> setString(StringUtils::format("SCORE : %d", gScore));
auto labelHigh = (Label *)this -> getChildByTag(TAG_LABEL_HIGHSCORE);
labelHigh -> setString(StringUtils::format("/ %d", gScoreHigh));
}
void GameScene::resetAttack(Ref * sender) {
auto spr = (Sprite *)sender;
missiles.eraseObject(spr);
this -> removeChild(spr);
}
void GameScene::resetEnemy(Ref * sender) {
auto spr = (Enemy *)sender;
enemies.eraseObject(spr);
this -> removeChild(spr);
}
void GameScene::resetItem(Ref * sender) {
auto item = (Sprite *)sender;
items.eraseObject(item);
this -> removeChild(item);
}
void GameScene::resetBoom(Ref * sender) {
auto particle = (ParticleSystemQuad *)sender;
this -> removeChild(particle);
}
void GameScene::getItem(float delta) {
isGetItem = false;
}
void GameScene::UpdateLife(void) { // 화면의 생명 수치 조정
auto life = (Label *)getChildByTag(TAG_GAME_LB_PLAYER_LIFE);
life -> setString(StringUtils::format("Life : %d", gPlayerHP - TAG_GAME_PLAYER_DIE));
}
<코드실행 데모영상>