How to write a box2d contact listener using cocos2d? - objective-c

I've been reading various tutorials on how to write a contact listener, and I can't wrap my head around it.
Here is what I have so far:
In each of the classes that I have representing a physics object I do:
_body->SetUserData(self);
I write a contact listener class containing the following two methods:
void ContactListener::BeginContact(b2Contact* contact)
{
// Box2d objects that collided
b2Fixture* fixtureA = contact->GetFixtureA();
b2Fixture* fixtureB = contact->GetFixtureB();
// Sprites that collided
MyNode* actorA = (MyNode*) fixtureA->GetBody()->GetUserData();
MyNode* actorB = (MyNode*) fixtureB->GetBody()->GetUserData();
}
void ContactListener::EndContact(b2Contact* contact)
{
// Box2d objects that collided
b2Fixture* fixtureA = contact->GetFixtureA();
b2Fixture* fixtureB = contact->GetFixtureB();
// Sprites that collided
MyNode* actorA = (MyNode*) fixtureA->GetBody()->GetUserData();
MyNode* actorB = (MyNode*) fixtureB->GetBody()->GetUserData();
}
I don't know what to do next. I now have the two sprites which are colliding, but I want to do the following:
1) When they collide, I want to remove one of the sprites from the world, based on the type of object. (for example if one a cat object and the other is a mouse object, I want to remove the mouse object.
2) I want to let the cat object know it ate a mouse
3) I want the cat to continue moving as if it didn't contact with the mouse.
4) I still wan't the cat to collide normally with things like the terrain.
What do I do next ? I'm pretty clueless on what to do? How do I get the cat to continue to collide normally with the terrain, but not with the mouse? When do I remove the mouse?

Having an "Entity" class that hold the reference to the Box2D body and does the manipulations on it is definitely a good way to go. If you have a Spaceship class vs. a Meteor class, each of them can supply their own derived methods of controlling the body (AI), but each of them has common logic and code to support operations on "Things that have a body" (e.g. common "Entity" base class). I think you are on the right track.
It gets a little murky when the contacts start happening. This is where you start getting into the architecture of your overall system, not just the structure of the physics world or a single Coco2d Scene.
Here is how I have done this in the past:
First I set up the contact listener, listed below:
class EntityContactListener : public ContactListener
{
private:
GameWorld* _gameWorld;
EntityContactListener() {}
typedef struct
{
Entity* entA;
Entity* entB;
} CONTACT_PAIR_T;
vector<CONTACT_PAIR_T> _contactPairs;
public:
virtual ~EntityContactListener() {}
EntityContactListener(GameWorld* gameWorld) :
_gameWorld(gameWorld)
{
_contactPairs.reserve(128);
}
void NotifyCollisions()
{
Message* msg;
MessageManager& mm = GameManager::Instance().GetMessageMgr();
for(uint32 idx = 0; idx < _contactPairs.size(); idx++)
{
Entity* entA = _contactPairs[idx].entA;
Entity* entB = _contactPairs[idx].entB;
//DebugLogCPP("Contact Notification %s<->%s",entA->ToString().c_str(),entB->ToString().c_str());
msg = mm.CreateMessage();
msg->Init(entA->GetID(), entB->GetID(), Message::MESSAGE_COLLISION);
mm.EnqueueMessge(msg, 0);
msg = mm.CreateMessage();
msg->Init(entB->GetID(), entA->GetID(), Message::MESSAGE_COLLISION);
mm.EnqueueMessge(msg, 0);
}
_contactPairs.clear();
}
void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
b2Fixture* fixtureA = contact->GetFixtureA();
b2Body* bodyA = fixtureA->GetBody();
Entity* entityA = bodyA->GetUserData();
b2Fixture* fixtureB = contact->GetFixtureB();
b2Body* bodyB = fixtureB->GetBody();
Entity* entityB = bodyB->GetUserData();
if(test if entityA and entityB should not have collision response)
{
contact->SetEnabled(false);
}
// Do this if you want there to be collision notification, even if
// there is no response.
AddContactPair(entA,entB);
}
void AddContactPair(Entity* entA, Entity* entB)
{
for(uint32 idx = 0; idx < _contactPairs.size(); idx++)
{
if(_contactPairs[idx].entA == entA && _contactPairs[idx].entB == entB)
return;
// Not sure if this is needed...
if(_contactPairs[idx].entA == entB && _contactPairs[idx].entA == entB)
return;
}
CONTACT_PAIR_T pair;
pair.entA = entA;
pair.entB = entB;
_contactPairs.push_back(pair);
}
// BEWARE: You may get multiple calls for the same event.
void BeginContact(b2Contact* contact)
{
Entity* entA = (Entity*)contact->GetFixtureA()->GetBody()->GetUserData();
Entity* entB = (Entity*)contact->GetFixtureB()->GetBody()->GetUserData();
assert(entA != NULL);
assert(entB != NULL);
// Not sure this is still needed if you add it in the pre-solve.
// May not be necessary...
AddContactPair(entA, entB);
}
// BEWARE: You may get multiple calls for the same event.
void EndContact(b2Contact* contact)
{
}
};
Because of the way the engine works, you can get multiple contact hits for the same bodies. This listener filters them so if two Entities collide, you only get one message.
The Listener only stores the collisions that have occurred. It could be modified to further separate them into "begins" and "ends" for other purposes. Here, contact meant "you've been hit by something". I didn't need to know if it stopped contacting.
The call to NotifyCollisions is the "secret sauce". It sends out a message to both the contacted entities (via a message system) that they hit something and the other Entity is what they hit. Bullet hit ship. Bullet destroys self. Ship damages self based on bullet properties (GetDamageInflicted() method). This in turn signals the graphics system to remove the bullet from the display. If the ship was destroyed, it is destroyed as well.
From an overall execution standpoint:
Before you begin, assign the contact listener.
Each Cycle of your game:
Call "Update" on all your Entities. This updates their physics forces, etc.
Update Box2d world.
Call NotifyCollisions on Listener.
Remove dead Entities from system.
Was this helpful?

