Game Development Community

Platforming Basics - Jumping and collision

by Rich Wilson · in Torque X 2D · 02/06/2007 (6:51 pm) · 15 replies

I'm building the foundation for a platformer and I'm trying to work out some collision issues.

I'm planning on incorporating motion states into the character component (jumping, walking, swimming, climbing) so that I can check for the A button and then if the state is "walking","standing" etc. act accordingly, and if the state is "jumping" do nothing. (this is without consideration for double jump, which might call for a "jump apex" window and accompanying state)

So here's the rub. I would ideally change state to "jumping" when the button is pressed and back to "standing" when the character lands on the ground again. How do I check for a landing collision and then determine if that collision came from beneath the character or if he ran into the side of a wall in mid-air or something. Is there a better way to do this? (I kludged in a check to see if Y velocity is 0 to allow jumping, but this let in jumping at the apex of a previous jump) Some kind of general purpose thing might be handy, as I'll probably be doing wall-jump and ledge grab at a later point.

edit - I just considered mounting small scene object boxes to the feet and other areas of the character to detect specific collisions. This might be an overcomplicated way to approach the problem.

#1
02/06/2007 (8:25 pm)
Your OnCollision callback receives an object called CollisionInfo (or similar) and one of the pieces of information is the point at which the collision occurred and the normal. You could use one or both of those pieces of information in conjunction to determine if you "landed" on your bottom!
#2
02/07/2007 (6:31 am)
Ah, the elusive "ground check" ... yea, it's a pain. The ground checking code in the starter kit wound up around 500 lines. I went with a seperate collision image based on the character's collision image and called into the poly image checks directly with a tailored position and velocity to get the info I needed. Ground checking is definitely not an easy task - especially if you want to allow for moving and sloped surfaces and conditional collisions. The order you do things is pretty important, so be careful of how and when you apply forces.

Like Ben said, if you want a simple solution you can just use the normal of the collision. If the Y component of the surface normal is less than -0.2 or so, it's pretty much facing up. A simple normal check should be fairly consistant, though you might run into some timing issues because your ground checks may be happening at arbitrary times based on your process order and you might not get consistant collisions when moving parallel to the ground surface.
#3
02/07/2007 (1:44 pm)
There's also the possibility of your character walking off of a ledge without ever pushing the jump button. So you might need a "falling" state as well.
#4
02/07/2007 (2:34 pm)
Collision location and normal, how convenient! I'll definitely check this out. I'm beginning to notice why most beginner projects are shooters or some kind of puzzle game. Physics and environment collision can be a pain. I'll post again once I've taken a shot at this implementation.

@Casey: Good point about falling. I wouldn't want the player triggering a double jump from a fall or something like that.

Maybe I'll be able to wrap this all up in a nice component I can distribute to others once it's finished. I plan on pairing this component with trigger region components with a RegionType property like "ladder" or "water" that can change the player's state upon entering them, but I'm sure I'll be divulging more info on that system later in another post as I inevitably run into problems.
#5
02/08/2007 (11:36 am)
I've added collision methods to my player control component, making it extend T2DCollisionComponent as well as ITickObject. I did this so I could have the OnCollision handler to detect collision with the environment and use that info to change the player state.

Since I brought the collision component into the player component, however, the player no longer collides with anything. It just falls through the world (I have a hacked in gravity force added on tick).

I left the default collision component in, but that didn't work, so I removed it. I exposed the collidesWith part from the collisioncomponent bits of my player component and included t2dTileLayerType and TileType as I had been using with my regular collision component, but to no avail.

I set a breakpoint in my OnCollision callback, and it wasn't firing during any part of this process.

In overriding OnCollision, did I cut out the code that tells the object how to react to the collision physically? How would I ideally integrate this collision functionality into a player component? I want to keep it all under one roof if possible (collision checking, player control, player stats, etc.) since all of the pieces will need to talk to each other. (changing states based off of collision, jump height based off of strength stat, etc.)
#6
02/08/2007 (2:51 pm)
The player physics in the starter kit are state-based aswell. The way I decided to approach it was to define a set of states and allow them to evaluate the player object before they process its physics. The ground check happens after the player's forces are not only assigned, but also applied, to make sure that the ground check is always accurate.

