Jumping - (side scroller)

Dec 6, 2009 at 5:04 PM

Hi.

I'm new to Farseer and i'm trying to detect whether the player stands on the ground or not.

 

I have read:

http://farseerphysics.codeplex.com/Thread/View.aspx?ThreadId=64997

and

http://farseerphysics.codeplex.com/Thread/View.aspx?ThreadId=44960

But I still don't understand how to detect wheter the player stands on the ground.

 

My very simple sprite code:

 

    public class Sprite
    {
        public Body Body { get; set; }
        public float Width { get; set; }
        public float Height { get; set; }
        public Texture2D Texture { get; set; }
        public Geom Geom { get; set; }

        public Sprite(PhysicsSimulator PhysicsSimulator, Texture2D texture, Vector2 position, float width, float height, bool isStatic)
        {
            this.Texture = texture;
            Body = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, width, height, 1f);
            Body.Position = position;
            Body.IsStatic = isStatic;
            this.Geom = GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, Body, width, height);

            this.Width = width;
            this.Height = height;
        }

        public void Draw(SpriteBatch spriteBatch, Camera camera)
        {
            if (Texture != null)
                spriteBatch.Draw(Texture, Body.Position - camera.Position, Color.White);
        }
    }

I was looking for something like: this.Geom.OnCollision+= et....   But it is not an event..

Here is my Player-class code:

    public class Player // todo: how to check if player stands on ground?
    {
public Sprite Sprite { get; set; }
        Input input = new Input(PlayerIndex.One);
        public Level Level { get; set; }
        public bool IsOnGround { get; set; }

        public Player(Level level, PhysicsSimulator PhysicsSimulator, Vector2 position)
        {
            this.Level = level;
            this.Sprite = new Sprite(PhysicsSimulator, null, position, 32, 64, false);
            HookKeys();
        }


        public void Update(GameTime gameTime)
        {
            IsOnGround = ?????????????????
            CheckKeys();
            CheckCamera();
            input.Update();
        }

.......
}


What is the best solution to handle this?

 

 

 

Dec 6, 2009 at 5:25 PM
Edited Dec 6, 2009 at 5:28 PM

For a start, you should remove Body and Geom from the Sprite class and put them in the Player class since they'll be used for the player collisions, and a player can have several sprites depending on what is happening. On the assumption that you are updating the PhysicsSimulator somewhere else, you will need to add OnCollision and OnSeperation events to the Geom that represents the player. These will let you know when the player collides with something, and when he separates from something. NOTE: I'm still learning Farseer myself so any corrections to this would be greatly appreciated.

So, first you will have 2 methods similar to these:

private bool OnCollision(Geom g1, Geom g2, ContactList contactList)
{
    return IsOnGround = true;
}

private void OnSeparation(Geom g1, Geom g2)
{
    IsOnGround = false;
}

Then, to hook them up to the Geom you will use:

Geom.OnCollision += OnCollision;
Geom.OnSeparation += OnSeparation;

Now whenever the Geom representing the player hits something IsOnGround will be set to true, and when he separates from something IsOnGround will be set to false.

Dec 6, 2009 at 5:45 PM

Thanks for the fast reply. It indeed works. However, it has a nasty side-effect:

When the player jumps against a vertical wall or against the ceiling then the isonground will be set to true. Meaning that the player can jump again. This way the player can float under the ceiling while having the jump button pressed.

So that's why I should add another geometry at the feet so we can detect whether the feet collide? And by making that feet-geometry 2f smaller (1f for the left side and 1f for the right side) it can never collide with a vertical wall? Is this the way to go?

Dec 6, 2009 at 6:23 PM

Yes, sounds like a good idea. I think there's a specific type of geometry for that, but I'm not sure.

Dec 7, 2009 at 10:19 AM
Edited Dec 7, 2009 at 2:08 PM

in http://farseerphysics.codeplex.com/Thread/View.aspx?ThreadId=44960 they are talking about using rays for the feet.

 

So far I have 3 options when implementing the feet:

  • using rays
  • using a sensor
  • using another geom and attach it to the player's geom so that it moves alike.

 

> The problem with a sensor is that it cannot detect collision with statics (so I read) when moving it arround. So this one is not an option anymore.

