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\$
3
  • \$\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, 2020 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, 2020 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, 2020 at 23:00

1 Answer 1

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) 
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .