Giới thiệu
Còn gì vui hơn khi có thể tự tay làm ra 1 game? Bài viết hướng dẫn tạo game bằng Cocos2d-x cho dịp Halloween, game với những trái bí ngô được khắc hình trông vui và ngộ nghĩnh.
Game đã dừng phát hành trên cả AppStore và Google Play, bài viết vẫn giữ lại và chia sẻ cho độc giả có thể tự tay phát triển lại.

Kịch bản và cách chơi game
Bắt đầu với 2 quả bí, quả đầu tiên sẽ xuất hiện trước, khi người chơi đã sẵn sàng chơi thì quả bí đầu tiên sẽ di chuyển sang bên trái, nhỏ dần và biến mất, trong khi đó màn hình bên phải sẽ xuất hiện quả bí thứ 2 di chuyển dần ra giữa màn hình và lớn lên khi di chuyển, nhiệm vụ của người chơi là trong thời gian giới hạn phải trả lời rằng quả bí thứ 2 có giống quả bí đầu tiên hay không? Game rất đơn giản nhưng để chơi được điểm cao thì khá là khó, vì điểm càng cao thì thời gian trả lời càng ngắn.
Download data và source code của game
Hiện thực
Trước khi bắt đầu làm game với Cocos2d-x, cần chuẩn bị 1 số công cụ, phần mềm:
- Cocos2d-x v3.4 trở lên.
- Data: đính kèm trong 2 file Part1.zip và Part2.zip bên trên, chia làm 2 phần là data cho menu và data cho gameplay.
- Cocos Studio: công cụ này để tạo ra file *.plist từ data ở trên, tải từ trang chủ của cocos2d-x, cũng có hướng dẫn cách để tạo ra file *.plist từ data là những file ảnh PNG. Lưu ý là khi tạo file *.plist thì tối data kich thước nên 2048x2048 hoặc nhỏ hơn, trong bài viết sử dụng 2048x2048.
- Visual Studio 2019 hoặc lớn hơn.
Game này sử dụng data cho màn hình 720x1280, nó sẽ tự động phóng to thu nhỏ cho tất cả các màn hình.
Command để tạo project và build project
cocos pumpkins -p com.gamnvl.pumpkins -l cpp -d E:\ cocos run -d E:\pumpkins -p android --a18
Config project
Game có tổng cộng 4 state:
- StateLoad: màn hình nạp game.
- StateMainMenu: màn hình main menu.
- StateGamePlay: màn hình chơi game.
- StateEndGame: màn hình khi kết thúc game.
Trong thư mục chính tạo project xong, vào thư mục Classes và dựa vào 2 file HellowordSceen.h và HellowordSceen.cpp, sao chép thành 4 cặp file mới với tên mới lần lượt là tên là:
- StateLoad.h và StateLoad.cpp
- StateMainMenu.h và StateMainMenu.cpp
- StateGamePlay.h và StateGamePlay.cpp
- StateEndGame.h và StateEndGame.cpp
Bao gồm sửa tên class trong các file trùng với tên file.
Tạo file config.h để khai bao cho các cấu hình, biến toàn cục cần thiết trong game, có thể xem tham khảo trong mã nguồn đính kèm của bài viết này. Nội dung trong file config không quá phức tạp, bao gồm: khai báo đường dẫn, tọa độ, tên file, ...
Khai báo các file mới vừa thêm vào project, mở file CMakeLists.txt ngoài thư mục chính của project và tìm theo tên "AppDelegate.cpp", cập nhật thêm như sau:
Classes/AppDelegate.cpp Classes/StateLoad.cpp Classes/StateMainMenu.cpp Classes/StateGamePlay.cpp Classes/StateEndGame.cpp
Bên dưới là khai báo những file .h, cập nhật như sau:
Classes/AppDelegate.h Classes/StateLoad.h Classes/StateMainMenu.h Classes/StateGamePlay.h Classes/StateEndGame.h Classes/NativeAndroidHelper.h Classes/config.h
Proejct này có thể chạy trên Win32 và thiết bị Android, do đó cần khai báo thêm ở file "Pumpkins\proj.android\jni\Android.mk" và "Pumpkins\proj.win32\Pumpkins.vcxproj", cập nhật như sau:
../../Classes/AppDelegate.cpp \ ../../Classes/StateLoad.cpp \ ../../Classes/StateMainMenu.cpp \ ../../Classes/StateGamePlay.cpp \ ../../Classes/StateEndGame.cpp \
<ClCompile Include="..\Classes\AppDelegate.cpp" /> <ClCompile Include="..\Classes\StateLoad.cpp" /> <ClCompile Include="..\Classes\StateMainMenu.cpp" /> <ClCompile Include="..\Classes\StateGamePlay.cpp" /> <ClCompile Include="..\Classes\StateEndGame.cpp" /> <ClCompile Include="main.cpp" /> </ItemGroup> <ItemGroup> <ClInclude Include="..\Classes\AppDelegate.h" /> <ClInclude Include="..\Classes\StateLoad.h" /> <ClInclude Include="..\Classes\StateMainMenu.h" /> <ClInclude Include="..\Classes\StateGamePlay.h" /> <ClInclude Include="..\Classes\StateEndGame.h" /> <ClInclude Include="..\Classes\config.h" />
Sau đó mở file AppDelegate.cpp và cập nhật dòng include Helloword.h thành StateLoad.h, config cho chạy game với màn hình 720x1280 và tự động "fit" khi chạy trên màn hình lớn hơn, mặc định FPS là 60, có thể cập nhật lại 40 để FPS ổn định và ít tốn Pin hơn.
#include "AppDelegate.h" #include "StateLoad.h" #include "config.h" USING_NS_CC; static cocos2d::Size designResolutionSize = cocos2d::Size(480, 320); static cocos2d::Size smallResolutionSize = cocos2d::Size(480, 320); static cocos2d::Size mediumResolutionSize = cocos2d::Size(1024, 768); static cocos2d::Size largeResolutionSize = cocos2d::Size(2048, 1536); AppDelegate::AppDelegate() { } AppDelegate::~AppDelegate() { } //if you want a different context,just modify the value of glContextAttrs //it will takes effect on all platforms void AppDelegate::initGLContextAttrs() { //set OpenGL context attributions,now can only set six attributions: //red,green,blue,alpha,depth,stencil GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8}; GLView::setGLContextAttrs(glContextAttrs); } // If you want to use packages manager to install more packages, // don't modify or remove this function static int register_all_packages() { return 0; //flag for packages manager } bool AppDelegate::applicationDidFinishLaunching() { #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) sdkbox::PluginFacebook::init(); sdkbox::PluginChartboost::init(); #endif // initialize director auto director = Director::getInstance(); auto glview = director->getOpenGLView(); if(!glview) { glview = GLViewImpl::create("Funny Halloween Pumpkins"); glview->setFrameSize(SCREEN_WIDTH_DESIGN / 2, SCREEN_HEIGHT_DESIGN / 2); //SCREEN_WIDTH_DESIGN , SCREEN_HEIGHT_DESIGN trong config.h director->setOpenGLView(glview); } // turn on display FPS director->setDisplayStats(false); // set FPS. the default value is 1.0/60 if you don't call this director->setAnimationInterval(1.0 / 40); // Set the design resolution glview->setDesignResolutionSize(SCREEN_WIDTH_DESIGN, SCREEN_HEIGHT_DESIGN, ResolutionPolicy::EXACT_FIT); Size frameSize = glview->getFrameSize(); register_all_packages(); // create a scene. it's an autorelease object auto scene = StateLoad::createScene(); // run director->runWithScene(scene); return true; } // This function will be called when the app is inactive. When comes a phone call,it's be invoked too void AppDelegate::applicationDidEnterBackground() { Director::getInstance()->stopAnimation(); } // This function will be called when the app is active again void AppDelegate::applicationWillEnterForeground() { Director::getInstance()->startAnimation(); }
StateLoad