> A Geom could work. Here is the Geom-solution that i got so far (the feet offset might be slightly off). The only problem here is that the player is always allowed to jump...

    class PlayerTEST    {
PhysicsSimulator PhysicsSimulator;
PhysicsSimulatorView simView;
Body body;
Geom[] geom;
bool IsOnGround;

public PlayerTEST()
        {
// f-engine
PhysicsSimulator = new PhysicsSimulator(new Vector2(0, 20));
simView = new PhysicsSimulatorView(PhysicsSimulator);
simView.LoadContent(Engine.game.GraphicsDevice, Engine.game.Content);

// player test
IsOnGround = false;
const float feet_x_offset = 2f;
const float height = 64f;
const float width = 32f;
body = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, width, height, 1);
geom = new Geom[2];
// geom[0] = player's geom
// geom[1] = feet geom
geom[0] = GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, body, width, height);
geom[0].Tag = "player";
geom[1] = GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, body, width - (feet_x_offset * 2), height / 2, new Vector2(feet_x_offset, height * 0.75f-height/2), 0f);
geom[1].Tag = "player_feet";
body.Position = new Vector2(50, 50);
geom[1].OnCollision += OnCollision;
body.Position = new Vector2(50, 0);

// platform test
Body platform = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, 500, 16, 1);
GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, platform, 500, 16);
platform.IsStatic = true;
platform.Position = new Vector2(10, 250);
// platform test
Body wall = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, 32, 256, 1);
GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, wall, 32, 256);
wall.IsStatic = true;
wall.Position = new Vector2(260 - 32, 128);
}


private bool OnCollision(Geom g1, Geom g2, ContactList contactList)
{
return IsOnGround = true;
}

private void OnSeparation(Geom g1, Geom g2)
{
IsOnGround = false;
}



        public void Update(GameTime gameTime)
{
PhysicsSimulator.Update((float)gameTime.ElapsedGameTime.TotalSeconds);

//input
KeyboardState keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.Right))
body.ApplyForce(new Vector2(50, 0));
if (keyState.IsKeyDown(Keys.Left))
body.ApplyForce(new Vector2(-50, 0));
if (IsOnGround && keyState.IsKeyDown(Keys.Up))
body.ApplyImpulse(new Vector2(0, -5));
}


public void Draw(SpriteBatch spriteBatch)
{
simView.Draw(spriteBatch);
}
}









> As for the rays, I still haven't found a good tutorial for it.

 

Can anyone perhaps point out a good tutorial for jumping, taking slopes, ceilings and walls into account for a simple guy like me? Or perhaps tell me what I'm doing wrong? I'm a C# programmer but I'm new to game programming. Thanks in advance.

Dec 8, 2009 at 1:37 PM

After some more tries I finally came up with the code below. The code works fine on anything except on very steep slopes. But that can be fixed by lowering the offset of the feet-geom.