Related

deathCounter not incrementing

Hi in this game that i am trying to modify there are 3 different types of enemies all with the same enemyHealth script. in this health script i am attempting to increase a counter every time an enemy dies. the code below shows the enemyHealth class
using UnityEngine;
namespace CompleteProject
{
public class EnemyHealth : MonoBehaviour
{
public int startingHealth = 100; // The amount of health the enemy starts the game with.
public bool isDead; // Whether the enemy is dead.
public int deathCount;
public int currentHealth; // The current health the enemy has.
public float sinkSpeed = 2.5f; // The speed at which the enemy sinks through the floor when dead.
public int scoreValue = 10; // The amount added to the player's score when the enemy dies.
public AudioClip deathClip; // The sound to play when the enemy dies.
Animator anim; // Reference to the animator.
AudioSource enemyAudio; // Reference to the audio source.
ParticleSystem hitParticles; // Reference to the particle system that plays when the enemy is damaged.
CapsuleCollider capsuleCollider; // Reference to the capsule collider.
bool isSinking; // Whether the enemy has started sinking through the floor.
void Awake ()
{
// Setting up the references.
anim = GetComponent <Animator> ();
enemyAudio = GetComponent <AudioSource> ();
hitParticles = GetComponentInChildren <ParticleSystem> ();
capsuleCollider = GetComponent <CapsuleCollider> ();
// Setting the current health when the enemy first spawns.
currentHealth = startingHealth;
}
void Update ()
{
// If the enemy should be sinking...
if(isSinking)
{
// ... move the enemy down by the sinkSpeed per second.
transform.Translate (-Vector3.up * sinkSpeed * Time.deltaTime);
}
}
public void TakeDamage (int amount, Vector3 hitPoint)
{
// If the enemy is dead...
if(isDead)
// ... no need to take damage so exit the function.
return;
// Play the hurt sound effect.
enemyAudio.Play ();
// Reduce the current health by the amount of damage sustained.
currentHealth -= amount;
// Set the position of the particle system to where the hit was sustained.
hitParticles.transform.position = hitPoint;
// And play the particles.
hitParticles.Play();
// If the current health is less than or equal to zero...
if(currentHealth <= 0)
{
// ... the enemy is dead.
Death ();
}
}
void Death ()
{
// The enemy is dead.
isDead = true;
// Turn the collider into a trigger so shots can pass through it.
capsuleCollider.isTrigger = true;
deathCount++;
Debug.Log("Deaths" + deathCount);
// Tell the animator that the enemy is dead.
anim.SetTrigger ("Dead");
// Change the audio clip of the audio source to the death clip and play it (this will stop the hurt clip playing).
enemyAudio.clip = deathClip;
enemyAudio.Play ();
}
public void StartSinking ()
{
// Find and disable the Nav Mesh Agent.
GetComponent <UnityEngine.AI.NavMeshAgent> ().enabled = false;
// Find the rigidbody component and make it kinematic (since we use Translate to sink the enemy).
GetComponent <Rigidbody> ().isKinematic = true;
// The enemy should no sink.
isSinking = true;
// Increase the score by the enemy's score value.
ScoreManager.score += scoreValue;
// After 2 seconds destory the enemy.
Destroy (gameObject, 2f);
}
}
}
all of the 3 enemies in the game have this script attached on their prefabs so they behave in the same way, so I cannot see why the counter will not increment when an enemy is destroyed.
when debugging the counter seems to go to 1 and then every kill after that just stays at 1 again.
that is correct. It's a member, so it will existing for each instance of your enemy. You want to track three different healths, right?
So enemy 1 start with 100, enemy 2 with 100, enemy 3 with 100. When enemy 1 gets 20 damage, it goes to 80 and 2,3 stay at 100, right? Why would the counter behave differently then? They all start at 0 and go to 1 when they die.
Either have an upper object (e.g. enemymanager) and have that count the times an enemy die (preferred solution) or use a static variable.

