[howto] detect if a character stands on ground or jumps/falls

Jan 22, 2009 at 6:03 PM
Edited Jan 31, 2009 at 12:26 PM
here is a logic which notifies a character if he stands on ground stable, slips down a slope or floats in air (jumping, falling) which might be interesting for platformer games. this is a simplified version of my original code, it might not compile, but shows the idea. basically i check the sum of al contact normals against a angle range (f.e. between +45° slope to -45° slope). the moment of inertia should be float.PositiveInfinity in order for the character to not rotate and the "feet" of the character should be round.

public class Platformer : AbstractComponent
{
        public static readonly Message msgStabelized    = new Message( MSG_STABELIZED );
        public static readonly Message msgSlips            = new Message( MSG_SLIPS );
        public static readonly Message msgFloats        = new Message( MSG_FLOATS );

        enum STATE
        {
            Stabelized,
            Slips,
            Floats
        }



        Entity    Character;
        Vector2    MinVector;
        Vector2    MaxVector;
        STATE State = STATE.Floats;
        
        // minAngle and maxAngle are relative to UNIT_Y [-180°...+180°]
        // so -45° and +45° should be fine
        public PlatformLogic( Entity character, Shape feet, double minAngle, double maxAngle )
        {
            Character = character;
        
            feet.OnCollision    += OnCollision;
            feet.OnSeperation    += OnSeperation;

            double minRad = (-minAngle + 90.0) * (Math.PI / 180.0);
            double maxRad = (-maxAngle + 90.0) * (Math.PI / 180.0);

            MinVector = new Vector2( (float)Math.Cos( minRad ), (float)Math.Sin( minRad ) );
            MaxVector = new Vector2( (float)Math.Cos( maxRad ), (float)Math.Sin( maxRad ) );
        }

        // get the mean direction vector of the contact normals
        // and check if it is between minAngle and maxAngle (as vectors via cross product)
        public bool OnCollision( Geom g1, Geom g2, ContactList contactList )
        {
            Vector2 normal = Vector2.Zero;

            foreach( var contact in contactList )
            {
                normal += contact.Normal;
            }

            double minCrossP = normal.X * MinVector.Y - normal.Y * MinVector.X;
            double maxCrossP = normal.X * MaxVector.Y - normal.Y * MaxVector.X;

            bool isOnGround = minCrossP >= 0 && maxCrossP <= 0;
            if( isOnGround )
            {
                if( State != STATE.Stabelized )
                {
                    State = STATE.Stabelized;
                    msgStabelized.Send( Character );
                }
            }
            else
            {
                if( State != STATE.Slips )
                {
                    State = STATE.Slips;
                    msgSlips.Send( Character );
                }
            }

            return true;
        }

        public void OnSeparation( Geom g1, Geom g2 )
        {
            if( State != STATE.Floats )
            {
                State = STATE.Floats;
                msgFloats.Send( Character );
            }
        }
    }
}
May 13, 2009 at 5:31 PM
Edited May 13, 2009 at 11:20 PM

multiple Geoms issue:

the approach stated above only works with one platform Geom. to handle multiple Geoms merge with this: http://farseerphysics.codeplex.com/Thread/View.aspx?ThreadId=56162

flickering issue:

with the approach stated above you will note a "flickering" as the character will rapidly swap between stabelized and floating when running down a slope. to solve this issue do the following add a threshold Geom beneath the character which is activated on "Float" and sends corresponding messages dependend on the current y-veloctiy ("Downwards", "Upwards").

1. extend the states with "Downwards" (falling) and "Upwards" (jumping, flying up)

2. add the threshold Geom. a little rectangle should work fine.

3.

ThresholdGeom.CollisionEnabled = false;

ThresholdGeom.CollisionResponseEnable = false;

ThresholdGeom.OnCollision += OnThresholdCollision;

ThresholdGeom.OnSeperation += OnThresholdSeperation;

4. when setting the "Floats" state, do nothing with the character (like setting a falling animation), but set

ThresholdShape.Geom.CollisionEnabled = true;

5.

private bool OnThresholdCollision( Geom g1, Geom g2, ContactList contactList )
{
    ThresholdCollisionTicks = Ticks;

    return true;
}

