您好,登錄后才能下訂單哦!
游戲需要分享才能獲得快樂,想想你以前玩過的那些游戲,那些會是真正地存在你嬸嬸的腦海里?是獨自一人躲在被窩里酣戰PSP,還是和哥們在網吧一起開黑?是一個人單刷迅龍三連,還是和朋友聯機怒刷黃黑龍?
從來沒有孤獨的快樂,也從來沒有孤獨的游戲。
今天要做的就是一個非常簡單但有有點復雜的雙人游戲-Air Hockey。
通過這篇教程,你可以學到
1.cocos2d-x的生命周期;
2.如何讓游戲兼容各種屏幕;
3.如何處理多點觸控;
4.如何模擬斜碰...
還是老套路
終端進入 cocos2d-x-2.2/tools/project-creator/ ,執行
./create_project.py -project AirHockey -package com.maclab.airhockey -language cpp
在 /cocos2d-x-2.2/projects/AirHockey 中就有創建好的各平臺的工程模板。
修改proj.android 下build_native.sh,添加一行指定NDK_ROOT
在eclipse中導入proj.android 工程,記得不要勾Copy to Project into workspace.
如果之前未將 /cocos2d-x-2.2/cocos2dx/platform/android/java 導入,在這里要導入。
創建軟鏈接,終端進入 pro.android,執行命令:
ln -s ../Resources ./Resources
在Eclipse中刷新工程,Resources文件夾就出現了。
換一個狂拽酷炫點的圖標
將android工程中res文件夾下的icon.png換成這個就可以了。
在項目上右擊,run as->android application
一切順利的話工程就創建好了,如果沒法運行,檢查cocos2dx是否配置好。
這個問題上一篇教程直接略過了,這里來仔細分析下。
AirHockey文件夾下有一個Classes文件夾和幾個pro.*文件夾,Classes文件夾有兩個類,一個AppDelegate,似私有繼承自cocos2d::CCApplication,一個HelloWorldScence類,共有繼承自cocos2d::CCLayer。
大部分框架,基本上都可以分為兩部分:
1. 一個入口主類,它定義了整個應用程序的生命周期,并提供一些全局的資源
2. 一些繪制到屏幕上的“頁面”控件。
下面是AppDelegate.cpp的內容
#include "AppDelegate.h" #include "HelloWorldScene.h" USING_NS_CC; AppDelegate::AppDelegate() { } AppDelegate::~AppDelegate() { } bool AppDelegate::applicationDidFinishLaunching() { // 初始化導演類 CCDirector* pDirector = CCDirector::sharedDirector(); CCEGLView* pEGLView = CCEGLView::sharedOpenGLView(); pDirector->setOpenGLView(pEGLView); // 顯示狀態信息 pDirector->setDisplayStats(true); // 設置FPS. 默認是60fps pDirector->setAnimationInterval(1.0 / 60); //創建一個場景. 它會自動釋放內存 CCScene *pScene = HelloWorld::scene(); // 運行 pDirector->runWithScene(pScene); 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() { CCDirector::sharedDirector()->stopAnimation(); // 如果使用了SimpleAudioEngine,下面這行代碼的注釋請去掉 // SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic(); } //當游戲從掛起狀態回來的時候,方法被調用 void AppDelegate::applicationWillEnterForeground() { CCDirector::sharedDirector()->startAnimation(); // 如果使用了SimpleAudioEngine,下面這行代碼的注釋請去掉 // SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic(); }
下面是看linux和Android中怎樣啟動游戲的。
首先看pro.linux下的代碼,只有一個main.cpp
#include "../Classes/AppDelegate.h" #include "cocos2d.h" #include "CCEGLView.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string> USING_NS_CC; int main(int argc, char **argv) { // create the application instance AppDelegate app; CCEGLView* eglView = CCEGLView::sharedOpenGLView(); eglView->setFrameSize(800, 480); return CCApplication::sharedApplication()->run(); }
CCEGLView封裝了使用openGL作為顯示底層API的一個基本的窗體的創建和控制,在linux中,窗口用的是GLFW庫。
main中第一行生命了一個AppDelegate對象,第二行初始化一個CCEGLView指針,sharedOpenGLView中的內容如下:
CCEGLView* CCEGLView::sharedOpenGLView() { if (s_pEglView == NULL)//s_pEglView是一個CCEGLView指針,靜態成員變量 { s_pEglView = new CCEGLView(); if(!s_pEglView->Create())//main中調用時,會執行這一步 { delete s_pEglView; s_pEglView = NULL; } } return s_pEglView; }
我們再看看CCApplication::sharedApplication()->run()執行的又是什么東西。
int CCApplication::run() { PVRFrameEnableControlWindow(false); // Main message loop: MSG msg; LARGE_INTEGER nFreq; LARGE_INTEGER nLast; LARGE_INTEGER nNow; QueryPerformanceFrequency(&nFreq);//獲取當前系統頻率和計數 QueryPerformanceCounter(&nLast); // Initialize instance and cocos2d. if (!applicationDidFinishLaunching())//虛函數,調用子類的重載,這里也會設置一些顯示窗口的屬性 { return 0; } CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();獲取CCEGLView的單一實例 pMainWnd->centerWindow(); ShowWindow(pMainWnd->getHWnd(), SW_SHOW);//這里顯示窗口 while (1)//消息循環 { if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // 獲取當前的計數 QueryPerformanceCounter(&nNow); // 判斷時間流逝,是否該繪制下一幀 if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart) { nLast.QuadPart = nNow.QuadPart; CCDirector::sharedDirector()->mainLoop();//渲染場景(清除顯示設備,重繪場景) } else { Sleep(0); } continue; } if (WM_QUIT == msg.message)//獲取退出消息,跳出循環 { // Quit message loop. break; } // 處理Windows消息 if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; }
(1)首先先獲取當前系統的頻率和計數。這是一個很大的值,所以用了一個LARGE_INTEGER型變量來存儲。
(2)調用子類的applicationDidFinishLaunching(),執行進入程序后的一些初始化工作。
(3)獲取CCEGLView單例,顯示窗口。
(4)進入循環While(1),重繪每一幀的場景。
感興趣的繼續研究源碼...
可以總結出linux下的啟動流程:
再看Android平臺。
Android使用的是java,cocos2d-x使用的C++,這里其實是用C++開發android程序,需要的用到技術是JNI。(不知道JNI為何物的猛擊我)
看一下pro.android/jni/hellocpp/main.cpp
#include "AppDelegate.h" #include "cocos2d.h" #include "CCEventType.h" #include "platform/android/jni/JniHelper.h" #include <jni.h> #include <android/log.h> #define LOG_TAG "main" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) using namespace cocos2d; extern "C" { jint JNI_OnLoad(JavaVM *vm, void *reserved) { JniHelper::setJavaVM(vm); return JNI_VERSION_1_4; } void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h) { if (!CCDirector::sharedDirector()->getOpenGLView()) { CCEGLView *view = CCEGLView::sharedOpenGLView(); view->setFrameSize(w, h); AppDelegate *pAppDelegate = new AppDelegate(); CCApplication::sharedApplication()->run(); } else { ccGLInvalidateStateCache(); CCShaderCache::sharedShaderCache()->reloadDefaultShaders(); ccDrawInit(); CCTextureCache::reloadAllTextures(); CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND, NULL); CCDirector::sharedDirector()->setGLDefaultValues(); } } }
(1)JNI_OnLoad,這個函數主要是用來告訴Android VM當前使用的是什么版本是Jni,如果不提供此函數,則默認使用Jni1.1版本。這個函數在加載交叉編譯的庫的時候,就會執行。
(2)Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit,這個函數很明顯就是運行一個cocos2d-x的應用實例了,這和Win32是一樣的,當然它多了一個openGlView的檢測。一旦調用了它那么cocos2d-x游戲啟動。
接下來再看看它們是在哪里被調用的。
看一下Android工程中的主類:
package com.maclab.airhockey; import org.cocos2dx.lib.Cocos2dxActivity; import org.cocos2dx.lib.Cocos2dxGLSurfaceView; import android.os.Bundle; public class AirHockey extends Cocos2dxActivity{ protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); } public Cocos2dxGLSurfaceView onCreateView() { Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this); // AirHockey should create stencil buffer glSurfaceView.setEGLConfigChooser(5, 6, 5, 0, 16, 8); return glSurfaceView; } static { System.loadLibrary("cocos2dcpp"); } }
public void init() { // FrameLayout ViewGroup.LayoutParams framelayout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); FrameLayout framelayout = new FrameLayout(this); framelayout.setLayoutParams(framelayout_params); // Cocos2dxEditText layout ViewGroup.LayoutParams edittext_layout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); Cocos2dxEditText edittext = new Cocos2dxEditText(this); edittext.setLayoutParams(edittext_layout_params); // ...add to FrameLayout framelayout.addView(edittext); // Cocos2dxGLSurfaceView this.mGLSurfaceView = this.onCreateView(); // ...add to FrameLayout framelayout.addView(this.mGLSurfaceView); // Switch to supported OpenGL (ARGB888) mode on emulator if (isAndroidEmulator()) this.mGLSurfaceView.setEGLConfigChooser(8 , 8, 8, 8, 16, 0); this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer()); this.mGLSurfaceView.setCocos2dxEditText(edittext); // Set framelayout as the content view setContentView(framelayout); }
而NativeIint方法在則在Cocos2dxRenderer中被調用:
@Override public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) { Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight); this.mLastTickInNanoSeconds = System.nanoTime(); }
最后總結層次調用關系如下:
是不是有點頭暈~_~,沒關系,別忘了我們最初的目標 - 做狂拽炫酷的游戲!
要讓游戲在不同分辨率下都獲得良好的用戶體驗,應該滿足這幾個要求:
背景圖填滿整個畫面,不出現黑邊;
背景圖的主要內容都顯示在屏幕上,盡可能少的裁剪圖片(減少超出屏幕可視區域的部分);
如果背景圖需要放大,盡可能減小放大的比例,避免放大后出現明顯的模糊;
用戶界面的文字標簽、按鈕在任何分辨率下都應該完整顯示,并且容易交互。
相信很多游戲開發者開發者選擇ios作為首選開發平臺的一個主要原因就是IOS沒有坑爹的碎片化,系統沒有碎片化,屏幕沒有碎片化, 機器性能沒有碎片化....但到了Android,一切就成了噩夢。
一個解決屏幕碎片化的方法就是為不同的屏幕分辨率準備不同的資源文件。另一種方法是按比例縮放。
縮放比例的做法比較方便,在AppDelegate::applicationDidFinishLaunching()中添加代碼:
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(800, 480,kResolutionExactFit);
在800*480窗口下,正常現實的效果如下:
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(200, 480,kResolutionExactFit);
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(200, 480,kResolutionShowAll);
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(200, 480,kResolutionNoBorder);
如果是采用準備多套資源文件的方法,則在Resources文件夾中創建img/xhdip和img/hdip文件夾。
將下載好的圖片放到對應的文件夾中,
還是修改AppDelegate.cpp的代碼。
首先是添加一個結構體,在定義幾個static變量:
typedef struct tagResource { cocos2d::CCSize size; char directory[100]; }Resource; static Resource smallResource = { cocos2d::CCSizeMake(1024, 600), "img/hdip" }; static Resource mediumResource = { cocos2d::CCSizeMake(1280, 720), "img/xhdip" }; static cocos2d::CCSize designResolutionSize = cocos2d::CCSizeMake(1024, 600);
bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector* pDirector = CCDirector::sharedDirector(); CCEGLView* pEGLView = CCEGLView::sharedOpenGLView(); pDirector->setOpenGLView(pEGLView); //CCEGLView::sharedOpenGLView()->setDesignResolutionSize(200, 480,kResolutionShowAll); CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, kResolutionNoBorder); CCSize frameSize = pEGLView->getFrameSize(); std::vector<std::string> resDirOrders; // if the frame's height is larger than the height of medium resource size, select large resource. if (frameSize.height > mediumResource.size.height) { pDirector->setContentScaleFactor(mediumResource.size.height/designResolutionSize.height); resDirOrders.push_back(mediumResource.directory); } // if the frame's height is larger than the height of small resource size, select medium resource. else { pDirector->setContentScaleFactor(smallResource.size.height/designResolutionSize.height); resDirOrders.push_back(smallResource.directory); } resDirOrders.push_back("sounds"); resDirOrders.push_back("/"); CCFileUtils::sharedFileUtils()->setSearchResolutionsOrder(resDirOrders); // turn on display FPS pDirector->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this pDirector->setAnimationInterval(1.0 / 60); // create a scene. it's an autorelease object CCScene *pScene = HelloWorld::scene(); // run pDirector->runWithScene(pScene); return true; }
預加載音效
將資源中的*.wav文件都放到工程的Resources文件夾中。
首先修改pro.linux 下的MakeFile,
INCLUDES = -I.. -I../Classes\ -I$(COCOS_ROOT)/CocosDenshion/include SHAREDLIBS += -lcocos2d -lcocosdenshion
#include "SimpleAudioEngine.h" using namespace CocosDenshion ;
//Setting sounds std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename("hit.wav"); SimpleAudioEngine::sharedEngine()->preloadEffect(fullPath.c_str()); fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename("score.wav"); SimpleAudioEngine::sharedEngine()->preloadEffect(fullPath.c_str());
首先看一下何為Sprite.
Spirite是渲染框架中最小的一個單元了,游戲精靈是游戲制作中非常重要的一個概念。它是游戲內容最好的呈現。游戲開發者主要的控制對象也會是精靈。精靈對象可以是游戲畫面中的任何一對象,可以是游戲中的主角,也可以是汽車,或者是一個樹,一個高富帥,一個窮屌絲,哪怕是一片樹葉也可以是一個精靈對象。從技術角度來看,精靈對象就是一個可以不斷變化的圖片,而其本身也具備了一些特殊的屬性和方法,比如紋理、尺寸、翻轉、透明以及角度的改變。精靈作為游戲的重要元素,最后也會被開發者新賦予一些新的邏輯或者物理屬性。
下面是我們繼承CCSprite的GameSprite類。
gamesprite.h
#ifndef GAMESPRITE_H #define GAMESPRITE_H #include "cocos2d.h" using namespace cocos2d; class GameSprite : public CCSprite { public: CC_SYNTHESIZE(CCPoint, _nextPosition, NextPosition); CC_SYNTHESIZE(CCPoint, _vector, Vector); CC_SYNTHESIZE(CCTouch *, _touch, Touch); GameSprite(void); ~GameSprite(void); static GameSprite* gameSpriteWithFile(const char *pszFileName); virtual void setPosition(const CCPoint& pos); float radius(){return getTexture()->getContentSize().width * 0.5f;} }; #endif // GAMESPRITE_H
gamesprite.cpp
#include "gamesprite.h" GameSprite::GameSprite(void) { _vector = ccp(0,0); } GameSprite::~GameSprite(void) { } GameSprite* GameSprite::gameSpriteWithFile(const char *pszFileName) { GameSprite * sprite = new GameSprite(); if (sprite && sprite->initWithFile(pszFileName)) { sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return NULL; } void GameSprite::setPosition(const CCPoint& pos) { CCSprite::setPosition(pos); if (!_nextPosition.equals(pos)) { _nextPosition = pos; } }
gameSpriteWithFIle用于從文件創建一個自動釋放內存的Sprite;setPosition用于改變Sprite位置,同時更新_nextPosition; radius這個inline函數用于返回物體的半徑,也就是sprite寬度的一半。
修改proj.linux/MakeFile,將新添加的 cpp 索引進來。
SOURCES = main.cpp \ ../Classes/AppDelegate.cpp \ ../Classes/HelloWorldScene.cpp\ ../Classes/gamesprite.cpp
android版本的編譯需要修改proj.android/jni/Android.mk
LOCAL_SRC_FILES := hellocpp/main.cpp \ ../../Classes/AppDelegate.cpp \ ../../Classes/HelloWorldScene.cpp\ ../../Classes/gamesprite.cpp
場景的搭建都在HelloWorld::init()中處理。
// on "init" you need to initialize your instance bool HelloWorld::init() { _player1Score = 0; _player2Score = 0; _screenSize = CCDirector::sharedDirector()->getWinSize(); ////////////////////////////// // 1. super init first if ( !CCLayer::init() ) { return false; } CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize(); CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin(); // add court background CCSprite* court = CCSprite::create("court.png"); // position court on the center of the screen court->setPosition(ccp(visibleSize.width* 0.5 + origin.x, visibleSize.height* 0.5 + origin.y)); // add court as a child to this layer this->addChild(court, 0); _player1 = GameSprite::gameSpriteWithFile("mallet.png"); _player1->setPosition(ccp(_player1->radius() * 2,_screenSize.height * 0.5) ); this->addChild(_player1,1); _player2 = GameSprite::gameSpriteWithFile("mallet.png"); _player2->setPosition(ccp(_screenSize.width - _player1->radius() * 2, _screenSize.height * 0.5)); this->addChild(_player2,1); _ball = GameSprite::gameSpriteWithFile("puck.png"); _ball->setPosition(ccp( _screenSize.width* 0.5 - 2 * _ball->radius(), _screenSize.height * 0.5)); this->addChild(_ball,1); _players = CCArray::create(_player1, _player2, NULL); _players->retain(); _player1ScoreLabel = CCLabelTTF::create("0", "Arial", 60); _player1ScoreLabel->setPosition(ccp( _screenSize.width * 0.5 - 80, _screenSize.height - 60)); this->addChild(_player1ScoreLabel, 2); _player2ScoreLabel = CCLabelTTF::create("0", "Arial", 60); _player2ScoreLabel->setPosition(ccp(_screenSize.width * 0.5 + 80, _screenSize.height - 60)); this->addChild(_player2ScoreLabel, 2); //listen for touches this->setTouchEnabled(true); //create main loop this->schedule(schedule_selector(HelloWorld::update)); return true; }
運行之后的效果:
Android:、
Linux:
在析構函數中釋放之前開辟的內存:
HelloWorld::~HelloWorld() { CC_SAFE_RELEASE(_players); }
一步步來,首先是處理多點觸控。
在HelloWorld.h中添加三個關于主控的函數:
virtual void ccTouchesBegan(CCSet* pTouches, CCEvent*event); virtual void ccTouchesMoved(CCSet* pTouches, CCEvent*event); virtual void ccTouchesEnded(CCSet* pTouches, CCEvent*event);
這里主要要處理的問題就是手指和GamePlayer對應問題,用下面的代碼就可以解決。
CCSetIterator i; CCTouch* touch; CCPoint tap; GameSprite * player; for( i = pTouches->begin(); i != pTouches->end(); i++) { touch = (CCTouch*) (*i); if(touch) { tap = touch->getLocation(); for (int p = 0; p < 2; p++) { player = (GameSprite *) _players->objectAtIndex(p); //Do some thing. } } }
前四行聲名了4個局部變量,i是CCSet的迭代器,touch是指向CCTouch的指針,CCPoint用于記錄觸碰的位置,player是一個GameSpirit的指針。
第五行遍歷所有的觸控點,接下來對每個觸控點進行處理。第六行將迭代器轉換為CCTouch指針,接下來對于當前的觸控點,再遍歷所有_players中的player,然后進行處理。
三個關于觸控的函數的處理流程相同,不同的是做的工作不一樣。
ccTouchesBegan中要做的是將觸控點與對應的player連接起來,ccTouchesMoved要做的是更新player的位置,ccTouchesEnded要做的是清空觸控信息。具體的實現如下:
void HelloWorld::ccTouchesBegan(CCSet *pTouches, CCEvent *event) { CCSetIterator i; CCTouch* touch; CCPoint tap; GameSprite * player; for( i = pTouches->begin(); i != pTouches->end(); i++) { touch = (CCTouch*) (*i); if(touch) { tap = touch->getLocation(); for (int p = 0; p < 2; p++) { player = (GameSprite *) _players->objectAtIndex(p); if (player->boundingBox().containsPoint(tap)) { player->setTouch(touch); } } } } } void HelloWorld::ccTouchesMoved(CCSet *pTouches, CCEvent *event) { CCSetIterator i; CCTouch* touch; CCPoint tap; GameSprite * player; for( i = pTouches->begin(); i != pTouches->end(); i++) { touch = (CCTouch*) (*i); if(touch) { tap = touch->getLocation(); for (int p = 0; p < 2; p++) { player = (GameSprite *) _players->objectAtIndex(p); if (player->getTouch() != NULL && player->getTouch() == touch) { CCPoint nextPosition = tap; //keep player inside screen if (nextPosition.x < player->radius()) nextPosition.x = player->radius(); if (nextPosition.x > _screenSize.width - player->radius()) nextPosition.x = _screenSize.width - player->radius(); if (nextPosition.y < player->radius()) nextPosition.y = player->radius(); if (nextPosition.y > _screenSize.height - player->radius()) nextPosition.y = _screenSize.height - player->radius(); //keep player inside its court if (player->getPositionX() < _screenSize.width *0.5f) { if (nextPosition.x > _screenSize.width * 0.5 - player->radius()) { nextPosition.x = _screenSize.width * 0.5 - player->radius(); } } else{ if (nextPosition.x < _screenSize.width * 0.5 + player->radius()) { nextPosition.x = _screenSize.width * 0.5 + player->radius(); } } player->setNextPosition(nextPosition); player->setVector(ccp(tap.x - player->getPositionX(), tap.y - player->getPositionY())); } } } } } void HelloWorld::ccTouchesEnded(CCSet *pTouches, CCEvent *event) { CCSetIterator i; CCTouch* touch; CCPoint tap; GameSprite * player; for( i = pTouches->begin(); i != pTouches->end(); i++) { touch = (CCTouch*) (*i); if(touch) { tap = touch->getLocation(); for (int p = 0; p < 2; p++) { player = (GameSprite *) _players->objectAtIndex(p); if (player->getTouch() != NULL && player->getTouch() == touch) { player->setTouch(NULL); player->setVector(ccp(0,0)); } } } } }
因為根本就沒有在游戲中更新player的位置啊。在HelloWorld::update中添加下面的代碼:
//move pieces to next position _player1->setPosition(_player1->getNextPosition()); _player2->setPosition(_player2->getNextPosition()); _ball->setPosition(_ball->getNextPosition());
這里的碰撞檢測要處理兩個方面,一個是player和小球的碰撞,一個是墻壁和小球的碰撞。player和小球的碰撞會改變小球的運動路線,墻壁和小球碰撞,要么反彈,要么得分。
player和小球碰撞要注意一個“陷入”的問題,就是在檢測的時候要預測一下小球的走位,不然小球和player就會發生重疊。
還有就是小球和player發生碰撞的情況屬于斜碰,在計算的時候,首先通過player和小球的速度計算出一個附加在小球上的力,然后再計算出一個角度,最后設置小球的速度。
和墻壁的碰撞就簡單一些了,也要注意陷入的問題。然后就是得分的話要更新記分牌。
最后別忘了在碰撞的時候添加音效。完整的update函數如下:
void HelloWorld::update(float dt) { CCPoint ballNextPosition = _ball->getNextPosition(); CCPoint ballVector = _ball->getVector(); ballVector = ccpMult(ballVector, 0.98f); ballNextPosition.x += ballVector.x; ballNextPosition.y += ballVector.y; //test for puck and mallet collision float squared_radii = pow(_player1->radius() + _ball->radius(), 2); GameSprite * player; CCPoint playerNextPosition; CCPoint playerVector; for (int p = 0; p < 2; p++) { player = (GameSprite *) _players->objectAtIndex(p); playerNextPosition = player->getNextPosition(); playerVector = player->getVector(); float diffx = ballNextPosition.x - player->getPositionX(); float diffy = ballNextPosition.y - player->getPositionY(); float distance1 = pow(diffx, 2) + pow(diffy, 2); float distance2 = pow(_ball->getPositionX() - playerNextPosition.x, 2) + pow(_ball->getPositionY() - playerNextPosition.y, 2); if (distance1 <= squared_radii || distance2 <= squared_radii) { float mag_ball = pow(ballVector.x, 2) + pow(ballVector.y, 2); float mag_player = pow (playerVector.x, 2) + pow (playerVector.y, 2); float force = sqrt(mag_ball + mag_player); float angle = atan2(diffy, diffx); ballVector.x = force * cos(angle); ballVector.y = force * sin(angle); ballNextPosition.x = playerNextPosition.x + (player->radius() + _ball->radius() + force) * cos(angle); ballNextPosition.y = playerNextPosition.y + (player->radius() + _ball->radius() + force) * sin(angle); SimpleAudioEngine::sharedEngine()->playEffect("score.wav"); } } //check collision of ball and sides if (ballNextPosition.x < _ball->radius()) { ballNextPosition.x = _ball->radius(); ballVector.x *= -0.8f; SimpleAudioEngine::sharedEngine()->playEffect("hit.wav"); } if (ballNextPosition.x > _screenSize.width - _ball->radius()) { ballNextPosition.x = _screenSize.width - _ball->radius(); ballVector.x *= -0.8f; SimpleAudioEngine::sharedEngine()->playEffect("hit.wav"); } //ball and top of the court if (ballNextPosition.y > _screenSize.height - _ball->radius()) { if (_ball->getPosition().x < _screenSize.width * 0.5f - 40 * 0.5f || _ball->getPosition().x > _screenSize.width * 0.5f + 40 * 0.5f) { ballNextPosition.y = _screenSize.height - _ball->radius(); ballVector.y *= -0.8f; SimpleAudioEngine::sharedEngine()->playEffect("hit.wav"); } } //ball and bottom of the court if (ballNextPosition.y < _ball->radius() ) { if (_ball->getPosition().x < _screenSize.width * 0.5f - 40 * 0.5f || _ball->getPosition().x > _screenSize.width * 0.5f + 40 * 0.5f) { ballNextPosition.y = _ball->radius(); ballVector.y *= -0.8f; SimpleAudioEngine::sharedEngine()->playEffect("hit.wav"); } } //finally, after all checks, update ball's vector and next position _ball->setVector(ballVector); _ball->setNextPosition(ballNextPosition); //check for goals! if (ballNextPosition.x < 50) { this->playerScore(2); } if (ballNextPosition.x > _screenSize.width-50) { this->playerScore(1); } //move pieces to next position _player1->setPosition(_player1->getNextPosition()); _player2->setPosition(_player2->getNextPosition()); _ball->setPosition(_ball->getNextPosition()); }
void HelloWorld::playerScore (int player) { SimpleAudioEngine::sharedEngine()->playEffect("score.wav"); _ball->setVector(CCPointZero); char score_buffer[10]; //if player 1 scored... if (player == 1) { _player1Score++; sprintf(score_buffer,"%i", _player1Score); _player1ScoreLabel->setString(score_buffer); //move ball to player 2 court _ball->setNextPosition(ccp( _screenSize.width* 0.5 + 2 * _ball->radius(), _screenSize.height * 0.5)); //if player 2 scored... } else { _player2Score++; sprintf(score_buffer,"%i", _player2Score); _player2ScoreLabel->setString(score_buffer); //move ball to player 1 court _ball->setNextPosition(ccp( _screenSize.width* 0.5 - 2 * _ball->radius(), _screenSize.height * 0.5)); } //move players to original position _player1->setPosition(ccp(_player1->radius() * 2,_screenSize.height * 0.5)); _player2->setPosition(ccp(_screenSize.width - _player1->radius() * 2, _screenSize.height * 0.5)); //clear current touches _player1->setTouch(NULL); _player2->setTouch(NULL); }
【玩轉cocos2d-x之三】cocos2d-x游戲是怎么跑起來的 - http://blog.csdn.net/jackystudio/article/details/12554167
【玩轉cocos2d-x之四】cocos2d-x怎么實現跨平臺 - http://blog.csdn.net/jackystudio/article/details/12610287
Cocos2d-x 2.0 自適應多種分辨率 - http://dualface.github.io/blog/2012/08/17/cocos2d-x-2-dot-0-multi-resolution/
Cocos2d-x by Example Beginner's Guide
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。