I solved the feet to player collision by assigning different collision categories to the both of them. I hope it's the right approach.

 

  
    class PlayerTEST
    {
        PhysicsSimulator PhysicsSimulator;
        PhysicsSimulatorView simView;
        Body body;
        Geom gBody;
        Geom gFeet;
        bool IsOnGround;

        public PlayerTEST()
        {
            // f-engine
            PhysicsSimulator = new PhysicsSimulator(new Vector2(0, 20));
            simView = new PhysicsSimulatorView(PhysicsSimulator);
            simView.LoadContent(Engine.game.GraphicsDevice, Engine.game.Content);

            // player test
            IsOnGround = false;
            const float feet_x_offset = 2f;
            const float feet_y_leap_out = 2f;
            const float height = 64f;
            const float width = 32f;
            body = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, width, height, 1);

            gBody = GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, body, width, height);
            gBody.Tag = "player";
            gBody.CollisionCategories = CollisionCategory.Cat2;
            gBody.CollidesWith = CollisionCategory.Cat1;

            gFeet = GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, body, width - (feet_x_offset * 2), height / 2, new Vector2(0, (height * 0.75f - height / 2) + feet_y_leap_out), 0f);
            gFeet.Tag = "player_feet";
            body.Position = new Vector2(50, 50);
            gFeet.OnCollision += OnCollision;
            gFeet.OnSeparation += OnSeparation;
            gFeet.CollisionCategories = CollisionCategory.Cat2;
            gFeet.CollidesWith = CollisionCategory.Cat1;
            body.Position = new Vector2(50, 0);
            body.MomentOfInertia = float.MaxValue; // prevent rotation

            // platform test
            Body platform = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, 96, 16, 1);
            GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, platform, 96, 16);
            platform.IsStatic = true;
            platform.Position = new Vector2(64+16, 256+8);
            // diagonal platform test     
            Vertices vertices = new Vertices();
            vertices.Add(new Vector2(0, 0));
            vertices.Add(new Vector2(128, 64));
            vertices.Add(new Vector2(128, 128));
            vertices.Add(new Vector2(0, 128));
            Body platform2 = BodyFactory.Instance.CreatePolygonBody(PhysicsSimulator, vertices, 1f);
            GeomFactory.Instance.CreatePolygonGeom(PhysicsSimulator, platform2, vertices, 0);
            platform2.IsStatic = true;
            platform2.Position = new Vector2(128+64, 256 + 8+64);
            
            // wall right
            Body wallRight = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, 32, 256, 1);
            GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, wallRight, 32, 256);
            wallRight.IsStatic = true;
            wallRight.Position = new Vector2(256+16, 128);
            // wall left
            Body wallLeft = BodyFactory.Instance.CreateRectangleBody(PhysicsSimulator, 32, 256, 1);
            GeomFactory.Instance.CreateRectangleGeom(PhysicsSimulator, wallLeft, 32, 256);
            wallLeft.IsStatic = true;
            wallLeft.Position = new Vector2(16, 128);
        }

        private bool OnCollision(Geom g1, Geom g2, ContactList contactList)
        {
            //if (g1.Tag.ToString() != "player" && g2.Tag.ToString() != "player")
                return IsOnGround = true;
            //else
            //    return false;
        }

        private void OnSeparation(Geom g1, Geom g2)
        {
            IsOnGround = false;
        }

        public void Update(GameTime gameTime)
        {
            PhysicsSimulator.Update((float)gameTime.ElapsedGameTime.TotalSeconds);

            //input
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Right))
                body.ApplyForce(new Vector2(50, 0));
            if (keyState.IsKeyDown(Keys.Left))
                body.ApplyForce(new Vector2(-50, 0));
            if (IsOnGround && keyState.IsKeyDown(Keys.Up))
                body.ApplyImpulse(new Vector2(0, -20));
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            simView.Draw(spriteBatch);
        }
    }

Dec 8, 2009 at 3:52 PM
Edited Dec 8, 2009 at 3:52 PM

It's kind of unnecessary imo to have an extra geom, raycasting or sensors as feet just to check if your player is standing on the ground and not a wall.

The simplest solution, and it works great for me, is just to check either the hit geom (from OnCollision) or the position of the contact and compare it to your player. If the geom or hit is below your player, you are standing on your feet.

Having checks like this you can always add more detailed checks also, like if your standing on an edge, colliding with a wall etc.

Dec 8, 2009 at 4:15 PM
Edited Dec 8, 2009 at 4:19 PM

Sounds like a great idea. But how do I implement that with advanced geometries like polygons? For example:

ASCII drawing:

 

  00
P 00
000000
000000
  00
  00

 