Detect when mouse is not in QGraphicsScene

I am having a class inherited from QGraphicsScene. I have a mouseMoveEvent in this class. Based on the mouse move I am sending the co ordinates via signal to the main window. I have multiple GraphicViews in main window. Based on the scene from which the signal is received I am display the scene co ordinates using QGraphicsTextItem.
The problem is when I move out of the scene area I am unable to hide the QGraphicsTextItem.
Can someone give me a work around for this?
Class Scene::QGraphicsScene
{
void MouseMoveEvent(QGraphicsSceneMouseEvent *Event)
{
int XPos = event.x();
int YPos = event.y();
emit SignalPos(XPos,YPos);
}
}
//In Main Window
connect(scene1,SignalPos(int,int),this,SlotPos1(int,int);
//Similarly for scene2,scene3,scene4
void MainWindow::SlotPos(int X, int Y)
{
m_qgtxtItemX.setText(QString::x);
//I want to hide this once I am out of scene.It is a member variable. I tried
//taking local variable but it didn't work.
//Similarly for y and other slots
}
Install an event filter on the scene.
scene1->installEventFilter(this);
then implement the method in the class that is referenced by "this":
bool eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::Leave)
{
qDebug() << "Mouse left the scene";
}
return false;
}
Just tried it and it worked! If you are installing the event filter on more than one object, please use "watched" to differentiate between them.
Best,

Creating Touchjoints/Mousejoints on small objects in box2d

I'm using Cocos2d iPhone with Box2D to create a basic physics engine.
Occasionally the user is required to drag around a small box2D object.
Creation of touchjoints on small objects is a bit hit and miss, with the game engine seeing it as a tap on blank space as often as actually creating the appropriate touchjoint. In practice this means the user is constantly mashing their fingers against the screen in vain attempts to move a stubborn object. I want the game to select small objects easily without this 'hit and miss' effect.
I could create the small objects with larger sensors around them, but this is not ideal because objects above a certain size (around 40px diameter) don't need this extra layer of complexity; and the small objects are simply the big objects scaled down to size.
What are some strategies I could use to allow the user experience to be better when moving small objects?
Here's the AABB code in ccTouchBegan:
b2Vec2 locationWorld = b2Vec2(touchLocation.x/PTM_RATIO, touchLocation.y/PTM_RATIO);
b2AABB aabb;
b2Vec2 delta = b2Vec2(1.0/PTM_RATIO, 1.0/PTM_RATIO);
//Changing the 1.0 here to a larger value doesn't make any noticeable difference.
aabb.lowerBound = locationWorld - delta;
aabb.upperBound = locationWorld + delta;
SimpleQueryCallback callback(locationWorld);
world->QueryAABB(&callback, aabb);
if(callback.fixtureFound){
//dragging code, updating sprite location etc.
}
SimpleQueryCallback code:
class SimpleQueryCallback : public b2QueryCallback
{
public:
b2Vec2 pointToTest;
b2Fixture * fixtureFound;
SimpleQueryCallback(const b2Vec2& point) {
pointToTest = point;
fixtureFound = NULL;
}
bool ReportFixture(b2Fixture* fixture) {
b2Body* body = fixture->GetBody();
if (body->GetType() == b2_dynamicBody) {
if (fixture->TestPoint(pointToTest)) {
fixtureFound = fixture;
return false;
}
}
return true;
}
};
What about a minimum collision box for touches? Objects with less than 40px diameter use the 40px diameter, all larger objects use their actual diameter.
What I ended up doing - thanks to iforce2d, was change ReportFixture in SimpileQueryCallback to:
bool ReportFixture(b2Fixture* fixture) {
b2Body* body = fixture->GetBody();
if (body->GetType() == b2_dynamicBody) {
//if (fixture->TestPoint(pointToTest)) {
fixtureFound = fixture;
return true;
//}
}
return true;
}
And increase the delta to 10.0/PTM_RATIO.

Cocos2D: Two objects of the same class colliding?