private void OnThresholdSeparation( Geom g1, Geom g2 )
{
    if( ThresholdCollisionTicks < Ticks )
    {
        if( Body.Velocity.Y > 0 )
        {
            State = STATE.UPWARDS;
        }
        else
        {
            State = STATE.DOWNWARDS;
        }
    }
}

void Update( double dt )
{
    ++Ticks;

    if( State != LastState )
    {
        switch( State )
        {
            case STATE.STABELIZED:
                Console.WriteLine( "stabelized" );
                break;
            case STATE.SLIPS:
                Console.WriteLine( "slips" );
                break;
            case STATE.FLOATS:
                Console.WriteLine( "floats" );
                break;
            case STATE.UPWARDS:
                Console.WriteLine( "up" );
                break;
            case STATE.DOWNWARDS:
                Console.WriteLine( "down" );
                break;
        }

        LastState = State;
    }

    if( State == STATE.UPWARDS && Body.Velocity.Y < 0 )
    {
        State = STATE.DOWNWARDS;
    }
    else if( State == STATE.DOWNWARDS && Body.Velocity.Y > 0 )
    {
        State = STATE.UPWARDS;
    }
}

faster movement during jump issue:

as the linear drag ("air friction") is usually much, much lower than the platform friction, the character will move sideways much faster during jump. either deactivate sideway movement during jump (i think this sucks) or use the approach stated here: http://www.huesforalice.com/project/box2d_tinger

this approach ignores gravity and use a non-physics based movement (sets velocity directly) for sideway movement

even better approach:

i did not actually implement this myself - as the threshold approach is easy and works fine for me - but this would be even better: http://www.youtube.com/watch?v=EncfMb7M5vQ

i thought this tough thing through though (scnr :))

the character has two feet as rays where at least one always has to have length 1

[image:http://gamedev.fh-hagenberg.at/wiki/images/7/7b/PlatformLogic_-_Raybased_01.png]

sideway force is applied

http://gamedev.fh-hagenberg.at/wiki/images/3/33/PlatformLogic_-_Raybased_02.png

right foot collides with terrain...

http://gamedev.fh-hagenberg.at/wiki/images/f/fd/PlatformLogic_-_Raybased_03.png

...and length becomes 0.5. that's fine though because the other one still has 1

http://gamedev.fh-hagenberg.at/wiki/images/e/e9/PlatformLogic_-_Raybased_04.png

as the character moves further against the slope the left foot becomes < 1...

http://gamedev.fh-hagenberg.at/wiki/images/1/16/PlatformLogic_-_Raybased_05.png

...so the character has to be pushed up in order for one foot to become 1 again. this is the tricky part. in a physics based environment you can't just set the position (probably you could, as the translation is very small). a colleague of mine told me to use a "proportional–integral–derivative controller" which would approximate the correct force.

http://gamedev.fh-hagenberg.at/wiki/images/b/ba/PlatformLogic_-_Raybased_06.png

another special case is if the character is running down a slope

http://gamedev.fh-hagenberg.at/wiki/images/5/58/PlatformLogic_-_Raybased_07.png

...running down a slope...

http://gamedev.fh-hagenberg.at/wiki/images/6/6f/PlatformLogic_-_Raybased_08.png

the ray could be greater than 1...

http://gamedev.fh-hagenberg.at/wiki/images/6/66/PlatformLogic_-_Raybased_09.png

...so the character has to be pushed down

http://gamedev.fh-hagenberg.at/wiki/images/8/83/PlatformLogic_-_Raybased_10.png

 

argl, how the hell do i make picture previews in this forum?!?

Coordinator
May 13, 2009 at 9:22 PM

Great info yobiv! If we manage to get more howto's, I'll make a wiki page containing them all.

As for the images, take a look here: http://codeplex.codeplex.com/Wiki/View.aspx?title=Wiki%20Links&referringTitle=CodePlex%20Help%20Wiki

May 13, 2009 at 11:19 PM

from my part i can't think of any more howtos currently.

the markup in your link seems to work only for the wiki.

Coordinator
May 13, 2009 at 11:22 PM

Yeah, keep forgetting that. Let me know if you have any new ideas for tutorials.