When the player (P) hits the polygon (the zero's) then how do I check whether that specific part of the polygon is below the player? The same problem would arise with a U-shaped polygon with the player falling inside it.

 

 

P.S.

I just found out that Adding: gFeet.IgnoreCollisionWith(gBody); to my previous post makes it possible to put both the body and the feet in the same collision category (and also making more sense).

The same goes for: vertices.AddRange(new Vector2[] { new Vector2(0, 0), new Vector2(192, 302), new Vector2(192, 512), new Vector2(0, 512) }); // Now it's in 1 line of code.

Dec 8, 2009 at 5:37 PM

I've never had to do this before, so it's a total guess, but... you could try checking the contact list that is given in OnCollision, and see if any of the normals point in the general upward direction.  You would then also need to keep a list of these geoms so they can be removed on seperation.  The IsOnGround field could be replaced with a read-only property that checks to see if said geom list is empty.

I don't have VS around to help me with intellisense right now, so here's some pseudo pseudocode. =p

List<Geom> groundGeoms = new List<Geom>();

bool OnCollision(Geom geom1, Geom geom2, ContactList contacts) {
  var ground = contacts.ForAny(contact => contact.Normal.Y > Math.Abs(contact.Normal.X))
  if (ground && !groundGeoms.Exists(geom2)) groundGeoms.Add(geom2);
  return true;
}

void OnSeperation(Geom geom1, Geom geom2) {
  if (groundGeoms.Exists(geom2)) groundGeoms.Remove(geom2);
}

bool IsOnGround { get { return groundGeoms.Count > 0; } }

I also can't remember if geom1 and geom2 can become switched... bah.  Sorry if this doesn't work. =[

Dec 9, 2009 at 12:31 AM
Edited Dec 9, 2009 at 10:23 AM

Yota's Idea for the checking the normal of the collisionpoint is a great idea, hadn't thought of that, gonna test it out later. Since the OnCollision is run every frame that you collide with something it's not really needed to check the OnSeperation imo, just set the value to false each frame and do the check each frame since your OnCollision method already will be called each frame, but both ways would work just as well I guess..

Also yes, geom1 and geom2 can be the switched around, but just check if geom1 or geom2 is the one connected to the player.

Regarding polygon shapes and other complex forms it's all the same really. Either you check the collisionpoint from contactlist, there you can compare the position compared the your players AABB,  or it's position compared to the targetted object.. or the normal of the contact like Yota suggested. If you've got concave meshes like your asci drawing suggests though, you'll probably want to check the normal as the AABB isn't a good enough approximation of your geoms relative positions.

 

Dec 9, 2009 at 2:11 PM
Edited Dec 9, 2009 at 2:19 PM

Let's see if I understand it correctly (took me some time to figure it out :P):

 

OnCollision (Yota's post): 

1. Create an enumerable variable called ground that contains all geom's that are below the player using LINQ.

2. if (ground && !groundGeoms.Exists(geom2)) groundGeoms.Add(geom2); // <-- I don't understand this part. I only understand the "groundGeoms.Add(geom2);" part.

3. Return true ofcourse since we are standing on 1 or more geom's.

 

The OnSeperation simply removes the seperated geom from the list of ground-colliding geom's so that the IsOnGround getter functions correctly.

 

Alright that seems nice. I don't know whether it performes better than adding another geom on the feet. Because not only would I apply this game logic to the player but also to every enemy (an enemy must also know when he reaches a cliff, a wall, bounces against the player, etc). So right now my enemies have their normal geom and their left, right and feet geom's. So performance is important. Altough Yota's solution looks a lot cleaner in the PhysicsSimulatorView (only 1 geom per player/enemy instead of 4-5).

 

I guess that Yota's approach also works for detecting player-vs-wall-collision, It's just a matter of replacing the   contact => contact.Normal.Y > with contact => contact.Normal.X >   and   contact => contact.Normal.X <   I suppose?

 

@jsmars: I don't understand what you mean by "you'll probably want to check the normal as the...". How can a normal fix the concave meshes problem? I think my knowledge on Mathematics abandones me there.

 

P.S.

@jsmars: Thanks. You're right. The onseperation event is not needed when using the 'geoms approach' by simply placing IsOnGround=false at the end of the update() procedure.

 




Dec 9, 2009 at 2:35 PM
Edited Dec 9, 2009 at 2:45 PM

Sorry for my previous post, the text got kind of confusing.

The solution you got out of it is more expensive than simply having a foot geom though, so that's not quite right.

I think you should try Yota's solution with checking to see if the arbiters normal is facing the direction you count as ground directions, because really this is the best overall solution. This works for walls and everything else too. I think I might look into changing my own solution to this because it may work better than my current one.

My other alternative is also very simple, but mainly works convex objects, as I use SAT I don't really look into concave objects as any concave polygons are converted into convex. What I did was I simply compared the AABB's to see which edge has collided by looking for the two edges which are closest. (if the difference between my players bottom value and the targets top value is the smallest, your standing on top of the target.

Oh and for both solutions you'll need some way to know if the geoms you are looking at are you players geoms, I've referenced each Geoms "parent" game object within the geom itself, but there may be better ways to do this.