Box2D 2.1a Tutorial – Part 4 (Collision Detection)

Up until this point we have learnt how to create a Box2D world, add bodies, add joints to those bodies, and to texture them. While Box2D handles all the collision detection and resolution of the physics, it would also be useful for us to be able to determine when and what objects collide. So this will be what we will learn today.

In this tutorial we will create a simple Box2D world and populate it with a red and blue ball that drops down from the sky. When each ball collides with the ground we will reposition them at their original location from which they fell.

Once again we will extend MouseJointTutorial to save us from coding from scratch.

To be able to detect when collisions happen requires use of a b2ContactListener. The b2ContactListener can be considered an abstract class (even though AS3 doesn’t actually support them) as we do not instantiate it directly but have to extend it first. It would probably be a good idea to have a look at the b2ContactListener class yourself. You will observe the following functions that we can override.

public virtual function BeginContact(contact:b2Contact):void { }
public virtual function EndContact(contact:b2Contact):void { }
public virtual function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void {}
public virtual function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void { }

We will only be overriding BeginContact today since that is all we need for determining when the balls hit the ground. So lets get started.
First, we will create the b2Body balls and textures. You should know this process pretty well by now so I won’t go into lengths to explain it.

package
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import General.Input;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;

	public class CollisionDetectionTutorial extends MouseJointTutorial
	{
		public const RADIANS_TO_DEGREES:Number = 57.2957795;
		public const DEGREES_TO_RADIANS:Number = 0.0174532925;
		private var _blueBall:b2Body;
		private var _redBall:b2Body;
		private var _blueBallTexture:MovieClip;
		private var _redBallTexture:MovieClip;

		override protected function setup():void
		{
			_blueBall = createBall(300, 200, 50);
			_redBall = createBall(500, 300, 50);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

		protected function createBall(x:Number, y:Number, radius:Number):b2Body
		{
			var robotBody:b2BodyDef = new b2BodyDef();
			robotBody.type = b2Body.b2_dynamicBody;
			robotBody.fixedRotation = true;
			robotBody.position.Set(x / PIXELS_TO_METRE, y / PIXELS_TO_METRE);
			var body:b2Body = _world.CreateBody(robotBody);

			var robotBodyDef:b2CircleShape = new b2CircleShape();
			robotBodyDef.SetRadius(radius / PIXELS_TO_METRE);

			var robotBodyFixtureDef:b2FixtureDef = new b2FixtureDef();
			robotBodyFixtureDef.shape = robotBodyDef;
			robotBodyFixtureDef.restitution = 0.7;
			robotBodyFixtureDef.friction = 0.5;

			body.CreateFixture(robotBodyFixtureDef);
			return body;
		}

		override protected function update(e:Event):void
		{
			var timeStep:Number = 1 / 60;
			var velocityIterations:int = 6;
			var positionIterations:int = 2;

			UpdateMouseWorld();
			MouseDestroy();
			MouseDrag();

			_world.Step(timeStep, velocityIterations, positionIterations);
			_world.ClearForces();
			updateTextures();
			General.Input.update();
		}

		private function updateTextures():void
		{
			_redBallTexture.x = _redBall.GetPosition().x * PIXELS_TO_METRE;
			_redBallTexture.y = _redBall.GetPosition().y * PIXELS_TO_METRE;
			_redBallTexture.rotation = _redBall.GetAngle() * RADIANS_TO_DEGREES;

			_blueBallTexture.x = _blueBall.GetPosition().x * PIXELS_TO_METRE;
			_blueBallTexture.y = _blueBall.GetPosition().y * PIXELS_TO_METRE;
			_blueBallTexture.rotation = _blueBall.GetAngle() * RADIANS_TO_DEGREES;
		}
	}
}

Now we can get to the actual collision detection code. Create a class and call it BallContactListener, have it extend b2ContactListener and override the BeginContact function .

package
{
	import Box2D.Dynamics.Contacts.b2Contact;
	import Box2D.Dynamics.b2ContactListener;

	public class BallContactListener extends b2ContactListener
	{
		public function BallContactListener()
		{
		}

		override public function BeginContact(contact:b2Contact):void
		{
		}
	}
}

In our setup function we will create an instance of this BallContactListener and pass it to our world via the SetContactListener function.

		override protected function setup():void
		{
			var ballContactListener = new BallContactListener();
			_world.SetContactListener(ballContactListener);

			_blueBall = createBall(300, 200, 50);
			_redBall = createBall(500, 300, 50);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

At the moment our BeginContact function in the BallContactListener is empty. We know that it takes in a b2Contact as a parameter and we can take at a guess that it will supply us with the information we need to determine who collided with who.

So lets look what is available. We can see that we can call GetFixtureA and GetFixtureB to return a b2Fixture. From those fixtures we can retrieve a b2Body. The problem is, while we have the b2Bodies that have collided, how do we determine which bodies they correspond with? The answer is at the moment we can’t. Fortunately b2Bodies have two functions that will solve this problem for us, those being GetUserData and SetUserData which allow us to set any data we want as a property. We can leverage this to pass in a string to the body. So in our setup function lets assign the names to the relevant bodies. For best practise I am going to make a class called BodyType and have it contain public static const strings of the names.

		override protected function setup():void
		{
			_world.SetContactListener(ballContactListener);
			_groundBody.SetUserData(BodyType.GROUND);

			_blueBall = createBall(300, 200, 50);
			_blueBall.SetUserData(BodyType.BLUE_BALL);
			_redBall = createBall(500, 300, 50);
			_redBall.SetUserData(BodyType.RED_BALL);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

We can now go back into your BallContactListener since we have all the data we need. The one remaining question unanswered is which body will be in which fixture, fixture A or B? We can’t really be sure, so we have to test for both cases.

		override public function BeginContact(contact:b2Contact):void
		{
			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.BLUE_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			||(contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.BLUE_BALL))
			{

			}

			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.RED_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			|| (contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.RED_BALL))
			{

			}
		}

Now that we have the logic in our BeginContact function for determining if a ball collides with the ground we need a way of handling this in our main class. For now we will just use a event dispatcher.

package
{
	import flash.events.Event;
	import Box2D.Dynamics.Contacts.b2Contact;
	import flash.events.EventDispatcher;
	import Box2D.Dynamics.b2ContactListener;

	public class BallContactListener extends b2ContactListener
	{
		public static const BLUE_BALL_START_CONTACT:String = "blueBallStartContact";
		public static const RED_BALL_START_CONTACT:String = "redBallStartContact";
		public var eventDispatcher:EventDispatcher;

		public function BallContactListener()
		{
			eventDispatcher = new EventDispatcher();
		}

		override public function BeginContact(contact:b2Contact):void
		{
			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.BLUE_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			||(contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.BLUE_BALL))
			{
				eventDispatcher.dispatchEvent(new Event(BLUE_BALL_START_CONTACT));
			}

			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.RED_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			|| (contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.RED_BALL))
			{
				eventDispatcher.dispatchEvent(new Event(RED_BALL_START_CONTACT));
			}
		}
	}
}

After adding the listeners in the CollisionDetectionTutorial class it would be fair to assume that we could add in the code for repositioning the bodies in them. However, if you try to do that you will see that this does not work the way intended. The problem is when the bContactListener is called, it is happening at some stage during which all the physics calculations are happening and so modifying the Box2D world causes problems. We need to wait until it is safe to update items in the world. A good place that is safe is in the update function. So our listeners instead will set a Boolean flag so that in our update function can check those values and then know what to do.

