Hi all,
Currently I’m facing a strange bug with Box2D - Cocos2dx v3.0.
I’m following this tutorial (http://www.raywenderlich.com/28606/how-to-create-a-breakout-game-with-box2d-and-cocos2d-2-x-tutorial-part-2) but modify it a little bit, just playing around since I’m new with Box2d.
In this simple game I’ve created a paddle with very big mass and no friction to reflect the ball, prevent it from colliding the bottom edge of screen. When playing on phone, I realized it’d be somehow stubborn if the paddle continue to slide freely on the bottom edge (player will lose track of it in an instant, which will easily lead to game over), so when TouchesBegin and TouchesMoved, I’ve set the friction of that paddle to 0.0f; when player end with the touches, the friction is set to 0.2f (which slow down then stop the paddle).
Problem is my paddle gradually become slow and slow even when I’m touching and dragging… then it completely stop! I’ve tried to print down the paddleFicture->friction but have no clue. I thought “setting” a variable will not cause it to be increased or decreased unwantedly… ?
Here’s my code:
#include "HelloWorldScene.h"
Scene* HelloWorld::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = HelloWorld::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 HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
/////////////////////////////
// 2. add a menu item with "X" image, which is clicked to quit the program
// you may modify it.
// add a "close" icon to exit the progress. it's an autorelease object
auto closeItem = MenuItemImage::create("CloseNormal.png", "CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
closeItem->setPosition(Vec2(origin.x - closeItem->getContentSize().width/2 + visibleSize.width,
origin.y + closeItem->getContentSize().height/2));
// create menu, it's an autorelease object
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
/////////////////////////////
// 3. add your codes below...
// add a label shows "Hello World"
// create and initialize a label
auto label = LabelTTF::create("Hello World", "Arial", 24);
// position the label on the center of the screen
label->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label->getContentSize().height));
// add the label as a child to this layer
this->addChild(label, 1);
// add "HelloWorld" splash screen"
//auto sprite = Sprite::create("HelloWorld.png");
// position the sprite on the center of the screen
//sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
//this->addChild(sprite);
// Add sprites of objects to the layer
ball = Sprite::create("Ball_2.png");
ball->setPosition((visibleSize.width - ball->getContentSize().width)/2,
(visibleSize.height - ball->getContentSize().height)/2); // Center of the window
this->addChild(ball);
Sprite* paddle = Sprite::create("Paddle.png");
paddle->setPosition((visibleSize.width - paddle->getContentSize().width)/2,
paddle->getContentSize().height/2); // Bottom of window
this->addChild(paddle);
// Create a world
//b2Vec2 gravity = b2Vec2(0.0f, -9.81f); // Gravity of Earth
b2Vec2 gravity = b2Vec2(0.0f, 0.0f); // Gravity of some God-live planets
world = new b2World(gravity);
// Draw borders for physical testing
world->SetContinuousPhysics(true);
this->addChild(B2DebugDrawLayer::create(world, PTM_RATIO), PTM_RATIO);
// Ball body and shape
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position = b2Vec2(ball->getPosition().x/PTM_RATIO,
ball->getPosition().y/PTM_RATIO);
ballBodyDef.userData = ball;
body = world->CreateBody(&ballBodyDef);
// Paddle body and shape
b2BodyDef paddleBodyDef;
paddleBodyDef.type = b2_dynamicBody;
paddleBodyDef.position = b2Vec2(paddle->getPosition().x/PTM_RATIO,
paddle->getPosition().y/PTM_RATIO);
paddleBodyDef.userData = paddle;
paddleBody = world->CreateBody(&paddleBodyDef);
// Physical shape of objects. Better the same as their respective graphical shape
b2CircleShape circle;
circle.m_radius = (ball->getContentSize().width/2)/PTM_RATIO;
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 0.5f;
ballShapeDef.friction = 0.0f;
ballShapeDef.restitution = 1.0f;
body->CreateFixture(&ballShapeDef);
b2PolygonShape rect;
rect.SetAsBox((paddle->getContentSize().width/PTM_RATIO)/2,
(paddle->getContentSize().height/PTM_RATIO)/2);
b2FixtureDef paddleShapeDef;
paddleShapeDef.shape = ▭
paddleShapeDef.density = 1.0f;
paddleShapeDef.friction = 0.2f;
paddleShapeDef.restitution = 0.1f;
paddleFixture = paddleBody->CreateFixture(&paddleShapeDef);
// Create edge around the entire screen
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0);
groundBody = world->CreateBody(&groundBodyDef);
b2EdgeShape groundEdge;
b2FixtureDef boxShapeDef;
boxShapeDef.shape = &groundEdge;
// Wall definition
groundEdge.Set(b2Vec2(0, 0), b2Vec2(visibleSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(0, 0), b2Vec2(0, visibleSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(visibleSize.width/PTM_RATIO, 0), b2Vec2(visibleSize.width/PTM_RATIO, visibleSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(0, visibleSize.height/PTM_RATIO), b2Vec2(visibleSize.width/PTM_RATIO, visibleSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
// Set NULL to prevent the "empty created" case.
mouseJoint = NULL;
// Prismatic joints let us restrict the movement
// of one body to another, along a specified axis.
b2PrismaticJointDef prisJointDef;
b2Vec2 xAxis = b2Vec2(1.0f, 0.0f);
prisJointDef.collideConnected = true;
prisJointDef.Initialize(paddleBody, groundBody, paddleBody->GetWorldCenter(), xAxis);
world->CreateJoint(&prisJointDef);
// Using acceleromater. The deprecated message for Scene isn't used here
//Device::setAccelerometerEnabled(true);
//auto accelerateListener = EventListenerAcceleration::create(CC_CALLBACK_2(HelloWorld::onAcceleration, this));
//this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(accelerateListener, this);
// Using touch
auto touchListener = EventListenerTouchAllAtOnce::create();
touchListener->onTouchesBegan = CC_CALLBACK_2(HelloWorld::onTouchesBegan, this);
touchListener->onTouchesMoved = CC_CALLBACK_2(HelloWorld::onTouchesMoved, this);
touchListener->onTouchesEnded = CC_CALLBACK_2(HelloWorld::onTouchesEnded, this);
touchListener->onTouchesCancelled = CC_CALLBACK_2(HelloWorld::onTouchesCancelled, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener, this);
// Initialize interval vars
tickInterval = 1.0/60.0;
kickInterval = 2.0;
// Update the graphic to the physic every 1/60 sec
this->schedule(schedule_selector(HelloWorld::tick), tickInterval);
// Apply force to the ball every 2 sec
//this->schedule(schedule_selector(HelloWorld::kick), kickInterval);
// Just kick once to start the game
kick(0.0);
return true;
}
void HelloWorld::menuCloseCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
return;
#endif
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
void HelloWorld::tick(float dt) {
world->Step(tickInterval, 10, 10);
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext()) {
if (b->GetUserData() != NULL) {
Sprite* ballData = (Sprite*)b->GetUserData();
ballData->setPosition(Vec2( b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO ));
ballData->setRotation(-1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));
b2Vec2 velocity = b->GetLinearVelocity();
float32 speed = velocity.Length();
static int maxSpeed = 10;
if (speed > maxSpeed) {
//b->SetLinearDamping(0.5);
} else if (speed < maxSpeed) {
b->SetLinearDamping(0.0);
}
}
}
}
void HelloWorld::kick(float dt) {
b2Vec2 force = b2Vec2(100, 100);
body->ApplyLinearImpulse(force, body->GetPosition(), true);
}
void HelloWorld::onAcceleration(Acceleration* acc, Event* anEvent) {
// Landscape left values
//b2Vec2 accGravity = b2Vec2(acc->y * 30, -acc->x * 30);
//world->SetGravity(accGravity);
}
void HelloWorld::onTouchesBegan(const std::vector<Touch*>& touches, Event *touchEvent) {
// On Windows, this mouseJoint is created but empty.
// That's why if we return here, its m_bodyB will still NULL
// and cause crashing later.
// On phone it is not created at all (as we expected it should be)
// so this check will not cause any problem.
CCLOG("Now friction = %f", paddleFixture->GetFriction());
paddleFixture->SetFriction(0.0f);
///*
if (mouseJoint != NULL) {
return;
}
//*/
// Get the first available touch.
auto myTouch = new Touch();
for(auto &item: touches) {
if (item) {
myTouch = item;
break;
}
}
Point location = myTouch->getLocationInView();
location = Director::getInstance()->convertToGL(location);
b2Vec2 worldLoc = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
//CCLOG("Touch began at x = %f, y = %f", location.x, location.y);
if (paddleFixture->TestPoint(worldLoc)) {
//CCLOG("Tested point. Now init the mouseJoint.");
b2MouseJointDef mouseJointDef;
mouseJointDef.bodyA = groundBody;
mouseJointDef.bodyB = paddleBody;
mouseJointDef.target = worldLoc;
mouseJointDef.collideConnected = true;
mouseJointDef.maxForce = 10000.0f * paddleBody->GetMass();
mouseJoint = (b2MouseJoint*)world->CreateJoint(&mouseJointDef);
paddleBody->SetAwake(true);
}
}
void HelloWorld::onTouchesMoved(const std::vector<Touch*>& touches, Event *touchEvent) {
paddleFixture->SetFriction(0.0f);
if (mouseJoint == NULL) {
return;
}
auto myTouch = new Touch();
for(auto &item: touches) {
if (item) {
myTouch = item;
break;
}
}
Point location = myTouch->getLocationInView();
location = Director::getInstance()->convertToGL(location);
b2Vec2 worldLoc = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
//CCLOG("Touch moved to x = %f, y = %f", location.x, location.y);
mouseJoint->SetTarget(worldLoc);
paddleBody->SetAwake(true); // Sometime it *mysteriously* asleep - so set it like this
}
void HelloWorld::onTouchesEnded(const std::vector<Touch*>& touches, Event *touchEvent) {
paddleFixture->SetFriction(0.2f);
if (mouseJoint != NULL) {
world->DestroyJoint(mouseJoint);
mouseJoint = NULL;
}
}
void HelloWorld::onTouchesCancelled(const std::vector<Touch*>& touches, Event *touchEvent) {
paddleFixture->SetFriction(0.2f);
if (mouseJoint != NULL) {
world->DestroyJoint(mouseJoint);
mouseJoint = NULL;
}
}
P/S: when let it be, the paddle will slowly slide to the bottom right corner of the screen (though I 've set the gravity to 0, 0. I still have no clue about this too >.<