[ODE] How to hold objects (like in HL2)?

David Walters hidden.asbestos at googlemail.com
Tue Aug 8 07:23:05 MST 2006


> If I'm doing dSetPosition, then collision won't act on the object I
> hold. How to be then? So that I can hold it and it's affected by collision?
> I thought about adding forces, but then it won't stay at a certain position.
> What to do?

Hi, a "Half-Life 2" gravity gun was one of the first significant
features that I set myself the goal of achieving with ODE. I'll try
and explain here what I did to get a result with which I'm very
pleased with.

I would point out that what I ended up with was actually more like the
psychokinetic ability in Pandemic's "Destroy All Humans", in that
title the object has a more lazy drifting motion through space -
rather than the rigid effect of the gravity gun which pretty much pins
the object in front of you. Nevertheless, this approach is something
that I've tested extensively and it does work very well with ODE.


The basic procedure is as follows:

---

* Handle your character moving around the world as normal, I used a
sphere to begin with, but moved onto a capsule - it really doesn't
matter. So long as you have a world divided into things that are the
world, and things that can move around that have an ODE body attached.


* Handle player input to activate the gravity gun by creating a dRay
which starts at the centre of the player (probably offset slightly to
more accurately position it at the gun barrel) and extends in the
direction of aiming for a number of units.


* In your collision callback you need to treat ray / geom collisions
as a special case, i.e. instead of the usual contact joint creation to
get solid objects colliding you'd detect if "dGeomGetClass( geom ) ==
dRayClass" and handle it differently.

For this application you want to find the 'nearest colliding body
which isn't the player' which will be the target for your gravity gun.

The usual call to dCollide will return a number of collisions for the
ray, in this case contact 'depth' field actually indicates the
distance along the ray to the target. Ignoring the player's geom will
allow you to get the nearest thing you're trying to grab with the gun
simply using MIN.

btw. I'd recommend looking into the dGeomGetData call so that you can
assosicate a game specific structure to the objects you want to
interact with - in my engine if that data is NULL then the geom is
part of the world and will be ignored.


* After the collision checking has completed (don't forget to remove
the ray from the collision space), you should have the geom id for the
entity you want to pick up. Keep hold of this in your player structure
because you'll need it until you release the object.


* In your player update code, you have the target's geom id - from
this you can get the current body position. You also obviously have
your player's position and orientation. Using this information we can
compute a force vector to push and pull the object to the desired
point in front of the player.

The force you apply to the target body is simple to work out. You
compute a point along the direction you're aiming for a distance which
you can tweak and that I call the "capture distance", and this is the
desired position of the geom you're manipulating.

At this point I would point out that you would probably be alright to
simply call dBodySetPosition using this new position and you'd get a
result that is quite similar to "Half-Life 2". This is worth
experimenting with, but it must be noted that it wasn't really what I
was going for in appearance and so I haven't seen whether this is
reliable - or whether weirdness ensues when you bash a crate or
whatever into a wall...

Anyway, you have a desired position for your object, and you have the
current position. The force you apply is simply the vector between the
two scaled by some factor that you will have to manually tweak - this
force scaling factor is ideally multiplied by the mass of the target
object so that heavier bodies will move at roughly the same speed as
lighter ones.


* That's it! You should now be able to move around the world and the
target point for the captured object will update and push/pull the
object to it's new position. From then on It's all about tuning the
many variables that emerge from implementing this feature to get it
working just right.

---

Some additional notes:

The target movement is linear, so if your player makes a sudden 180
degree turn and the captured object is moving slowly towards it's new
target it's likely to smack you in the back of the head! - this can be
adjusted by, for example having the capture point higher up. This is
what I did as I was going for more of a psychic power and could be
more vague about where the object would be suspended.

Gravity will still affect the object during capture - this isn't
helpful as you want total control over the forces, and it is a
'Gravity Gun' after all. Simply call "dBodySetGravityMode" to disable
the object.

You can recreate the 'kick' and 'drop' features by simply stopping the
application of the correcting force and reenabling gravity. Optionally
you can disengage with a single large kick force along the aiming
direction vector. This is a great gameplay feature and looks really
cool!

In "Destroy All Humans" you can actually adjust the 'capture
distance', you'll need to fit the minimum and maximum ranges to your
particular environments of course. I've not added this feature but
it's not really any different to walking forward or back, so shouldn't
be a problem.


---


Anyway, I hope this explains everything, if you have any more
questions feel free to post them to this list, you're probably not the
only one trying to do this and there are probably better ways to do it
than what i've suggested so a discussion will benefit everyone.

Good Luck!

Regards,
Dave


More information about the ODE mailing list