package
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import General.Input;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;

	public class CollisionDetectionTutorial extends MouseJointTutorial
	{
		public const RADIANS_TO_DEGREES:Number = 57.2957795;
		public const DEGREES_TO_RADIANS:Number = 0.0174532925;
		private var _blueBallContact:Boolean = false;
		private var _redBallContact:Boolean = false;
		private var _blueBall:b2Body;
		private var _redBall:b2Body;
		private var _blueBallTexture:MovieClip;
		private var _redBallTexture:MovieClip;

		private function onRedBallStartContact(e:Event):void
		{
			_redBallContact = true;
		}

		private function onBlueBallStartContact(e:Event):void
		{
			_blueBallContact = true;
		}

		override protected function setup():void
		{
			var ballContactListener = new BallContactListener();
			ballContactListener.eventDispatcher.addEventListener(BallContactListener.BLUE_BALL_START_CONTACT, onBlueBallStartContact);
			ballContactListener.eventDispatcher.addEventListener(BallContactListener.RED_BALL_START_CONTACT, onRedBallStartContact);
			_world.SetContactListener(ballContactListener);
			_groundBody.SetUserData(BodyType.GROUND);

			_blueBall = createBall(300, 200, 50);
			_blueBall.SetUserData(BodyType.BLUE_BALL);
			_redBall = createBall(500, 300, 50);
			_redBall.SetUserData(BodyType.RED_BALL);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

		protected function createBall(x:Number, y:Number, radius:Number):b2Body
		{
			var robotBody:b2BodyDef = new b2BodyDef();
			robotBody.type = b2Body.b2_dynamicBody;
			robotBody.fixedRotation = true;
			robotBody.position.Set(x / PIXELS_TO_METRE, y / PIXELS_TO_METRE);
			var body:b2Body = _world.CreateBody(robotBody);

			var robotBodyDef:b2CircleShape = new b2CircleShape();
			robotBodyDef.SetRadius(radius / PIXELS_TO_METRE);

			var robotBodyFixtureDef:b2FixtureDef = new b2FixtureDef();
			robotBodyFixtureDef.shape = robotBodyDef;
			robotBodyFixtureDef.restitution = 0.7;
			robotBodyFixtureDef.friction = 0.5;

			body.CreateFixture(robotBodyFixtureDef);
			return body;
		}

		override protected function update(e:Event):void
		{
			var timeStep:Number = 1 / 60;
			var velocityIterations:int = 6;
			var positionIterations:int = 2;

			UpdateMouseWorld();
			MouseDestroy();
			MouseDrag();

			_world.Step(timeStep, velocityIterations, positionIterations);
			_world.ClearForces();
			updateTextures();
			General.Input.update();
			checkCollisions();
		}

		private function updateTextures():void
		{
			_redBallTexture.x = _redBall.GetPosition().x * PIXELS_TO_METRE;
			_redBallTexture.y = _redBall.GetPosition().y * PIXELS_TO_METRE;
			_redBallTexture.rotation = _redBall.GetAngle() * RADIANS_TO_DEGREES;

			_blueBallTexture.x = _blueBall.GetPosition().x * PIXELS_TO_METRE;
			_blueBallTexture.y = _blueBall.GetPosition().y * PIXELS_TO_METRE;
			_blueBallTexture.rotation = _blueBall.GetAngle() * RADIANS_TO_DEGREES;
		}

		private function checkCollisions():void
		{
			if(_blueBallContact)
			{
				_blueBall.SetPosition(new b2Vec2(300 / PIXELS_TO_METRE, 200 / PIXELS_TO_METRE));
				_blueBall.SetLinearVelocity(new b2Vec2(0, 0));
				_blueBallContact = false;
			}

			if(_redBallContact)
			{
				_redBall.SetPosition(new b2Vec2(500 / PIXELS_TO_METRE, 300 / PIXELS_TO_METRE));
				_redBall.SetLinearVelocity(new b2Vec2(0, 0));
				_redBallContact = false;
			}
		}
	}
}

EDIT: I would like to thank SticksStones for going to the effort to improve this code. You can find his version here. Please note that these Box2D tutorials are aimed at teaching the Box2D concepts and are in no way meant to be an indication of how you should structure your code unless otherwise stated. Quite often I will write a tutorial to minimize the amount of new/foreign code compared to the previous one. Of course this can come at the expense of code quality so please do not directly implement the code without thinking about how you can design it better! :)

