chapter8 實驗手冊
第八章 碰撞檢測與運動模擬基于Box2D的游戲?qū)嵗ㄒ唬┗A知識物理模擬、精靈的繪制與移動、觸摸事件的應用(1) 繪制游戲界面(2) 通過單點觸摸屏幕,球桿瞄準目標(3) 設置能量條(觸屏檢測)(4) 球桿擊球(5) 碰撞檢測(物理模擬)(二)游戲配置文件游戲需要新建一個Config.h的頭文件,此文件用來定義游戲中需要的參數(shù)。#include "cocos2d.h"USING_NS_CC;using namespace cocos2d;#define PTM_RATIO 32 / 定義32像素代表1米static const int SLIDER_BG_TAG = 1000;static const int SLIDER_TAG = 1001;static const int DASH_LINE_TAG = 1002;static const int MAIN_BILLIARDS_TAG = 1003;static const int AIM_ICON_TAG = 1004;static const int AIM_GAN_TAG = 1005;static const int MAX_SPEED = 2600;static const std:string ball5 = "Chapter07/Ball/3.png","Chapter07/Ball/4.png","Chapter07/Ball/5.png","Chapter07/Ball/6.png","Chapter07/Ball/10.png"static const Vec2 ballPos = Vec2(300,500),Vec2(500,300),Vec2(100,600),Vec2(600,100),Vec2(800,500);(三)物理場景的繪制由PhysicsScene.h類實現(xiàn),這里具體給出其實現(xiàn),定義均在實現(xiàn)文件中實現(xiàn)了。PhysicsScene.cpp#include "PhysicsScene.h"PhysicsScene:PhysicsScene()PhysicsScene:PhysicsScene()bool PhysicsScene:init()if (!Layer:init()return false;/ physics sceneSize visibleSize = Director:getInstance()->getVisibleSize();Vec2 origin = Director:getInstance()->getVisibleOrigin();/ 定義世界的邊界auto body = PhysicsBody:createEdgeBox(visibleSize,PHYSICSBODY_MATERIAL_DEFAULT,5.0f); auto edgeNode = Node:create(); edgeNode->setPosition(Vec2(visibleSize.width/2,visibleSize.height/2); edgeNode->setPhysicsBody(body); this->addChild(edgeNode); / 屏幕觸摸auto listener = EventListenerTouchOneByOne:create();listener->onTouchBegan = CC_CALLBACK_2(PhysicsScene:onTouchBegan, this);Director:getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);return true;Scene* PhysicsScene:createScene()/ 創(chuàng)建物理世界auto scene = Scene:createWithPhysics();/ 繪制調(diào)試遮罩/ scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld:DEBUGDRAW_ALL);auto layer = PhysicsScene:create();scene->addChild(layer);return scene;bool PhysicsScene:onTouchBegan(Touch *touch, Event *unused_event)Vec2 location = touch->getLocation();addNewSpriteAtPosition(location);return false;void PhysicsScene:addNewSpriteAtPosition(Vec2 p)int picpath = rand() % 11;auto sp = Sprite:create(StringUtils:format("Chapter07/Ball/%d.png",picpath); sp->setTag(1);auto body = PhysicsBody:createCircle(sp->getContentSize().width / 2);/ auto body = PhysicsBody:createBox(sp->getContentSize(); sp->setPhysicsBody(body);sp->setPosition(p);this->addChild(sp);(四)物理物體的封裝,桌球的實現(xiàn)由BilliardSprite.h類實現(xiàn),這里具體給出其實現(xiàn),定義均在實現(xiàn)文件中實現(xiàn)了。BilliardSprite.cpp#include "BilliardSprite.h"BilliardSprite:BilliardSprite()void BilliardSprite:update(float t)_sprite->setPosition(Vec2(_body->GetPosition().x * PTM_RATIO,_body->GetPosition().y * PTM_RATIO);_sprite->setRotation(-1 * CC_RADIANS_TO_DEGREES(_body->GetAngle();BilliardSprite:BilliardSprite()if (_name != NULL)_name = NULL;BilliardSprite* BilliardSprite:create(char* name, bool isStatic, bool isBullet, b2World* world, std:string picPath, Vec2 position, float density /*= 1.0*/, float friction /*= 0.1*/, float restitution /*= 0.78 */)BilliardSprite *pRet = new(std:nothrow) BilliardSprite();if (pRet && pRet->init(name, isStatic, isBullet, world, picPath, position, density, friction, restitution) pRet->autorelease();return pRet;else delete pRet;pRet = NULL;return NULL;bool BilliardSprite:init(char* name, bool isStatic, bool isBullet, b2World* world, std:string picPath, Vec2 position, float density /*= 1.0*/, float friction /*= 0.1*/, float restitution /*= 0.78*/)if (!Sprite:init()return false;_name = name;/ 創(chuàng)建精靈_sprite = Sprite:create(picPath);this->addChild(_sprite);/ bodyDefb2BodyDef bodyDef;if (!isStatic)bodyDef.type = b2_dynamicBody;if (isBullet)bodyDef.bullet = true;bodyDef.position.Set(position.x / PTM_RATIO,position.y / PTM_RATIO);/ 創(chuàng)建body_body = world->CreateBody(&bodyDef); _body->SetUserData(this);b2CircleShape circle;circle.m_radius = (_sprite->getContentSize().width / 2 ) / PTM_RATIO;if (isStatic)/ 靜態(tài)_body->CreateFixture(&circle,0.0f); else/ 動態(tài)b2FixtureDef fixtureDef;fixtureDef.shape = &circle;fixtureDef.density = density;fixtureDef.friction = friction;fixtureDef.restitution = restitution;_body->CreateFixture(&fixtureDef);this->scheduleUpdate();return true;(五)碰撞監(jiān)聽由GameContactListener.h類實現(xiàn),這里具體給出其實現(xiàn),定義均在實現(xiàn)文件中實現(xiàn)了。GameContactListener.cpp#include "GameContactListener.h"#include "BilliardSprite.h"GameContactListener:GameContactListener()GameContactListener:GameContactListener()void GameContactListener:BeginContact(b2Contact*contact)CCLOG("%s",static_cast<BilliardSprite*>(contact->GetFixtureA()->GetBody()->GetUserData()->getName();CCLOG("%s",static_cast<BilliardSprite*>(contact->GetFixtureB()->GetBody()->GetUserData()->getName();void GameContactListener:EndContact(b2Contact* contact)/ handle end eventvoid GameContactListener:PreSolve(b2Contact* contact,const b2Manifold* oldManifold)/ handle pre-solve eventvoid GameContactListener:PostSolve(b2Contact* contact,const b2ContactImpulse* impulse)/ handle post-solve eventvoid GameContactListener:deleteBody()(六)初始化物理世界PhysicsBox2dScene.cpp文件中void PhysicsBox2dScene:initPhysics()Size s = Director:getInstance()->getVisibleSize();_ContactListener = new GameContactListener();/ gravityb2Vec2 gravity;gravity.Set(0.0f, 0.0f);/ create world_world = new b2World(gravity);_world->SetContactListener(_ContactListener); / 碰撞/ allow sleep_world->SetAllowSleeping(true);/ physics test 避免碰撞檢測不連續(xù)產(chǎn)生碰撞穿透_world->SetContinuousPhysics(true);/ groundb2BodyDef groundBodyDef;/ left downgroundBodyDef.position.Set(0, 0);/ create groundb2Body* groundBody = _world->CreateBody(&groundBodyDef);groundBody->SetUserData("boundary");/ shapeb2EdgeShape groundBox;/ downgroundBox.Set(b2Vec2(0,0),b2Vec2(s.width/PTM_RATIO,0);groundBody->CreateFixture(&groundBox,0);/ upgroundBox.Set(b2Vec2(0,s.height/PTM_RATIO),b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO);groundBody->CreateFixture(&groundBox,0);/ leftgroundBox.Set(b2Vec2(0,s.height/PTM_RATIO),b2Vec2(0,0);groundBody->CreateFixture(&groundBox,0);/ rightgroundBox.Set(b2Vec2(s.width - 60)/PTM_RATIO,s.height/PTM_RATIO),b2Vec2(s.width - 60)/PTM_RATIO,0);groundBody->CreateFixture(&groundBox,0);(七)初始化場景初始化:PhysicsBox2dScene:PhysicsBox2dScene()_world = NULL;_aim = false;_angle = 0.0f;_curPower = 0.0f;_powerEnd = false;_sliderSpeed = 0.0f;_aimPos = Vec2(-1,-1);_canAim = true;在PhysicsBox2dScene.cpp文件中init函數(shù)中初始化場景:1、繪制能量條/ slider bgauto slider_bg = Sprite:create("Chapter07/slider/bg-f.png");slider_bg->setPosition(Vec2(visibleSize.width - 30, visibleSize.height/2);this->addChild(slider_bg, 2, SLIDER_BG_TAG);/ init power slider rect_powerRect = Rect(visibleSize.width - 58, 0, 58,visibleSize.height);/ sliderauto slider = Sprite:create("Chapter07/slider/fg-f.png");this->addChild(slider, 3, SLIDER_TAG);slider->setVisible(false);/ slider leftauto slider_left = Sprite:create("Chapter07/slider/bg-left.png");slider_left->setPosition(Vec2(visibleSize.width - 60, visibleSize.height/2);this->addChild(slider_left, 2);2、繪制非物理世界邊界/ slider leftauto slider_left = Sprite:create("Chapter07/slider/bg-left.png");slider_left->setPosition(Vec2(visibleSize.width - 60, visibleSize.height/2);this->addChild(slider_left, 2);/ boundary leftauto boundary_left = Sprite:create("Chapter07/slider/bg-left.png");boundary_left->setPosition(Vec2(3, visibleSize.height/2);this->addChild(boundary_left);/ boundary rightauto boundary_right = Sprite:create("Chapter07/slider/bg-left.png");boundary_right->setPosition(Vec2(visibleSize.width - 2, visibleSize.height/2);this->addChild(boundary_right, 2);3、初始化瞄準區(qū)域(物理世界大?。? init aim rect_aimRect = Rect(0, 0, visibleSize.width - 60, visibleSize.height);4、初始化物理世界/ init physics worldthis->initPhysics();5、初始化桌球/ 白球_mainBilliards = BilliardSprite:create("main", false, true, _world, "Chapter07/Ball/8.png", Vec2(200,visibleSize.height/2);this->addChild(_mainBilliards, 10, MAIN_BILLIARDS_TAG);for (int i=0;i<5;i+)auto ballSprite = BilliardSprite:create("ball",false, false, _world, balli, ballPosi);this->addChild(ballSprite);6、初始化添加瞄準線、瞄準圖片和球桿/ dash lineauto dashLine = Sprite:create("Chapter07/aim/dot_line.png");this->addChild(dashLine, 2, DASH_LINE_TAG);dashLine->setAnchorPoint(Vec2(0.5, 0);dashLine->setVisible(false);/ aim iconauto aim_icon = Sprite:create("Chapter07/aim/aim.png");this->addChild(aim_icon, 10, AIM_ICON_TAG);aim_icon->setScale(0.4f);aim_icon->setVisible(false);/ 球桿auto aim_gan = Sprite:create("Chapter07/aim/gan-f.png");this->addChild(aim_gan, 10, AIM_GAN_TAG);aim_gan->setAnchorPoint(Vec2(0.5, 0);aim_gan->setScale(0.6f);aim_gan->setVisible(false);(八)能量條的控制1、能量條區(qū)域觸摸響應事件的設置設置觸摸開始時響應的事件:bool PhysicsBox2dScene:onTouchBegan(Touch *touch, Event *unused_event)if (_aimRect.containsPoint(touch->getLocation() && _canAim)_aim = true;auto start_p = changePos(_mainBilliards->getPosition();auto end_p = touch->getLocation();updateLine(start_p, end_p);_powerEnd = false;return true;設置觸摸移動時響應的事件,分為兩種情況,一種是當觸摸移動的區(qū)域的是非物理世界時,改變的是能量條的狀態(tài);另一種情況是當觸摸移動的區(qū)域的是物理世界區(qū)域時,改變的是球桿瞄準的方向:void PhysicsBox2dScene:onTouchMoved(Touch *touch, Event *unused_event)auto slider_bg = (Sprite*)getChildByTag(SLIDER_BG_TAG);if (_powerRect.containsPoint(touch->getStartLocation() && _aim)_curPower = touch->getStartLocation().y - touch->getLocation().y;if (_curPower<0) _curPower = 0;else if( _curPower > slider_bg->getContentSize().height )_curPower = slider_bg->getContentSize().height;updatePowerSlider(_curPower);else if (_aimRect.containsPoint(touch->getStartLocation() && _canAim)if (_aimRect.containsPoint(touch->getLocation()auto start_p = changePos(_mainBilliards->getPosition();auto end_p = touch->getLocation();updateLine(start_p, end_p);設置觸摸結(jié)束時響應的事件:void PhysicsBox2dScene:onTouchEnded(Touch *touch, Event *unused_event)if (_powerRect.containsPoint(touch->getLocation() && _aim)auto slider_bg = (Sprite*)getChildByTag(SLIDER_BG_TAG);_sliderSpeed = _curPower / slider_bg->getContentSize().height * 50.0f;/ 擊球auto start_p = changePos(_mainBilliards->getPosition();auto end_p = _aimPos;auto v = changePos(end_p - start_p).getNormalized();float q = _curPower / slider_bg->getContentSize().height;/ 設置白球速度_mainBilliards->SetLinearVelocity(b2Vec2(v.x * q * MAX_SPEED, v.y * q * MAX_SPEED);auto dash_line = (Sprite*)getChildByTag(DASH_LINE_TAG);dash_line->setVisible(false);auto aim_icon = (Sprite*)getChildByTag(AIM_ICON_TAG);aim_icon->setVisible(false);auto aim_gan = (Sprite*)getChildByTag(AIM_GAN_TAG);aim_gan->setVisible(false);/ 擊球運動后,在所有物體都靜止之前無法再進行瞄準和擊球_canAim = false;_aim = false;_powerEnd = true; / 進度條可以消除2、為控制條添加監(jiān)聽在PhysicsBox2dScene.cpp文件中init函數(shù)中/ add touch listenerauto listener = EventListenerTouchOneByOne:create();listener->onTouchBegan = CC_CALLBACK_2(PhysicsBox2dScene:onTouchBegan, this);listener->onTouchMoved = CC_CALLBACK_2(PhysicsBox2dScene:onTouchMoved, this);listener->onTouchEnded = CC_CALLBACK_2(PhysicsBox2dScene:onTouchEnded, this);Director:getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener,this);(九)改變球桿方向b2Vec2 PhysicsBox2dScene:changePos(Vec2 pos)return b2Vec2(pos.x/PTM_RATIO, pos.y/PTM_RATIO);cocos2d:Vec2 PhysicsBox2dScene:changePos(b2Vec2 pos)return Vec2(pos.x*PTM_RATIO, pos.y*PTM_RATIO);(十)更新瞄準線的位置和球桿位置void PhysicsBox2dScene:updateLine(Vec2 start_p, Vec2 end_p)_aimPos = end_p;/ update line_angle = (_aimPos - start_p).getNormalized().getAngle() ;auto dashLine = (Sprite*)getChildByTag(DASH_LINE_TAG);dashLine->setPosition(start_p);dashLine->setTextureRect(Rect(0,0,2,abs(end_p - start_p).getLength();dashLine->setRotation(CC_RADIANS_TO_DEGREES(-_angle) + 90.0f);dashLine->setVisible(true);/ update aim iconauto aim_icon = (Sprite*)getChildByTag(AIM_ICON_TAG);aim_icon->setPosition(_aimPos);aim_icon->setVisible(true);/ 計算球桿位置auto aim_gan_pos = Vec2(end_p - start_p).getLength() + 45) * start_p.x - 45 * end_p.x) / (end_p - start_p).getLength(),(end_p - start_p).getLength() + 45) * start_p.y - 45 * end_p.y) / (end_p - start_p).getLength();auto aim_gan = (Sprite*)getChildByTag(AIM_GAN_TAG);aim_gan->setPosition(aim_gan_pos);aim_gan->setRotation(CC_RADIANS_TO_DEGREES(-_angle) - 90.0f);aim_gan->setVisible(true);(十一)實現(xiàn)碰撞檢測(物理模擬)1、碰撞檢測(物理模擬)的實現(xiàn)函數(shù)void PhysicsBox2dScene:update(float delta)float timeStep = 1.0f / 60.0f; / 時間同步int32 velocityIterations = 8; / 速度迭代次數(shù)int32 positionIterations = 1; / 位置迭代次數(shù)_world->Step(timeStep,velocityIterations,positionIterations);_ContactListener->deleteBody(); / 循環(huán)判定游戲輸贏for (b2Body* Cur = _world->GetBodyList(); Cur; Cur = Cur->GetNext() )if ( Cur->GetType() = b2_dynamicBody && Cur->IsAwake() = true )/ 模擬阻力Cur->SetLinearVelocity(b2Vec2(Cur->GetLinearVelocity().x * 0.989, Cur->GetLinearVelocity().y * 0.989);if( abs(Cur->GetLinearVelocity().x) < 0.5f && abs(Cur->GetLinearVelocity().y) < 0.5f )Cur->SetAwake(false);if (_powerEnd)/ 滑塊消失if (_curPower !=0)_curPower -= _sliderSpeed; else if (_curPower <= 0)_curPower = 0.0f;updatePowerSlider(_curPower);/ 所有球靜止才可以進行瞄準int awak = 0;for (b2Body* Cur = _world->GetBodyList(); Cur; Cur = Cur->GetNext() )if ( Cur->GetType() = b2_dynamicBody && Cur->IsAwake() = true )awak +;if (awak = 0)_canAim = true;2、在PhysicsBox2dScene.cpp文件中init函數(shù)中添加定時刷新函數(shù),實現(xiàn)物理模擬,碰撞檢測/ main loopthis->scheduleUpdate();(十二)游戲運行效果如圖。