Directional sensors

General discussion about Box2D tutorials
Post Reply
MattF
Posts: 6
Joined: Mon Jan 07, 2013 1:52 pm

Directional sensors

Post by MattF »

Heya.

First of all, thanks for great tutorials, they've been very helpful.

Following those i started creating a test for player character class, which would consist of main fixture, and 3 sensors for now: 1 for ground checking, and 2 sensors to the left and to the right. The idea is, to gather all enemies that are in one of those sensors in the vector, and then depending on the direction of the player, attack all those to the right or to the left.

Atm attacking works, but the problem is, regardless of direction of the player enemies in any of those 2 sensors are affected at the same time. After doing some checking i found out, that when there is a contact, pointers to enemies are pushed in both vectors ( enemiesInRangeRight and enemiesInRangeLeft) and i have no idea why.

Here's some code( sorry for some stupid variable names:

Code: Select all

//Hero
std::vector<Enemy*> enemiesInRangeLeft;
std::vector<Enemy*> enemiesInRangeRight;

void enemy_in_range_left(Enemy *enemy){ enemiesInRangeLeft.push_back(enemy);}
void enemy_left_range_left(Enemy* enemy) {enemiesInRangeLeft.erase(     std::find(enemiesInRangeLeft.begin(), enemiesInRangeLeft.end(), enemy ) );}

void enemy_in_range_right(Enemy *enemy){ enemiesInRangeRight.push_back(enemy);}
void enemy_left_range_right(Enemy* enemy) {enemiesInRangeRight.erase( std::find(enemiesInRangeRight.begin(), enemiesInRangeRight.end(), enemy ) );}


void attack(){ 
		
		if ( this->direction == left)
		{
		for ( int i = 0 ; i <enemiesInRangeLeft.size(); i++)
		enemiesInRangeLeft[i]->m_body->ApplyLinearImpulse( b2Vec2(0,50), enemiesInRangeLeft[i]->m_body->GetWorldCenter() );
		}
		else if ( this->direction == right)
		{
			for ( int i = 0 ; i <enemiesInRangeRight.size(); i++)
		enemiesInRangeRight[i]->m_body->ApplyLinearImpulse( b2Vec2(0,50), enemiesInRangeRight[i]->m_body->GetWorldCenter() );
		}
	}



// Contact Listener

class MyContactListener : public b2ContactListener
  {
    void BeginContact(b2Contact* contact) {
        Hero* hero;
        Enemy* enemy;
		
        
		if ( enemy_checker_right(contact, hero, enemy) )
            hero->enemy_in_range_right(enemy );
		if ( enemy_checker_left(contact, hero, enemy) )
            hero->enemy_in_range_left(enemy );
		if (ground_checker(contact,hero))
			hero->start_contact();
    }
  
    void EndContact(b2Contact* contact) {
          Hero* hero;
        Enemy* enemy;

        if ( enemy_checker_left(contact, hero, enemy) )
            hero->enemy_left_range_left( enemy);
		 if ( enemy_checker_right(contact, hero, enemy) )
            hero->enemy_left_range_right( enemy);
		if (ground_checker(contact,hero))
			hero->end_contact();
    }
  };



// Helpers

bool enemy_checker_left(b2Contact* contact, Hero*& hero, Enemy*& enemy)
  {
      b2Fixture* fixtureA = contact->GetFixtureA();
      b2Fixture* fixtureB = contact->GetFixtureB();

	  bool sensorA = fixtureA->IsSensor();
      bool sensorB = fixtureB->IsSensor();
      if ( ! (sensorA ^ sensorB) )
          return false;
	  
	  if (( fixtureA->GetFilterData().categoryBits == GROUND) || (fixtureB->GetFilterData().categoryBits == GROUND ))
		  return false;

	  Entity* entityA = static_cast<Entity*>( fixtureA->GetBody()->GetUserData() );
      Entity* entityB = static_cast<Entity*>( fixtureB->GetBody()->GetUserData() );
	 
	  if ( sensorA ) { 
		  if((fixtureA->GetFilterData().categoryBits == HERO_SENSOR_LEFT) || (entityB->getEntityType() == ENEMY))
		 // if((entityA->getEntityType() == HERO_SENSOR_LEFT) || (entityB->getEntityType() == ENEMY))
		  {
          hero = static_cast<Hero*> (entityA);
          enemy = static_cast<Enemy*> (entityB);
		  return true;
		  }
      }
      else if (sensorB){ 
		  if((fixtureB->GetFilterData().categoryBits == HERO_SENSOR_LEFT) || (entityA->getEntityType() == ENEMY))
		  {
          hero = static_cast<Hero*> (entityB);
          enemy = static_cast<Enemy*> (entityA);
		  return true;
		  }
      }
	
  }