Hiện thực phần StateLoad của game, chỉ cần hiển thị logo và load những data cần thiết để chạy game, trong hàm dưới sử dụng hàm update(float dt)
, hàm này được gọi mỗi frame của game và 1 biến để đếm thời gian load data. Mở file StateLoad.h và thêm vào như sau:
#ifndef __STATE_LOAD_H__H__ #define __STATE_LOAD_H__H__ #include "cocos2d.h" class StateLoad : public cocos2d::Layer { public: // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); void update(float dt); //overwrite hàm cocos CREATE_FUNC(StateLoad); private: int m_loadingStep; // dem thoi gian de chuyen sang state main menu }; #endif // __STATE_LOAD_H__H__
Mở file StateLoad.cpp, trong hàm init()
, gán m_loadingStep = 0
và cho phép hàm update()
chạy bằng cách gọi this->scheduleUpdate()
.
Trong nội dung của hàm update()
, load tuần tự như sau.
- Frame đầu tiên load data của menu từ file Menu0.plist được đặt tên là NamePlistMenu trong config.h
- Frame thứ 2 và 3 load data của gameplay từ file Pumpkins0.plist, Pumpkins1.plist
- Frame thứ 4 vẽ background và logo lên màn hình khoản 3s.
- Frame 120 là 3s vì 40 frame là 1s, thì chuyển sang state main menu.
Đoạn code thực hiện những việc trên sẽ như sau:
#include "StateLoad.h" #include "StateMainMenu.h" #include "config.h" USING_NS_CC; Scene* StateLoad::createScene() { // 'scene' is an autorelease object auto scene = Scene::createWithPhysics(); // scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); scene->getPhysicsWorld()->setGravity(Vect(0.0f, 0.0f)); // 'layer' is an autorelease object auto layer = StateLoad::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool StateLoad::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ) { return false; } Size winSize = Director::getInstance()->getWinSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); m_loadingStep = 0; this->scheduleUpdate(); return true; } void StateLoad::update(float dt) { LayerColor *BG; Sprite *logo; switch(m_loadingStep) { case 0: SpriteFrameCache::getInstance()->addSpriteFramesWithFile(NamePlistMenu); break; case 1: SpriteFrameCache::getInstance()->addSpriteFramesWithFile(NamePlistPumpkins0); break; case 2: SpriteFrameCache::getInstance()->addSpriteFramesWithFile(NamePlistPumpkins1); break; case 3: BG = LayerColor::create(blueColor4b); this->addChild(BG, zUi); logo = Sprite::createWithSpriteFrameName(NameLogo); logo->setPosition(SCREEN_WIDTH_DESIGN / 2, SCREEN_HEIGHT_DESIGN / 2); this->addChild(logo, zUi); break; case 120: Director::getInstance()->replaceScene(StateMainMenu::createScene()); this->unscheduleUpdate(); break; } m_loadingStep++; }
StateMainMenu