Working on game where plates will be falling from top to bottom. Some plates will also "bounce" on the ground and then start moving upwards again. This leads situations where a falling plate collides with a "rising plate".
My problem? I don´t know how to detect this collision.
Since all the plates comes from the same class I can´t write
if(CGRectIntersectsRect([self boundingBox], [self boudingBox]))
since this statement will always be true.
I create the plates with a for-loop:
for(i=0; i<9; i++){
Plate *plate = [Plate initPlate];
}
and then just reuse these plates throughout the game.
Any ideas or work arounds on how I detect a collision between two plates? Any advice would be greatly appreciated.
Regards.
You need to have a class that manages (for example using a NSMutableArray) the set of plates, and instead of checking for collisions on the Plate class you do it on this new class.
Assuming your array is:
NSMuttableArray *plateSet
You can do this:
for (Plate *bouncingPlate in plateSet)
{
if ([bouncingPlate bouncing])
{
for (Plate *fallingPlate in plateSet)
{
if (![fallingPlate bouncing])
{
/* Check for collision here between fallingPlate and bouncingPlate */
}
}
}
}
Or, more elegantly:
for (Plate *bouncingPlate in plateSet)
{
if (![bouncingPlate bouncing])
{
continue;
}
for (Plate *fallingPlate in plateSet)
{
if ([fallingPlate bouncing])
{
continue;
}
/* Check for collision here between fallingPlate and bouncingPlate */
}
}
yes...you need to add them to a NSMutableArray and then just use ccpDistance to check collision
something like this:
for (int i=0;i<8,i++){
for (int j=i+1,j<9,j++){
if(ccpDistance([[plates objectAtIndex:i]position],[[plates objectAtIndex:j]position])<plateRadius*2) {
//collision detected
}}}
of course this works if plates are circles
for squares use CGRectIntersectsRect:
for (int i=0;i<8,i++){
for (int j=i+1,j<9,j++){
if([plates objectAtIndex:i].visible && [plates objectAtIndex:j].visible &&(CGRectIntersectsRect([[plates objectAtIndex:i]boundingBox],[[plates objectAtIndex:j]boundingBox])) {
//collision detected
}}}

Controller selection

In my title screen, i have a code saying that the first controller using A is the PlayerIndex.one.
Here is the code:
public override void HandleInput(InputState input)
{
for (int anyPlayer = 0; anyPlayer <4; anyPlayer++)
{
if (GamePad.GetState((PlayerIndex)anyPlayer).Buttons.A == ButtonState.Pressed)
{
FirstPlayer = (PlayerIndex)anyPlayer;
this.ExitScreen();
AddScreen(new Background());
}
}
}
My question is: How can i use the "FirstPlayer" in other classes? (without this, there is no interest in this code)
I tried the Get Set thing but i can't make it work. Does i need to put my code in another class? Do you use other code to make this?
Thanks.
You can make a static variable say : SelectedPlayer,
and assign first player to it!
then you can call the first player through this class,
for example
class GameManager
{
public static PlayerIndex SelectedPlayer{get;set;}
..
..
..
}
and right after the loop in your code, you can say:
GameManager.SelectedPlayer = FirstPlayer;
I hope this helps, if your code cold be clearer that would be easier to help :)
Ok, so to do this properly you're going to have to redesign a little.
First off, you should be checking for a new gamepad input (i.e. you should be exiting the screen only when 'A' has been newly pressed). To do this you should be storing previous and current gamepad states:
private GamePadState currentGamePadState;
private GamePadState lastGamePadState;
// in your constructor
currentGamePadState = new GamePadState();
lastGamePadState = new GamePadState();
// in your update
lastGamePadState = currentGamePadState;
currentGamePadState = GamePad.GetState(PlayerIndex.One);
Really what you need to do is modify your class that deals with input. The basic functionality from your HandleInput function should be moved into your input class. Input should have a collection of functions that test for new/current input. For example, for the case you posted:
public Bool IsNewButtonPress(Buttons buton)
{
return (currentGamePadState.IsButtonDown(button) && lastGamePadState.IsButtonUp(button));
}
Then you can write:
public override void HandleInput(InputState input)
{
if (input.IsNewButtonPress(Buttons.A)
{
this.ExitScreen();
AddScreen(new Background());
}
}
Note: this will only work for one controller. To extend the implementation, you'll need to do something like this:
private GamePadState[] currentGamePadStates;
private GamePadState[] lastGamePadStates;
// in your constructor
currentGamePadStates = new GamePadState[4];
currentGamePadStates[0] = new GamePadState(PlayerIndex.One);
currentGamePadStates[1] = new GamePadController(PlayerIndex.Two);
// etc.
lastGamePadStates[0] = new GamePadState(PlayerIndex.One);
// etc.
// in your update
foreach (GamePadState s in currentGamePadStates)
{
// update all of this as before...
}
// etc.
Now, you want to test every controller for input, so you'll need to generalise by writing a function that returns a Bool after checking each GamePadState in the arrays for a button press.
Check out the MSDN Game State Management Sample for a well developed implementation. I can't remember if it supports multiple controllers, but the structure is clear and can easily be adapted if not.