For collision, I've found that it's best to allow the collision component to do it's thing. If collisions stopped happening, it can be caused by one of several things: your platforms' object types aren't getting set properly, your player's CollidesWith property isn't getting set properly, you have SolveOverlap set to false on either your player or your platforms, or your collision polygons aren't being created properly.

As for OnCollision, there is no default OnCollision method to override. T2DCollisionComponent has a public property that stores a delegate, and the property is named OnCollision. It doesn't really make sense to derive your player class from T2DCollisionComponent because the "is a" relationship doesn't really exist there. The collision component is set up to allow you to assign a delegate, on your player component or wherever, that handles what to do when a collision event happens with the components' owner. There's also a resolve delegate that allows you to assign a custom physics resolve method. You can modify the resolve that will be used in your OnCollision delegate. What I'm getting at is that the T2DCollisionComponent exposes all the functionality you need to be able to process collisions from a seperate component and allow it to be self-contained, so the first thing I'd do would be separate your player and its collision component, and then try to pin down the collision problem.
#7
02/08/2007 (3:29 pm)
How do I expose a delegate to the collision component once I've added it to the player object? I've seen the OnCollision drop down list, but I don't know how to get my custom callback function to show up in that list.
#8
02/08/2007 (10:00 pm)
To get a delegate to show up in the list you need a static public property that stores it. For your purposes, it's much easier to just assign it in your player component's _OnRegister method though (named _InitComponent in the current open beta).

Something like the following would work (in your player component):

protected override bool _OnRegister(TorqueObject owner)
{
    if (!base._OnRegister(owner))
        return false;

    if (owner is T2DSceneObject && (owner as T2DSceneObject).Collision != null)
        (owner as T2DSceneObject).Collision.OnCollision = MyOnCollisionDelegate;

    return true;
}

This of course assumes that you have a method named MyOnCollisionDelegate on your player component. You can call it whatever you want, but it must follow the profile of a T2DOnCollisionDelegate.

Use the following as an example:

public void MyOnCollisionDelegate(T2DSceneObject ourObject, T2DSceneObject theirObject, T2DCollisionInfo info, ref T2DResolveCollisionDelegate resolve, ref T2DCollisionMaterial physicsMaterial)
{
    Console.WriteLine("Hey, I got a freaking collision!");
}

Hope this helped.

Edit: Formatting.
#9
02/09/2007 (8:35 am)
TorqueX doesn't seem to want to recognize _OnRegister. I had this same problem in another thread as well. I get a "no suitable method found to override" compile error, and I can find no reference to OnRegister in the TX Help (TorqueX API.chm)

Is this the kind of thing that could go under InitComponent, or is the unavailability of OnRegister a bug of some sort?

Edit: The callback assignment worked when I put the code in my InitComponent method.

Couple of things.
For some reason, it makes me uncomfortable to put code in one component that assumes the existence of another component, even though the check is in place to assure it's there before proceeding. Is this standard protocol?

For anyone trying to add the above code, "owner as T2DSceneObject.Collision" by itself will throw an error. You have to add parenthesis like this: "(owner as T2DSceneObject).Collision" Just a minor nitpick, but could provide a bit of a hangup for some.

Anyway, things are working now. Thanks for the help. Onward to the next stumbling block...
#10
02/09/2007 (9:17 am)
Rich, in some version of TorqueX after the open beta version they changed _InitComponent to _OnRegister. They're functionally identical, which one you use just depends on which version of TorqueX you're working with.
#11
02/09/2007 (9:29 am)
On the topic of whether you should be uncomfortable using components like that, as long as the interaction between the components is through well defined interfaces you're still using the programming model properly. The idea of components is that they are encapsulated chunks of functionality that can be easily re-used and swapped in and out for different "objects" in your game. So, say, if you have an AI component that interacts with your movement component for an enemy, you don't need to rewrite your movement stuff if you decide to use a different AI. And maybe you can use the same movement component with a component that gets user input instead of AI input. So in a lot of cases, component interaction is encouraged. As long as you put the checking in there to make sure the component exists, you're doing it right (also, in later versions of TorqueX, there's a way to tell TGBX about component dependencies, so if you add one that depends on a different one it will pop up a box that says "add this one, too?").
#12
02/09/2007 (10:21 am)
@Rich - Dan is correct. Since you are posting here and I don't see you posting in the closed beta forums, I'm assuming you are not using the version fo TXE which uses _OnRegister. Use _InitComponent instead in those place and you'll be set.




