0
\$\begingroup\$

I'm trying to add Box2d to my C++/SFML platformer game. I have my ground tiles with a b2staticBody. And the player character is b2_dynamicBody.

My usual method to check if player is grounded is to Raycast straight down from centre of sprite to just below the foot level. (NOTE: I do note that SFML is y-down).

For now, I just want the raycast to return true if it hits anything. But it always returns false. I watch the player land on the ground and can walk along the floor and fall off it (ie. The built-in box2d collision is working)

Here is my code (I am brand new to using Box2d in c++, so sorry if it's wrong. There aren't too many good examples to be found online)

void guy::update() {
bool bGrounded = false;
b2RayCastOutput output;
b2RayCastInput input;
input.p1 = bodyPtr->GetPosition();
input.p2 = bodyPtr->GetPosition() + b2Vec2(0, 1.f * Constants::PPU); 
input.maxFraction = 1.0f;
b2Fixture* fixture = bodyPtr->GetFixtureList();
std::cout << "p1: " << input.p1.x << " , " << input.p1.y << " / p2: " << input.p2.x << " , " << input.p2.y << std::endl;
std::cout << "sprite pos: " << sprite.getPosition().x << " , " << sprite.getPosition().y << std::endl;
if (fixture->RayCast(&output, input, 0))
{
    bGrounded = true;
}
else
{
    bGrounded = false;
}
std::cout << "isGrounded: " << bGrounded << std::endl;
}

The console outputs seem ok (ie. p1 and p2 are where I expected them to be), but the bool always returns 0.

Of course I am very open to an improved jumping function. Eventually I want to be able to add the ability to hold the jump button for some time to get a bigger jump. But the only way I can think to start with is checking if the player is touching ground first.

NOTE: Here is the ground Tiles start function (at the bottom you see i CreateFixture():

void Tile::start(size_t x, size_t y, sf::Texture& texture, bool isSolid, b2World* world)
{
worldPtr = world;
bIsSolid = isSolid;
sprite.setTexture(texture, true);
sprite.setOrigin(sprite.getTextureRect().width / 2.f, sprite.getTextureRect().height / 2.f);
sprite.setPosition((float)x * Constants::PPU, (float)y * Constants::PPU);

if (isSolid)
{
    bodyDef.position.Set(sprite.getPosition().x, sprite.getPosition().y 
    ); 
    bodyPtr = worldPtr->CreateBody(&bodyDef);
    b2Vec2 topEdge(-1, 0);

    box.SetAsBox(sprite.getTextureRect().width / 2.f, sprite.getTextureRect().height / 2.f);

    bodyPtr->CreateFixture(&box, 0.f);

}

}

Here is the player start() in case it will help:

void Guy::start(b2World* world)
{
worldPtr = world;
texture.loadFromFile("Textures/guy.png");
sprite.setTexture(texture, true);
sprite.setOrigin(sprite.getTextureRect().width / 2.f, sprite.getTextureRect().height / 2.f);
setPosition(4 * Constants::PPU, 10 * Constants::PPU);

bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(sprite.getPosition().x, sprite.getPosition().y); // TODO: Does this need to be offset?
bodyPtr = worldPtr->CreateBody(&bodyDef);

box.SetAsBox(sprite.getTextureRect().width / 2.f, sprite.getTextureRect().height / 2.f);

fixtureDef.shape = &box;
fixtureDef.density = 65.0f; // TODO Magic numbers
fixtureDef.friction = 0.05f;

bodyPtr->CreateFixture(&fixtureDef);
bodyPtr->SetFixedRotation(true);

}
\$\endgroup\$
  • \$\begingroup\$ Looks like you're testing against your guys fixture? Also, looks like you're testing only against the first fixture, are there more than one? \$\endgroup\$ – Vaillancourt Feb 22 at 17:33
  • \$\begingroup\$ there is only one fixture (i've added the start function for the ground objects into my question). The next thing i am about to try is to specify 'edges' as fixtures to see if that is why. The only fixture I added right now is a box shape (this is true for both the player and the ground tile) \$\endgroup\$ – Krangogram Feb 22 at 22:56
  • \$\begingroup\$ i bet it is the first thing you said. Ie) testing against guys fixture only. I wanted it to check any object with collider on it. (I also read here on SE somewhere that the Box2d Raycast will ignore 'back-facing' edges, so I thought Id be ok for it ignoring the player since the ray begins inside its collider \$\endgroup\$ – Krangogram Feb 22 at 23:00
0
\$\begingroup\$

OK well eventually I think i found the answer (I was WAY off when I originally wrote the question).

As I think Vaillancourt was alluding to in the comments above, I wrongly thought the only way to RayCast in box2d was via the b2Fixture class. There is in fact a RayCast function on b2World also.

Trying this World RayCast instead brought out a new problem for me. I couldnt understand what it wanted in the 'b2RayCastCallback' parameter of the method. From learning how to fix this, I think i learnt a lot about box2d and c++ in general so I am really happy to got this far by myself. (NOTE: If anyone experienced reads this and see's I am still wrong , please feel free to correct me, or edit the answer.).

I had to create a new class , which inherits from b2RayCastCallback (public). It only had one function i had to implement called ReportFixture. Then I created member variables to use as 'out' variables. I also created a constructor that sets a bool 'bHit' to false upon construction, the bHit bool is set true as soon as Ray hits something (which in turn leads it to call ReportFixture method that I implemented from base class.

Here is the new code which does now report in the console when my player character lands on the floor objects.

inside my player update:

void Guy::update(float deltaSeconds)
{
MoveSideways();


bool bGrounded = false;
RayCastCallback callback;
b2Vec2 p1 = bodyPtr->GetPosition();
b2Vec2 p2 = bodyPtr->GetPosition() + b2Vec2(0, 0.51f * sprite.getTextureRect().height);
worldPtr->RayCast(&callback, p1, p2);
if (callback.bHit)
{
    // We have a hit, it must be the ground (I hope.. :S) ...
    std::cout << "bHit" << std::endl;
}

New 'RayCastCallback class' I made:

 /// .h file
#include "box2d/box2d.h"

class RayCastCallback : public b2RayCastCallback
{
public:
RayCastCallback();
// Inherited via b2RayCastCallback
virtual float ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float fraction);

bool bHit;
b2Fixture* out_fixture = nullptr;
b2Vec2 out_point;
b2Vec2 out_normal;
float out_fraction;

};

/// .cpp file: 

#include "RaycastCallBack.h"

RayCastCallback::RayCastCallback()
{
bHit = false;
}

float RayCastCallback::ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float fraction)
{
bHit = true;
out_fixture = fixture;
out_point = point;
out_normal = normal;
out_fraction = fraction;
return 0; // there a different options for return here that allow the ray to continue or stop etc (see notes in base class) 
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.