Trong StateMainMenu gồm có:
- 1 background.
- 20 quả bí ngô đặt ở vị trí ngẫu nhiên với độ to nhỏ khác nhau và xoay theo mọi hướng.
- Title game Funny Halloween Pumpkins.
- Bên dưới là nút Play game.
- Bật tắt sound, high score và button rate.
StateMainMenu.h
Trong file này có các hàm như sau.
#ifndef __STATE_MAINMENU_H__ #define __STATE_MAINMENU_H__ #include "cocos2d.h" #include "ui/CocosGUI.h" USING_NS_CC; class StateMainMenu : public cocos2d::Layer { public: // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); void drawRandomPumpkins(); //Vẽ những trái bí ngô ở những tọa độ ngẫu nhiên khi vào state main menu void drawMenu(); //Vẽ và xử lý các menu như play game, tắt mở âm thanh, xem điểm cao nhất, đánh giá game. void gotoGamePlay(); //Chuyển tới state game play. void update(float dt); //Update mỗi frame để tính toán xử lý menu. void UpdateAdsBanner(); //Xử lý hiện và ẩn banner quảng cáo. void showInterstitial(); //Xử lý hiện hình quảng cáo full màn hình int effectTitle; void updateSoundBT(); //Update để vẽ thay đổi của nút âm thanh khi thay đổi trạng thái void RefeshSound(); //Vẽ lại trạng thái của nút âm thanh bool soundChange; void drawHighScore(); //Vẽ điểm số cao nhất bool m_showHighScore; /Giá trị điểm số cao nhất int tagHighScore; //Lưu giá trị tag của đối tượng để xóa đối tượng khỏi màn hình khi không cần void shareFaceBook(); //Chia sẽ điểm số cao nhất lên facebook void RateGame(); //Đánh giá chất lượng game void showDialogExit(); //Thông báo xác nhận thoát game void YesConfirm(); //Chấp nhận thông báo void NoConfirm(); //Từ chối thông báo bool m_DialogConfirm; int tagDialogConfirm; void onKeyReleased(EventKeyboard::KeyCode keyCode, cocos2d::Event *event); //Bắt sự kiện nhấn phím void onTouchEnded(cocos2d::Touch* touches, cocos2d::Event* event); //Bắt sự kiện kết thúc chạm man hình void onTouchMoved(cocos2d::Touch* touches, cocos2d::Event* event); // Bắt sự kiện di chuyển trên màn hình bool onTouchBegan(cocos2d::Touch* touches, cocos2d::Event* event); //Bắt sự kiện chạm màn hình // implement the "static create()" method manually CREATE_FUNC(StateMainMenu); private: int countToShowBanner; }; #endif // __STATE_MAINMENU_H__
StateMainMenu.cpp
Có 2 bước để làm 1 game hoàn thiện là:
- Tạo giao diện cho game.
- Gắn xử lý sự kiện cho game.
1. Tạo giao diện cho game
Trước tiên trong hàm init()
của State này, vẽ background cho game, đặt tại vị trí tâm của màn hình bằng đoạn code sau với NameBG_MM
, SCREEN_WIDTH_DESIGN
, SCREEN_HEIGHT_DESIGN
đã được khai báo trong config.h, có thể tham khảo thêm trong config.h được cung cấp trong mã đính kèm của bài.
auto SpriteBG = Sprite::createWithSpriteFrameName(NameBG_MM); SpriteBG->setPosition(SCREEN_WIDTH_DESIGN/2, SCREEN_HEIGHT_DESIGN/2); this->addChild(SpriteBG, zBG);
Tiếp tục vẽ những quả bí ngô ở các tọa độ ngẫu nhiên trên màn hình, đặt trong hàm drawRandomPumpkins()
và cũng gọi hàm này trong init()
, hàm drawRandomPumpkins()
như sau:
void StateMainMenu::drawRandomPumpkins() { Label *copyright = Label::createWithTTF(arial_font24, "@ gamenvl"); // tao text voi font 24 co noi dung @ gamenvl copyright->setTextColor(whiteColor4B); // set mau cho text copyright->setPosition(posTitleMM.x, 20); // set vi tri ve ra man hinh this->addChild(copyright, zTitile); // zTitile: gia tri zorder cao se ve de len doi tuong co zorder thap for (int i = 0; i < 20; i++) // ve 20 trai bi ngo o cac vi tri ngau nhien trong man hinh { int id = i;// random(i * 2, i * 2 + 1); int posX = (i%2==0 ? random(0, 360) : random(360, 720)); int posY = random(i/2 * 128 + (i/2>0 ?50:0), i/2 * 128 + 128); int rotate = random(0,255); float scaleEffect = 0.5 + sqrt((600-posX)*(600-posX) + (450-posY)*(450-posY)) * 0.5 / 800; auto SpritePumpkin = Sprite::createWithSpriteFrameName(NamePumpkins[id]); // tao trai bi ngo voi ten SpritePumpkin->setPosition(posX, posY); SpritePumpkin->setRotation(rotate); // xoay doi tuong trong gia tri 0->360 SpritePumpkin->setScale(0.25*scaleEffect); // scale doi tuong trong gia tri tu 0.5-1 SpritePumpkin->setOpacity(150); // set do xuyen suot cua doi tuong (0-255) this->addChild(SpritePumpkin, zBG); // dua ra man hinh } }
Cuối cùng vẽ menu trong hàm drawMenu()
và gọi hàm này trong init()
của State như sau:
void StateMainMenu::drawMenu() { auto btPlay = MenuItemImage::create(); // tao nut play game btPlay->setNormalImage(Sprite::createWithSpriteFrameName(NamebtPlay)); // set anh binh thuong btPlay->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtPlayPress)); // set anh khi nhap vao btPlay->setCallback(CC_CALLBACK_0(StateMainMenu::gotoGamePlay, this)); // goi toi ham gotoGamePlay khi nhap vao nut play btPlay->setPosition(posbtPlayMM); // set vi tri dat nut play auto btHighScoreBG = Sprite::createWithSpriteFrameName(NamebtBG); // tao BG nut diem cao btHighScoreBG->setPosition(posbtHighScore); btHighScoreBG->setOpacity(200); this->addChild(btHighScoreBG, zUi); auto btHighScore = MenuItemImage::create(); // tao nut diem cao btHighScore->setNormalImage(Sprite::createWithSpriteFrameName(NamebtHighScore)); btHighScore->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtHighScore)); btHighScore->setCallback(CC_CALLBACK_0(StateMainMenu::drawHighScore, this)); // goi toi ham ve diem cao khi nhan nut btHighScore->setPosition(posbtHighScore); auto btRateBG = Sprite::createWithSpriteFrameName(NamebtBG); // tuong tu nhu tren btRateBG->setPosition(posbtRate); btRateBG->setOpacity(200); this->addChild(btRateBG, zUi); auto btRate = MenuItemImage::create(); // tuong tu nhu tren btRate->setNormalImage(Sprite::createWithSpriteFrameName(NamebtRate)); btRate->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtRate)); btRate->setCallback(CC_CALLBACK_0(StateMainMenu::RateGame, this)); btRate->setPosition(posbtRate); auto menu = Menu::create(btPlay, btHighScore, btRate, NULL); // tao 1 menu gom cac nut play, diem cao va nut danh gia game menu->setPosition(Point::ZERO); // Luu y phai dat tai dim 0 vi da setpos o tren doi tuong roi this->addChild(menu, zUi); }
Phần giao diện đến đây coi như đã xong, chỉ còn thiếu nút Sound, vì nút Sound thay đổi liên tục nên vẽ trong lúc xử lý hàm update
.
2. Gắn xử lý sự kiện cho game
Để trong game nhận được sự kiện nhấn phím thì cần active nó trong hàm init()
:
this->setKeypadEnabled(true);
Để game chạy hàm update()
liên tục mỗi frame và nhận được các sự kiện chạm màn hình thì active nó trong hàm init()
.
this->scheduleUpdate(); auto dispatcher = Director::getInstance()->getEventDispatcher(); auto listener = EventListenerTouchOneByOne::create(); listener->setSwallowTouches(true); listener->onTouchBegan = CC_CALLBACK_2(StateMainMenu::onTouchBegan, this); listener->onTouchMoved = CC_CALLBACK_2(StateMainMenu::onTouchMoved, this); listener->onTouchEnded = CC_CALLBACK_2(StateMainMenu::onTouchEnded, this); dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
Xử lý cho nút Play. Khi nãy khi tôi tạo nút play có gán điều kiện nếu nhấn nút play thì nó sẽ gọi tới hàm gotoGamePlay(), và hàm này sẽ xử lý như sau:
void StateMainMenu::gotoGamePlay() { if (m_DialogConfirm || m_showHighScore) // 2 bien nay la dieu kien khi co thong bao thi se khong xu ly nut play return; auto audio = SimpleAudioEngine::getInstance(); // nhan bien am tham audio->stopBackgroundMusic(true); // tat am thanh truoc khi chuyen state Director::getInstance()->replaceScene(StateGamePlay::createScene()); // chuyen den state gameplay }
Xử lý cho nút high score. Khi tạo nút Híghcore tôi có gán điều kiện khi nhấn vào nút thì sẽ gọi hàm drawHighScore(), hàm xử lý như sau:
void StateMainMenu::drawHighScore() { if (m_DialogConfirm || m_showHighScore) // dieu kien khi dang co thong bao thi thoat khoi ham return; m_showHighScore = true; while (tagHighScore > 2000) // Luu y: nho set tagHighScore = 2000 trong init() { tagHighScore--; this->removeChildByTag(tagHighScore); //tagHighScore de remove thong bao trc khi ve nhu vay se khong bị duplicate khi ve } auto userdefault = UserDefault::sharedUserDefault(); // nho #include <SimpleAudioEngine.h> using namespace CocosDenshion; de su dung duoc int highScore = userdefault->getIntegerForKey(HIGH_SCORE); // lay gia tri diem cao luu trong bo nho auto bgHighScore = Sprite::createWithSpriteFrameName(NameHighScoreBG); // tao BG de ve diem len bgHighScore->setPosition(posHighScoreMM); bgHighScore->setTag(tagHighScore++); // Luu y tagHighScore de quan ly tat ca cac doi tuong trong bang diem this->addChild(bgHighScore, zUi); auto textScore = Label::createWithTTF(Pumpkins_font60, "HIGH SCORE"); // tao text textScore->setPosition(bgHighScore->getPosition().x, bgHighScore->getPosition().y + bgHighScore->getContentSize().height / 2 - 100); textScore->setTextColor(pinkColor4B); textScore->setTag(tagHighScore++); this->addChild(textScore, zUi); auto strB = CCString::createWithFormat("%i", highScore); // tao text mang gia tri diem cao auto Bscore = Label::createWithTTF(Pumpkins_font60, strB->getCString()); Bscore->setPosition(bgHighScore->getPosition().x, bgHighScore->getPosition().y - 100); Bscore->setTextColor(pinkColor4B); Bscore->setTag(tagHighScore++); this->addChild(Bscore, zUi); auto face_btn = Sprite::createWithSpriteFrameName(NamebtBG); face_btn->setPosition(bgHighScore->getPosition().x, bgHighScore->getPosition().y - bgHighScore->getContentSize().height / 2 - face_btn->getContentSize().height / 2); //face_btn->setOpacity(100); face_btn->setTag(tagHighScore++); this->addChild(face_btn, zUi); auto face = MenuItemImage::create(); // tao nut de share diem len facebook face->setNormalImage(Sprite::createWithSpriteFrameName(NamebtFace)); face->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtFace)); face->setPosition(face_btn->getPosition()); face->setCallback(CC_CALLBACK_0(StateMainMenu::shareFaceBook, this)); // goi den ham shareFacebook khi nhan nut auto menu = Menu::create(face, NULL); // tao menu chua nut face menu->setPosition(Point::ZERO); menu->setTag(tagHighScore++); this->addChild(menu, zUi); }
Thế là xong nút HighScore nhưng trong nút HighScore lại có thêm nút shareFacebook, nên giờ chúng ta sẽ đi đến xử lý nut shareFacebook, hàm này sẽ như sau:
void StateMainMenu::shareFaceBook() { auto userdefault = UserDefault::sharedUserDefault(); int highScore = userdefault->getIntegerForKey(HIGH_SCORE); // lay gia tri diem cao auto tempText = CCString::createWithFormat("My best score is %i. Let try it to get best score with me!", highScore); #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) // Luu y phan này chi danh cho android, chúng ta se tiep can o cuoi bai nhe sdkbox::PluginFacebook::requestReadPermissions({ FB_PERM_READ_PUBLIC_PROFILE, FB_PERM_READ_USER_FRIENDS }); sdkbox::PluginFacebook::requestPublishPermissions({ FB_PERM_PUBLISH_POST }); FBShareInfo info; info.type = FB_LINK; info.link = "https://play.google.com/store/apps/details?id=com.gamenvl.pumpkins"; info.title = "Funny Halloween Pumpkins"; info.text = tempText->getCString(); info.image = "http://i.imgur.com/vBwYKHH.png"; PluginFacebook::dialog(info); #endif }
Xử lý cho nút Rate game. Ở trên khi tôi tạo nút Rate game thì có gán điều kiện sẽ gọi tới hàm RateGame() khi nhấn vào, và hàm sẽ được viết như sau:
void StateMainMenu::RateGame() { if (m_DialogConfirm || m_showHighScore) return; std::string link = std::string("https://play.google.com/store/apps/details?id=com.gamenvl.pumpkins"); // link game tren google play Application::getInstance()->openURL(link); // Khi up game cac ban len chi thay the cai ten pack "com.gamenvl.pumpkins" }
Còn lại nút sound để tắt mở sound, xử lý nút này trong hàm update
của game như đã đề cập ở trên.
void StateMainMenu::update(float dt) { effectTitle++; // gia tri default set = 0 trong ham Init() float scale = 1.0f; if (effectTitle > 80) // doan code sau tinh toan ti le scale cua title game 40frame = 1s { scale = 1.03f; } if (effectTitle > 85) { scale = 1.06f; } if (effectTitle > 90) { scale = 1.09f; } if (effectTitle > 95) { scale = 1.12f; } if (effectTitle > 100) { scale = 1.0f; effectTitle = 0; } this->removeChildByTag(tagTitleMM1); this->removeChildByTag(tagTitleMM2); auto titleMM = Label::createWithTTF(Pumpkins_font90, "Funny Halloween"); titleMM->setColor(yellowColor3B); titleMM->setTag(tagTitleMM1); titleMM->setScale(scale); titleMM->setPosition(posTitleMM); this->addChild(titleMM, zTitile); auto titleMM1 = Label::createWithTTF(Pumpkins_font90, "Pumpkins"); titleMM1->setColor(yellowColor3B); titleMM1->setScale(scale); titleMM1->setTag(tagTitleMM2); titleMM1->setPosition(posTitleMM.x, posTitleMM.y - 100); this->addChild(titleMM1, zTitile); if (soundChange) updateSoundBT(); // Ham xu ly sound, soundChange nen default la true trong Init() UpdateAdsBanner(); // cai nay quang cao toi se noi toi o cuoi game nhe }
Như vậy trong hàm update
gọi hàm updateSoundBT()
, xử lý hàm này như sau:
void StateMainMenu::updateSoundBT() { auto userdefault = UserDefault::sharedUserDefault(); int soundEnable = userdefault->getIntegerForKey(SoundEnable); this->removeChildByTag(tagbtSoundBG, true); this->removeChildByTag(tagbtSound, true); auto btSoundBG = Sprite::createWithSpriteFrameName(NamebtBG); btSoundBG->setPosition(posbtSound); btSoundBG->setOpacity(200); btSoundBG->setTag(tagbtSoundBG); this->addChild(btSoundBG,zUi); auto btSound = MenuItemImage::create(); // tao nut sound if (soundEnable == soundOn) { btSound->setNormalImage(Sprite::createWithSpriteFrameName(NamebtSoundOn)); // set anh binh thuong neu sound on btSound->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtSoundOff)); // set anh tat sound neu chon } else { btSound->setNormalImage(Sprite::createWithSpriteFrameName(NamebtSoundOff)); // nguoc lai btSound->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtSoundOn)); } btSound->setPosition(posbtSound); btSound->setCallback(CC_CALLBACK_0(StateMainMenu::RefeshSound, this)); // goi toi ham RefeshSound khi nhap vap auto menu = Menu::create(btSound, NULL); menu->setPosition(Point::ZERO); menu->setTag(tagbtSound); this->addChild(menu, zUi); soundChange = false; }
Trong hàm updateSoundBT()
lại gọi tới hàm RefeshSound()
, vào hàm này tiếp tục.
void StateMainMenu::RefeshSound() { if (m_DialogConfirm || m_showHighScore) return; auto userdefault = UserDefault::sharedUserDefault(); int soundEnable = userdefault->getIntegerForKey(SoundEnable); if (soundEnable == soundOn) { auto audio = SimpleAudioEngine::getInstance(); audio->stopBackgroundMusic(); userdefault->setIntegerForKey(SoundEnable, soundOff); // Luu trang thai sound on or off vao bo nho userdefault->flush(); } else { auto audio = SimpleAudioEngine::getInstance(); audio->playBackgroundMusic("sounds/BG_Pumpkins.wav", true); userdefault->setIntegerForKey(SoundEnable, soundOn); // Luu trang thai sound on or off vao bo nho userdefault->flush(); } soundChange = true; }
Nút sound được tách làm 2 hàm vì 1 hàm để vẽ, hàm còn lại để xử lý thay đổi khi nhấn phím.
StateGamePlay

StateGamePlay.h
#ifndef __STATE_GAMEPLAY__H__ #define __STATE_GAMEPLAY__H__ #include "cocos2d.h" USING_NS_CC; class StateGamePlay : public cocos2d::Layer { public: // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); void update(float dt); void drawClock(float dt); // ve dong ho tinh thoi gian luot choi void drawScore(float dt); // ve diem so hien tai void drawPumpkins(); // ve trai bi ngo void drawTextGP(); // ve chu thich ra man hinh huong dan nguoi choi void drawAnswer(); // ve cau tra loi cho nguoi dung chon void AnswerNo(); // xu ly nguoi dung chon no void AnswerYes(); // xu ly khi nguoi dung chon yes void actionFinish(cocos2d::Node * sender); // remove doi tuong ra khoi man hinh khi thuc hien xong hanh dong void checkAnswer(bool ans); // kiem tra cau tra loi, neu dung se den cau tiep theo, sai se ket thuc game void gotoEndGame(); // chuyen sang state end game void UpdateAdsBanner(); // quang cao banner void showDialogBonusTime(); // thong bao thoi gian them khi xem video void YesConfirm(); // chap nhan thong bao void NoConfirm(); // khong chap nhan thong bao void onKeyReleased(EventKeyboard::KeyCode keyCode, cocos2d::Event *event); void onTouchEnded(cocos2d::Touch* touches, cocos2d::Event* event); void onTouchMoved(cocos2d::Touch* touches, cocos2d::Event* event); bool onTouchBegan(cocos2d::Touch* touches, cocos2d::Event* event); // implement the "static create()" method manually CREATE_FUNC(StateGamePlay); private: int countToShowBanner; ProgressTimer *m_timer; float m_currentTime; bool gameReady; bool gameTimeOut; bool gameAnswer; int Score; int scaleTextBegin; int PumpkinCurrentID; int PumpkinPreviousID; Sprite *PumpkinCurrent; // sprite trai bi ngo hien hanh Sprite *PumpkinPrevious; // sprite trai bi ngo truoc float maxTime ; float timeTotal; bool gameOver; int tagDialogBonus; bool m_DialogBonusShow = true; }; #endif // __STATE_GAMEPLAY__H__
StateGamePlay.cpp
Trong init()
của StateGamePlay, để game nhận được key và touch thì cần active hàm update giốnng bên trên:
this->scheduleUpdate(); // active ham update() chay lien tuc moi frame this->setKeypadEnabled(true); // active key pad bat su kien khi nhan key auto dispatcher = Director::getInstance()->getEventDispatcher(); // khai bao bat su kien touch auto listener = EventListenerTouchOneByOne::create(); listener->setSwallowTouches(true); listener->onTouchBegan = CC_CALLBACK_2(StateGamePlay::onTouchBegan, this); listener->onTouchMoved = CC_CALLBACK_2(StateGamePlay::onTouchMoved, this); listener->onTouchEnded = CC_CALLBACK_2(StateGamePlay::onTouchEnded, this); dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
Tiếp theo do background của phần game play không thay đổi, nên vẽ 1 lần ở phần init()
:
auto spriteBG = Sprite::createWithSpriteFrameName(NameBG_GP); spriteBG->setPosition(posMidleScreen); this->addChild(spriteBG, zBG);
Tới phần chính của game play là vẽ quả bí ngô ra màn hình, làm giống như là phần tóm tắt game play ở phía trên. Hàm drawPumpkins()
gọi trong init()
, nội dung của hàm drawPumpkins()
như sau:
void StateGamePlay::drawPumpkins() { if (PumpkinCurrentID == -1) // gan PumpkinCurrentID = -1 o dau ham Init() de khoi tao trai bi dau tien { PumpkinCurrentID = random(0,19); // trai bi dau tien se ngau nhien trong data 20 trai bi } else // khi bat dau choi game se vao phan nay { PumpkinPrevious = PumpkinCurrent; // toi gan doi tuong sprite hien hanh thanh sprite truoc PumpkinPrevious->runAction(MoveTo::create(0.1f, posPumpkinEnd)); // thuc hien hanh dong di chuyen sang ben trai man hinh PumpkinPrevious->runAction(Sequence::create(ScaleTo::create(0.1f, 0.1f), // scale nho doi tuong con 10% trong 0.1s CCCallFuncN::create(CC_CALLBACK_1(StateGamePlay::actionFinish, this)), NULL //goi toi ham actionFinish khi xong hanh dong tren ) ); if (random(0, 2) == 1) // toi cho xac suat 33% { PumpkinPreviousID = PumpkinCurrentID; // trai bi sau se giong trai bi truoc va cau tra loi dung se la YES gameAnswer = true; } else // xac suat 66% la sai { PumpkinPreviousID = PumpkinCurrentID; // chuyen id trai bi hien hanh int temp = random(0, 19); while (temp == PumpkinCurrentID) { temp = random(0, 19); } PumpkinCurrentID = temp; // gan gia tri id moi va khac id trai bi truoc do gameAnswer = false; // cau tra loi dung se la NO } } auto spritePumpkin = Sprite::createWithSpriteFrameName(NamePumpkins[PumpkinCurrentID]); spritePumpkin->setPosition(posPumpkinBegin); // set vi tri trai bi moi se xuat hien ben phai man hinh spritePumpkin->setScale(0.1f); // set do lon chi bang 10% trai bi goc auto moveto = MoveTo::create(0.1f, posPumpkinMid); // tao hanh dong di chuyen ra giua man hinh auto scaleto = ScaleTo::create(0.1f, 1.0f); // tao hanh dong scale lon 100% trai bi trong 0.1s spritePumpkin->runAction(moveto); // gan hanh dong di chuyen cho doi tuong sprite la trai bi spritePumpkin->runAction(scaleto); // gan hanh dong scale this->addChild(spritePumpkin, zUi); // hien thi len man hinh PumpkinCurrent = spritePumpkin; // Luu lai doi tuong kieu con tro de su dung timeTotal = float(maxTime - float(0.025*Score/2)); // thoi gian tra loi cau hoi se giam dan khi diem cang cao if (timeTotal < 1.0f) // thoi gian nho nhat la 1s timeTotal = 1.0f; m_currentTime = timeTotal; auto userdefault = UserDefault::sharedUserDefault(); int soundEnable = userdefault->getIntegerForKey(SoundEnable); if (soundEnable == soundOn) { auto audio = SimpleAudioEngine::getInstance(); audio->playEffect("sounds/wind.wav", false); } }
Bên trong hàm vẽ bí ngô trên có thêm hàm actionFinish()
, hàm này có tác dụng là xóa đối tượng khỏi màn hình để không còn tốn bộ nhớ quản lý vì càng quản lý nhiều đối tượng thì game xử lý chậm, và hàm này sẽ được viết như sau:
void StateGamePlay::actionFinish(cocos2d::Node* sender) { auto sprite = (Sprite*)sender; this->removeChild(sprite, true); // xoa doi tuong khoi man hinh }
Tiến hành phần xử lý game, tất cả được đặt trong hàm update()
, viết hàm update()
như sau:
void StateGamePlay::update(float dt) { drawClock(dt); // ve dong ho tinh thoi gian drawScore(dt); // ve diem so hien co drawTextGP(); // ve text huong dan choi drawAnswer(); // ve cau tra loi UpdateAdsBanner(); // update hien baner quang cao. }
1. drawClock(dt)
Hàm này tính toán thời gian và vẽ đồng hồ báo giờ, hàm này sẽ được viết như sau:
void StateGamePlay::drawClock(float dt) { if (gameReady) // khai bao gameReady = false o ham Init(), se bat len tru khi nguoi choi touch man hinh { this->removeChildByTag(tagClock); // xoa doi tuong dong ho cu khoi man hinh de lat toi cai moi (giong nhu toi refesh) this->removeChildByTag(tagClock1); this->removeChildByTag(tagClockTimer); auto ClockBG = Sprite::createWithSpriteFrameName(NameClockWhite); ClockBG->setPosition(posClock); ClockBG->setColor(yellowColor3B); ClockBG->setScale(1.1); ClockBG->setTag(tagClock); this->addChild(ClockBG, zUi); auto ClockBG1 = Sprite::createWithSpriteFrameName(NameClockBlue); ClockBG1->setPosition(posClock); ClockBG1->setTag(tagClock1); this->addChild(ClockBG1, zUi); m_timer = ProgressTimer::create(Sprite::createWithSpriteFrameName(NameClockWhite)); m_timer->setReverseDirection(true); m_timer->setPosition(posClock); m_timer->setTag(tagClockTimer); this->addChild(m_timer, zUi); m_currentTime -= dt; if (m_currentTime < 0) // khi het gio thi se bao nguoi choi de ket thuc game { m_currentTime = 0; gameTimeOut = true; // active biet time out checkAnswer(false); // goi den check answer voi tham so la false de ket thuc game } // cang gan het gio thi dong ho se chuyen sang mau do m_timer->setColor(Color3B(255 * (1 - m_currentTime / (timeTotal)), 255 * m_currentTime / (timeTotal), 50)); m_timer->setPercentage(m_currentTime * 100 / (timeTotal)); // hien thi phan thoi gian con lai } }
Ở trên có biến gameReady = false
khi init()
, khi người chơi đã sẵn sàng chơi, chạm vào màn hình thì game sẽ gán gameReady = true
trong hàm onTouchEnded()
như sau.
void StateGamePlay::onTouchEnded(cocos2d::Touch* touches, cocos2d::Event* event) { if (m_DialogBonusShow) // dang co thong bao thi kho xu ly return; if (gameReady == false) // neu game chua san sang thi se san sang { gameReady = true; drawPumpkins(); // ve trai bi ngo thu 2 de bat dau choi } }
2. drawScore(dt)
Lấy điểm hiện tại và vẽ ra màn hình.
void StateGamePlay::drawScore(float dt) { this->removeChildByTag(tagScoreBG); // xoa khỏi man hinh de ve cai moi this->removeChildByTag(tagScore); auto ScoreBG = Sprite::createWithSpriteFrameName(NameScoreBGGP); ScoreBG->setPosition(posScoreGP); ScoreBG->setTag(tagScoreBG); this->addChild(ScoreBG, zUi); auto strScore = CCString::createWithFormat("%d", Score); // tao bien string de gan diem so vao auto lbScore = Label::createWithTTF(Pumpkins_font90, strScore->getCString()); // tao label co gia tri la diem so lbScore->setTextColor(yellowColor4B); lbScore->setTag(tagScore); lbScore->setPosition(posScoreGP.x, posScoreGP.y - 5); this->addChild(lbScore, zUi); // dua ra man hinh }
3. drawTextGP()
Hàm này dùng để vẽ text hướng dẫn người chơi và sẽ được thực hiện như sau.
void StateGamePlay::drawTextGP() { scaleTextBegin++; // bien scale theo thời gian, dung de lam cho game sinh dong hon if (scaleTextBegin > 100) scaleTextBegin = 0; this->removeChildByTag(tagTextRem); this->removeChildByTag(tagTextBegin); if (gameReady == false) { auto textRemember = Label::createWithTTF(Pumpkins_font30, "Remember This Pumpkin"); textRemember->setTextColor(pinkColor4B); textRemember->setPosition(posTextRemember); textRemember->setTag(tagTextRem); this->addChild(textRemember, zUi); auto textBegin = Label::createWithTTF(Pumpkins_font30, "Tap To Begin"); // game chua san sang se hien text huong dan bat dau choi textBegin->setTextColor(pinkColor4B); textBegin->setScale(float(1.0f + scaleTextBegin / 100.0f)); // scale text theo thoi gian de gay su chu y textBegin->setPosition(posTextRemember.x, posTextRemember.y - 50); textBegin->setTag(tagTextBegin); this->addChild(textBegin, zUi); } else { auto textRemember = Label::createWithTTF(Pumpkins_font30, "Does The Current Pumpkin"); textRemember->setTextColor(pinkColor4B); textRemember->setPosition(posTextRemember); textRemember->setTag(tagTextRem); this->addChild(textRemember, zUi); auto textBegin = Label::createWithTTF(Pumpkins_font30, "Match The Previous One?"); textBegin->setTextColor(pinkColor4B); textBegin->setPosition(posTextRemember.x, posTextRemember.y - 50); textBegin->setTag(tagTextBegin); this->addChild(textBegin, zUi); if (gameTimeOut) // bien nay duoc active trong ham drawClock() khi het gio { if (this->getChildByTag(tagTimeOut) == NULL) { auto textTimeOut = Label::createWithTTF(Pumpkins_font90, "Time Out"); // hien thi text bao het gio textTimeOut->setTextColor(redColor4B); textTimeOut->setTag(tagTimeOut); textTimeOut->setPosition(posMidleScreen); textTimeOut->setScale(0.1); textTimeOut->runAction(ScaleTo::create(0.5f, 1.0f)); this->addChild(textTimeOut, zUi); } } } }
4. drawAnswer()
Hàm này có nhiệm vụ vẽ 2 câu trả lời là YES
và NO
để hỏi người chơi xem bí ngô hiện tại có giống quả bí ngô trước đó không, nếu giống thì trả lời YES
và khác thì trả lời NO
bằng cách nhấn chọn.
void StateGamePlay::drawAnswer() { if (gameReady) // game da san sang { if (this->getChildByTag(tagAnswer) == NULL) // kiem tra de chi ve cau tra loi 1 lan dau tien ma thoi { auto btNo = MenuItemImage::create(); btNo->setNormalImage(Sprite::createWithSpriteFrameName(NamebtNo)); btNo->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtNoS)); btNo->setCallback(CC_CALLBACK_0(StateGamePlay::AnswerNo, this)); // goi den ham AnswerNo khi nhan tra loi NO btNo->setPosition(posbtNo); auto btYes = MenuItemImage::create(); btYes->setNormalImage(Sprite::createWithSpriteFrameName(NamebtYes)); btYes->setSelectedImage(Sprite::createWithSpriteFrameName(NamebtYesS)); btYes->setCallback(CC_CALLBACK_0(StateGamePlay::AnswerYes, this)); // goi den ham AnswerYes khi nhan tra loi YES btYes->setPosition(posbtYes); auto answer = Menu::create(btNo, btYes, NULL); // dua vao menu answer->setPosition(Point::ZERO); answer->setTag(tagAnswer); this->addChild(answer, zUi); // dua menu ra man hinh } } }
AnswerNo()
void StateGamePlay::AnswerNo() { if (gameOver) // neu da ket thuc game thi khong vao nua return; if (!gameAnswer) // nguoi choi chon tra loi NO va cau tra loi NO la dung { Score++; // them diem cho nguoi choi checkAnswer(true); // chuyen den ham checkAnswer() voi gia tri la true dong nghia voi tra loi dung } else { checkAnswer(false); // nguoi choi tra loi NO nhung cau tra loi la dung, chuyen den ham checkAnswer voi gia tri false de ket thuc game } }
AnswerYes()
void StateGamePlay::AnswerYes() { if (gameOver) return; if (gameAnswer) { Score++; checkAnswer(true); } else { checkAnswer(false); } }
checkAnswer()
Hàm này có 2 giá trị để xử lý, đó là giá trị false
và true
, nếu giá trị là false
thì vẽ dấu check sai và kết thúc game, nếu giá trị đưa vào là true
thì vẽ dấu check đúng và tiếp tục đi đến câu kế tiếp. Đoạn code được thực hiện như sau.
void StateGamePlay::checkAnswer(bool ans) { if (ans && gameAnswer) // tra loi dung va cau tra loi la giong nhau { auto userdefault = UserDefault::sharedUserDefault(); int soundEnable = userdefault->getIntegerForKey(SoundEnable); if (soundEnable == soundOn) { auto audio = SimpleAudioEngine::getInstance(); audio->playEffect("sounds/right.wav", false); } auto right = Sprite::createWithSpriteFrameName(NamebtTrue); // tao ra dau check bao nguoi choi da tra loi dung right->setPosition(posbtYes); right->setScale(0.1f); right->runAction(Sequence::create(ScaleTo::create(0.5f,1.0f), CCCallFuncN::create(CC_CALLBACK_1(StateGamePlay::actionFinish,this)), NULL ) ); // thuc hien hanh dong scale tu 10% den 100% kich thuoc hinh anh trong thoi gian 0.5s, sau do se huy dau check. this->addChild(right, zUi); drawPumpkins(); // ve cau hoi tiep theo } else if (ans && !gameAnswer) // tra loi dung va cau tra loi la khac nhau { auto userdefault = UserDefault::sharedUserDefault(); int soundEnable = userdefault->getIntegerForKey(SoundEnable); if (soundEnable == soundOn) { auto audio = SimpleAudioEngine::getInstance(); audio->playEffect("sounds/right.wav", false); } auto right = Sprite::createWithSpriteFrameName(NamebtTrue); right->setPosition(posbtNo); right->setScale(0.1f); right->runAction(Sequence::create(ScaleTo::create(0.5f, 1.0f), CCCallFuncN::create(CC_CALLBACK_1(StateGamePlay::actionFinish, this)), NULL ) ); this->addChild(right, zUi); drawPumpkins(); } else if (!ans) // tra loi sai { auto userdefault = UserDefault::sharedUserDefault(); int soundEnable = userdefault->getIntegerForKey(SoundEnable); if (soundEnable == soundOn) { auto audio = SimpleAudioEngine::getInstance(); audio->playEffect("sounds/wrong.wav", false); } if (!gameTimeOut && gameAnswer) // tra loi sai khong phai do het gio, se hien thi dau check sai bao nguoi dung { auto wrong = Sprite::createWithSpriteFrameName(NamebtFalse); wrong->setPosition(posbtNo); wrong->setScale(0.1f); wrong->runAction(Sequence::create(ScaleTo::create(0.5f, 1.0f), CCCallFuncN::create(CC_CALLBACK_1(StateGamePlay::actionFinish, this)), NULL ) ); this->addChild(wrong, zUi); } else if (!gameTimeOut && !gameAnswer) // tra loi sai khong phai do het gio, se hien thi dau check sai bao nguoi dung { auto wrong = Sprite::createWithSpriteFrameName(NamebtFalse); wrong->setPosition(posbtYes); wrong->setScale(0.1f); wrong->runAction(Sequence::create(ScaleTo::create(0.5f, 1.0f), CCCallFuncN::create(CC_CALLBACK_1(StateGamePlay::actionFinish, this)), NULL ) ); this->addChild(wrong, zUi); } if (soundEnable == soundOn) { auto audio = SimpleAudioEngine::getInstance(); audio->playEffect("sounds/gameOver.wav", false); } gameOver = true; // ket thuc game this->runAction(Sequence::create(DelayTime::create(1.0f), CCCallFuncN::create(CC_CALLBACK_0(StateGamePlay::gotoEndGame, this)), NULL ) ); // thuc hien hanh dong chuyen sang state game play sau 1s } }
Phần chính của game đó là StateGamePlay được hướng dẫn hiện thực trong bài này, và cuối cùng sẽ là StateEndGame được hướng dẫn trong bài sau.