www.linkedin.com/img/webpromo/btn_viewmy_160x25.gif

www.mmogamedev.info/images/imgdc_ad1.gif
#13
02/09/2007 (11:33 am)
Sort of a fork in the discussion here.

Part 1: What do I do when code I have in the InitComponent method tells me that it can't be called after an object is registered? The example for this is trying to add a force component to the object for a constant gravitational pull, ala some other post I've seen on the board. It works if you're creating the object from code, I suppose, but when it's in a component on an already existing object, brought to life by the deserializer, then it's already registered by the time it gets to the code.
The specific example is:
T2DForceComponent fc = new T2DForceComponent();
            T2DForceComponent.Force f = new T2DForceComponent.Force();
            f.InitialStrength = -10;
            f.ConstantDirection = -1;
            f.ConstantDirectionIsWorldSpace = true;
            f.MinStrength = 1;
            f.MaxStrength = 2;
            fc.AddForce(f);
            owner.Components.AddComponent(fc);
The game chokes on the last line.

Part 2:
How do I invoke a method in a component from a completely separate object? I have a trigger region that fires when its OnEnter method is called. From there, I'm checking to see if the object holds my player component class.

public void EnterCallback(T2DSceneObject ourObject, T2DSceneObject theirObject)
        {
            if (theirObject.Components.FindComponent<PlayerComponent>() != null)
            {
                //do something here
            }
        }

From that point, I want to call a method within the PlayerComponent component that will change a MovementState string variable. Let's say the method is called ChangeMoveState(string newState)

As far as I've delved in the docs, I came up with this:

theirObject.Components.FindComponent().Components.GetComponentByIndex(0).//somehow execute method here

I could have stored the component collection in the initial query in a variable so I could just reference it within the if block, but I haven't streamlined it yet.
#14
02/09/2007 (11:35 am)
Oops, my bad. I forgot about that name change. And that closing parenthesis that was misplaced was fixed in the above post.

Dan is correct in what he was saying about component interaction. It's a very common mistake to fall back on inheritence when it's not called for. Seeing as how TX has a built-in containment system (components), it's definitely much better practice to componentize your code. It might seem a little counter-intuitive at first, but if you're anything like me it will eventually click and you'll come to love working with components.
#15
02/09/2007 (11:46 am)
1) That's right, you can't add components to objects that are already registered with the scene. This is intentional. The idea is that components and object types define what an object is and that should absolutely not change after the object is in the scene. I haven't worked with the force component, but I believe you can add the force component to the object without specifying any forces in the level builder and then later add forces in code.

2) I think you're looking for something like this:

PlayerComponent player = theirObject.Components.FindComponent<PlayerComponent>();

if(player != null)
    player.ChangeMoveState("breakdance");

One thing to note is that the component is stored locally so FindComponent doesn't have to be called again when the component is found. Also, that FindComponent returns a reference to the actual component itself. The long version, just for clarity, would look like this:

theirObject.Components.FindComponent<PlayerComponent>().ChangeMoveState("potty");

You wouldn't want to do this in practice because it makes a lot of assumptions. You also want to make as few calls to FindComponent as possible. It's not something to lose sleep over, but these find calls can quickly stack up if you're doing lots of them per tick. One way around it is to assign an object type to your player in its _InitComponent (_OnRegister) method so it can be identified without using FindComponent.

Here's how you would set it:

_sceneObjectOwner.SetObjectType(SomePlayerObjectType, true);

And here's the modified check:

if(theirObject.TestObjectType(SomePlayerObjectType))
{
    PlayerComponent player = theirObject.Components.FindComponent<PlayerComponent>();

    if(player != null)
        player.ChangeMoveState("breakdance");
}