That is it. This wraps up the basics of Box2D.

  • Cjboy1984

    Hello!
    Have you ever used this framework?
    http://www.sideroller.com/wck/

  • http://twitter.com/AllanBishop Allan Bishop

    Not as yet, although it looks like an attractive option for creating Box2D worlds. The ability to layout items via the FlashIDE could well improve productivity. I decided not learning that way just so that I was not reliant on the FlashIDE for development and to gain a good grasp of exactly what Box2D was doing.

  • Ivan

    Hi! I have problem here:

    import General.Input;

    I haven’t such class… Can anybody help me?

  • John

    Can you tell me how to work out the exact point of the collision please? I can’t figure it out. I can find the location of the two bodies involved in the collision, but I’d like to know the location where they have hit, so that I can toss in some particles at that point so it looks like the two objects collided with a little “bang”!

  • http://twitter.com/AllanBishop Allan Bishop

    Sure thing. I might update this tutorial with that info as it is useful, but in the meantime here is the answer. We make use of the b2Manifold for finding out the collision points. The following code goes in the contact listener function:
    ————————————————————————-

    var worldManifold:b2WorldManifold = new b2WorldManifold(); //create a new instance
    contact.GetWorldManifold(worldManifold); //the contact is the parameter of the function of course

    var points:Vector. = worldManifold.m_points;

    for each(var p:b2vec2 in points)
    {
    trace(“x”,p.x*PIXELS_TO_METRE,”y”,p.y*PIXELS_TO_METRE);
    }
    ————————————————————————

    Sorry, I can’t get rid of this so please just ignore it –>

  • http://twitter.com/AllanBishop Allan Bishop

    When you extract the zip of the Box2D package you should have 4 folders and a readme. Under the examples folder is the General package that contains the Input. Technically they are not part of Box2D engine but are useful and so are included.

    Also, for the tutorials I have written I modified the Input class a bit to make everything a bit more self contained (so far so good but if you run into troubles bear that in mind). You can find the modified file here http://blog.allanbishop.com/wp-content/uploads/2010/09/Input.as

  • Pingback: Box2D 2.1 Collision Point Demo/ Tutorial | Allan Bishop's Developer Blog()

  • SticksStones

    Hi Allan,
    I thought I should let you know that I’ve rewritten the code for tutorial without event dispatcher and made it easier to add as many balls as you like, in response to a question posted on the box2D forum’s.

    box2d.org/forum/viewtopic.php?f=8&p=27546#p27546

    I hope that’s ok. As far as I’m concerned much the code is yours, so you can do whatever you want with it.

    …I thought I should let you know and maybe you’ll have a use for it!

  • http://twitter.com/AllanBishop Allan Bishop

    That is fine =). Yes, setting a collide flag on the object directly, rather than dispatching and listening for an event is a much better solution. Thanks for fixing it!

    At the time I knew my approach was ugly (a public eventdispatcher variable – yuck!) but my aim is to help demonstrate the concepts of Box2D rather than (and in some cases, at the expense of) code structure.

    In saying so, perhaps people might take my code as gospel and copy it exactly, rather than taking the concepts and ideas to write their own code. So I think I need to address that. I might update this tutorial to mention that and have a link to the zip of your code to show how it can be improved on.

    It might also be a good reason to write a tutorial (or even series) that explores ways of structuring a game (with Box2D in mind), with good practices and taking into account some of the more popular game design patterns.

    Anyway thanks again :)

  • SticksStones

    I’d love a tutorial on finding out the strength of collisions, for applying damage to objects and playing sounds. I’m really struggling to find anything on it!

    Glad you like the code!

  • http://twitter.com/AllanBishop Allan Bishop

    ah sounds like a challenge :) I will see what I can come up with.

  • Mj_azani

    great post

  • splashdoodle

    Hi,
    Thanks for the codes.It is something im looking for.
    I would like to ask about using Bodytype as class to contain the string of names. Is there other way to do it?
    Thanks

  • Pingback: Richelle()

  • Pingback: Chantelle()

  • Pingback: sander()

  • Pingback: Vehmer()

  • Pingback: TadWinett()

  • Pingback: Chantelle()

  • Pingback: venzingS()

  • Pingback: Erederic()

  • Pingback: Ehantelle()

  • Pingback: kander()

  • Pingback: Xehmer()

  • your Blog is great

    i was puzzled about the virtual so i looked it up
    and learned that the “virtual” keyword does absolutly nothing in as3
    as3 reserve this word for an optional disigned of itself in the future
    i dont know where u took it from but virtual in c++ means that the function designed to be overriden in the future in java all function is virtual so there is no such keyword
    since as3 is based on java all function is virtual here as well

  • allanbishop

    Yup that is correct, the virtual keyword is just a place holder. BorisTheBrave who ported Box2D to Flash is the author of that and not me. I guess because Box2D started of as C++ he just ported it to look like the original even if the virtual keyword is pointless.

  • Comp

    Thanks. I’ve got an /Line 80 1067: Implicit coercion of a value of type flash.display:Stage to an unrelated type flash.display:Sprite.

    Running your version.

    I’ll see if I can figure out what it is.

  • Comp

    Thanks SticksStones ( & Allan) for the alternate code. But I see you’ve used some kind of object naming convention to create the elements. And involved a texture. It will take days to decipher this and implement it.

  • allanbishop

    Hmm that is the line with the code “_input = new Input(stage);”? If so then check the Input.as file. The constructor should be taking in a Stage type as the argument but it now sounds like it is a Sprite? You can either change the constructor argument to Stage and ensure that m_stageMc is also of type Stage. Or, you can have both as Sprite, and on line 80 pass in a Sprite (instead of stage) that has been added or will be added to the stage.

  • Comp

    I noticed the balls wouldn’t rotate. I set FixedRotation to false. I also set Density to 1.0, restitution to .1 and friction to .3. Then they rotated. The Box2D default density is 0.

  • Comp

    Okay, say I wanted to add another box in there which also made the balls disappear.

    Do I need a separate contact listener? I tried creating two GetUserData objects. I tried just adding the BOX definition to the UserDataObject with the ground info.

    It’s really not clear what is permitted to make this work.

    In other words, I want the balls to listen for contact with more than one object.

    I did it the logical way, creating the object the same as the ground, but it wouldn’t happen.

  • allanbishop

    We use a global contact listener which listens for all collisions in the world as SetContactListener accepts one b2ContactListener and not an array of them. The set and get user data is basically assigning ID’s so we can distinguish them easily in code. Here is some untested code to do what you want. The extra ground I am calling “GroundTwo” which you would set prior when creating the extra ground body.

    var fixtureUserDataA = contact.getFixtureA().GetBody().GetUserData();
    var fixtureUserDataB = contact.getFixtureA().GetBody().GetUserData();

    if(fixtureUserDataA==”Ground” || fixtureUserDataA==”GroundTwo” )
    {

    if(fixtureUserDataB==”BlueBall”)
    {
    eventDispatcher.dispatchEvent(new Event(BLUE_BALL_START_CONTACT));
    }
    else fixtureUserDataB==”RedBall”
    {
    eventDispatcher.dispatchEvent(new Event(RED_BALL_START_CONTACT));
    }
    }

    if(fixtureUserDataB==”Ground”|| fixtureUserDataB==”GroundTwo” )
    {
    if(fixtureUserDataA==”BlueBall”)
    {
    eventDispatcher.dispatchEvent(new Event(BLUE_BALL_START_CONTACT));
    }
    else fixtureUserDataA==”RedBall”
    {
    eventDispatcher.dispatchEvent(new Event(RED_BALL_START_CONTACT));
    }
    }

  • Comp

    How do you stop a MovieClip from looping over and over? I attached a MovieClip when the collision happens but it just plays over and over. I’ve tried everything to stop it I’ve tried stop(), unloadClip(), _visible, isPlaying, isEnabled, EVENT listener, addFrameScript, nothing will stop it from looping forever. There must be a way to stop movieClips.

  • allanbishop

    You can add a stop() on the last frame of the timeline inside the MovieClip. This will cause the MovieClip to stop on this last frame. To play it again call play() on the movieclip instance.

  • allanbishop

    Indeed. The density affects how the mass is calculated. If zero mass then a lot of things wont work properly. It is good that you highlighted this as it could trick people.

  • Comp

    I’ve been stuck on this as well. No matter what I end up with undefined method b2WorldManifold.

  • allanbishop

    Have you imported the b2Vec2 class? import Box2D.Common.Math.b2Vec2;

    If you are creating a b2Vec2 then the arguments it is expecting are an x and y position. If you are trying to implement the code snippet from in the comments you should not need to create a b2Vec2 yourself.

  • Alvin

    I’ve come a long way since two months ago. I’ve got a whole game with animations and everything.

    One thing I still can’t figure out. If I wanted to access the _balls array (or whatever object stores all the bodies) from a separate class like OtherClass.as with a bunch of other functions in it, how would I do this?

    I created a class OtherClass.as with functions that required the _balls array, and referenced the constants by Constants.PIXELS_TO_METRE. (put them in a constants class). But no matter what, I can’t get the Ball class involved.

    I tried public class OtherClass Extends Ball, Extends Event, Extends, MovieClip, Extends CollisionPointDemo. I tried mimicking the structures of BallContactEvent and Ball, because somehow BallContactEvent has access to __balls, and of course CollisionPointDemo as well.

    Finally I tried making _balls a static public vector or array and it finally could be referenced by Ball._balls. But then everything else in the program broke.

    I’ve read a number of explanations but really couldn’t find any similar enough to this situation. Most of the explanations are class/constructors but dont talk about the classes as separate FILES, which is what throws me off.

  • allanbishop

    Nice work! Ok so there a couple of ways to accomplish this with some better than others from a code design point of view. The Singleton design pattern is not a bad place to start. The basic idea is that you have a public static variable that holds a reference to an instance rather than making that static variable as you currently have.

    For example. You could create a class called BallMananger.as

    public class BallManager
    {
    var _balls:Vector;
    public static var Instance:BallManager;

    public function BallManager()
    {
    Instance = this;
    }

    //TODO addBalls function etc
    public function GetBalls():Vector
    {
    return _balls;
    }
    }

    Then in your OtherClass.as


    public class OtherClass
    {
    public function OtherClass()
    {
    //example of accessing balls
    var balls:Vector = BallManager.Instance.getBalls();
    }
    }

  • Lisbeth

    I used the function AddBubble() to make a function AddAnimation() which removes the current MovieClip and replaces with a different one (when a collision happens).

    But I can’t get the MovieClip to replace the old one. I can remove the original clip, but then I can’t add the second one. Or, I can add the replacement clip, but it is overlaid on the old clip. I’ve been looking at this code for days, and I can’t understand how the logic doesn’t apply. In the code, I remove the existing texture and then apply the new one. I’ve tried all kinds of variations and changing the order but no matter what, it won’t allow the new texture to be added if the old one is removed.

    public function addAnimation(clip:Class):void
    {
    // new texture
    var effect:MovieClip = new clip();

    //remove old texture
    removeChild(_texture);

    texture.addChild(effect as MovieClip);

    //sync up points
    _texture.x = _body.GetPosition().x * Constants.PIXELS_TO_METRE;
    _texture.y = _body.GetPosition().y * Constants.PIXELS_TO_METRE;

    _texture = texture;
    this.addChild(texture);

    updateTexture();

    }

  • allanbishop

    I assume it was the part 4 extend post that you are basing this on? The general structure is that I am using composition for displaying the Ball. I could have made the Ball class extend the texture movieclip but instead chose to make a texture that gets added to the empty Ball movieclip (I probably would change this to inheritance rather that composition if I rewrote this tutorial). Nevertheless, the DisplayList hierarchy looks like this:

    Stage->CollisionPointDemo->Ball->Texture->Bubble

    The code you have posted removes the texture, then adds the effect to the removed texture, then re-adds the texture. Indeed that does not need to happen (the source code for the demo does not have this??).

    For adding the effect you do not need to remove the texture. The texture is just the ball MovieClip. The effect (if it has been added to the ball MovieClip) is what you want to remove. There are a couple of ways of doing this. One is that you want the effect to play and then remove itself when it has finished. The easiest way is on the last frame in timeline of your effect movieclip is to remove itself from the parent.


    this.parent.removechild(this);

    Or if you want to manually remove it you could do something like:

    var effectRef:MovieClip; //add this member variable to your class. We will use it to store a reference to the effect so we can remove it easily.

    public function addAnimation(clip:Class):void
    {
    if(effectRef)
    {
    texture.removeChild(effectRef);
    }

    // new texture
    var effect:MovieClip = new clip();

    texture.addChild(effect);
    effectRef = effect; //update the reference

    //if the structure of your program is the same as the demo then you don't need to sync any of that stuff here

    }

  • Lisbeth

    Allan,

    Thanks, yes I posted in the wrong #4 tutorial, sorry.

    The code I used is slightly modified AddBubble, which if you recall gets the points between the collision and adds a sprite on those two points. I did manage to grasp the removal of the texture once it’s done.

    I basically have a monster head mc, and when a collision happens, I call addAnimation which loads a monster eating mc animation.

    The problem is I can still see the monster head underneath the monster eating animation.

    I’ll have to print out your answer and read through it. Thanks a ton!

  • allanbishop

    Oh so instead of having a texture and adding an effect on top you are swapping out the base texture with a new one? That being the case the above code will cause that. What you would want to do instead is:


    public function addAnimation(clip:Class):void
    {
    removeChild(_texture);
    var effect:MovieClip = new clip();
    _texture = effect;
    addChild(_texture);

    _texture.x = _body.GetPosition().x * Constants.PIXELS_TO_METRE;
    _texture.y = _body.GetPosition().y * Constants.PIXELS_TO_METRE;
    }

  • Lisbeth

    Oh my god! It worked. I’m kicking myself.
    Then to flip the static texture back on I can run a check during the updatePhysics() to check if the texture is missing and voila, seamlessly stick it back on.

    I owe you one! Your collision tutorial was what got me going on this thing. Now I’ve got explosions and all kinds of dynamics and interactions going on.

  • Lisbeth

    Ok I spoke too soon. It works. But in the last frame of the eating animation (actions panel) I have this:

    if(this!= null && this.parent != null)
    {

    var head:MovieClip = new PlayerHead();

    this._body.SetUserData(head);

    addChild(head);

    head.x = this._body.GetPosition().x * Constants.PIXELS_TO_METRE;
    head.y = this._body.GetPosition().y * Constants.PIXELS_TO_METRE;

    }

    It does replace the head after the eating animation .
    But then PlayerHead is no longer a reference found by the contact listener.
    I thought maybe I had to re-set the userData, but that strangely makes the head disappear.
    Almost anything I do except this code snippet, causes the head to disappear.

    I’ll keep experimenting with it. But if parent = playerhead and this = playereat, it stands to reason that addChild(this.parent) would work, but every way that seems logical is not working.

  • allanbishop

    Where does this code live, I know you said in the last frame but it seems to be accessing variables outside of it? If it is in the timeline then that can cause problems with references and general practice is to avoid that as much as possible.

    From the above I assume that when a collision occurs, an eating animation plays, then you want to swap it back with the default/idle animation?

    I would probably do something like:


    public function addAnimation(clip:Class):void
    {
    removeChild(_texture);
    var effect:MovieClip = new clip();

    //checking which type of animation it is
    if(effect is EatingAnimation)
    {
    effect.addFrameScript(effect.totalFrames-1, handleEndFrame);//this is an undocumented function that is not found in the Adobe manual
    }
    effect.play();

    _texture = effect;
    addChild(_texture);

    _texture.x = _body.GetPosition().x * Constants.PIXELS_TO_METRE;
    _texture.y = _body.GetPosition().y * Constants.PIXELS_TO_METRE;
    }

    //this function gets called on the last frame
    function handleEndFrame()
    {
    //use the addAnimation function to swap out the eating animation with the PlayerHead animation
    addAnimation(PlayerHead);
    }

  • Guest

    I spent 3 days messing with addFrameScript before chucking it. I doesn’t work in Flash Pro. It causes some kind of infinite loop, The animation works only the first one or two times, then stops working. I’m going to take a break with this, because I can see it will be several weeks to figure out this swapping of MovieClips. It might not be possible to swap MovieClips in Flash, is the only thing I can think of.

    I googled it and I don’t see any examples of anyone who ever swapped a MovieClip dynamically. Or changed which MovieClip was playing and went back to the old one.

  • Lisbeth

    I spent 3 days messing with addFrameScript before chucking it. I doesn’t work in Flash Pro. It causes some kind of infinite loop, The animation works only the first one or two times, then stops working. I’m going to take a break with this, because I can see it will be several weeks to figure out this swapping of MovieClips. It might not be possible to swap MovieClips in Flash, is the only thing I can think of.

    I googled it and I don’t see any examples of anyone who ever swapped a MovieClip dynamically. Or changed which MovieClip was playing and went back to the old one. It could also be a Box2D limitation.

    Either way, I’m giving up and just going to play the texture on top of the other texture.

  • allanbishop

    Hard to say what it is without seeing the source code but it is not a limitation of Flash or Box2D. The only other things I can think of is firstly change:


    removeChild(_texture);

    to

    _texture.stop();
    removeChild(_texture);

    Second, check that you don’t have code else where that is interfering. Is there any code on the timeline in your movieclips that you are adding?

    Lastly, check how often addAnimation is being called. It might be that the collision code is constantly firing if the colliding object is not being destroyed.