bool enemy_checker_right(b2Contact* contact, Hero*& hero, Enemy*& enemy)
  {
      b2Fixture* fixtureA = contact->GetFixtureA();
      b2Fixture* fixtureB = contact->GetFixtureB();

	  bool sensorA = fixtureA->IsSensor();
      bool sensorB = fixtureB->IsSensor();
      if ( ! (sensorA ^ sensorB) )
          return false;
	  
	  if (( fixtureA->GetFilterData().categoryBits == GROUND) || (fixtureB->GetFilterData().categoryBits == GROUND ))
		  return false;

	  Entity* entityA = static_cast<Entity*>( fixtureA->GetBody()->GetUserData() );
      Entity* entityB = static_cast<Entity*>( fixtureB->GetBody()->GetUserData() );
	 
	  if ( sensorA ) { 
		  if((fixtureA->GetFilterData().categoryBits == HERO_SENSOR_RIGHT) || (entityB->getEntityType() == ENEMY))
		  //if((entityA->getEntityType() == HERO_SENSOR_RIGHT) || (entityB->getEntityType() == ENEMY))
		  {
          hero = static_cast<Hero*> (entityA);
          enemy = static_cast<Enemy*> (entityB);
		  return true;
		  }
      }
      else if (sensorB){ 
		  if((fixtureB->GetFilterData().categoryBits == HERO_SENSOR_RIGHT) || (entityA->getEntityType() == ENEMY))
		  {
          hero = static_cast<Hero*> (entityB);
          enemy = static_cast<Enemy*> (entityA);
		  return true;
		  }
      }

      
  }
  

I tried polling by filter type, or purely by bodyuserdata as you can see. i'm sure it's some kind of stupid oversight on my part.

Any help would be appreciated.
iforce2d
Site Admin
Posts: 861
Joined: Sat Dec 22, 2012 7:20 pm

Re: Directional sensors

Post by iforce2d »

I think you just need to change the OR here to and AND:

Code: Select all

if ( (fixtureA->GetFilterData().categoryBits == HERO_SENSOR_LEFT) || 
     (entityB->getEntityType() == ENEMY) )
MattF
Posts: 6
Joined: Mon Jan 07, 2013 1:52 pm

Re: Directional sensors

Post by MattF »

Just as i thought, something quite stupid like this:)

Thanks a lot!
MattF
Posts: 6
Joined: Mon Jan 07, 2013 1:52 pm

Re: Directional sensors

Post by MattF »

Hi again, got one more question, regarding a problem, that came up after the previous fix.

It seems now, when both sensors are active things are acting strange, and i get reading and writing violations.
If i disable checking the sensor on one side in Contact Listener the other works perfectly. So enemies in right sensor when direction is right, are detected and attacked. Same for left, if it's active.

But when both are active, moving enemy to the left sensor causes it to be added to a vector and not removed later. Also, when directional attack only work on one side, so right sensor enemies are attacked regardless of direction, and left sensor enemies are attacked only when direction is left.

I wonder, if it has to do something with StartContact and EndContact? Should i resolve all callbacks in one helper function, then cast the pointer once in one of those functions?
metalbass_92
Posts: 18
Joined: Wed Dec 26, 2012 12:27 pm

Re: Directional sensors

Post by metalbass_92 »

To solve all the problems you can have with enemies not being deleted (maybe they are at the vector multiple times?) i would use a std::set : http://cplusplus.com/reference/set/set/.

A set cannot have repetitions and has logaritmic lookup time (vs vector linear lookup time)

That may not solve all your problems, but i think it will help.

Also i found the code for the methods enemy_checker_right/left to be a little overcomplicated; I think you shouldn't check for sensors, but if the fixture is the sensor that you want.
Xavier Arias
Cocos2d-x Game Programmer
iforce2d
Site Admin
Posts: 861
Joined: Sat Dec 22, 2012 7:20 pm

Re: Directional sensors

Post by iforce2d »

Yea good suggestion, when you don't want duplicates using a std::set makes things easier.

It's quite easy for these checks in the contact listener to become complicated. In the top-down car tutorial I started describing something which I hoped would be relatively simple but it still ended up fairly complicated :) See the "Handling varying surfaces" section. Somewhere along the way you will typically end up with a big if/else statement to channel all the various possibilities to a function which handles them.
MattF
Posts: 6
Joined: Mon Jan 07, 2013 1:52 pm

Re: Directional sensors

Post by MattF »

Thanks again for the suggestions:)

Using sets seems way better in this scenario, so i'll replace my vectors with those later.

For now i fixed my problem by performing all checks for both sensors and the doing final cast in one helper function. Also i switched checking method from polling object type from body user data, to simply checking fixture categoryBits. Then i simply cast from entity to either hero or specific enemy type.

Seems to work very well. It might be complicated later, when there will be more types, but i guess there is no way around that, and i'll need to handle big if/elses or switches;)
metalbass_92
Posts: 18
Joined: Wed Dec 26, 2012 12:27 pm

Re: Directional sensors

Post by metalbass_92 »

A way to avoid the big if/else statements is to use polymorphism into a list (std::list or a std::vector) of pointers that can receive contact events, this way you only need to define a base class which has virtual methods that will be overridden by every class that needs to know of a contact. You'll obviously need to add the instances that need to know about contacts to this list.

When a contact is received on your contact listener you only need to iterate through the list of pointers and call the method that suits the current contact (begin/end...) on each of them. As you want to stop iterating through the list when a contact is handled you can return (a bool) from these functions whether to stop iterating or not.

Hope this helps somebody!
Xavier Arias
Cocos2d-x Game Programmer
Post Reply