- Home /
Is the CapsuleCast function broken?
Hi all,
I can't get Physics.CapsuleCast to work correctly at all. My goal is to do a basic check to see if a vertically-oriented, non-rotating capsule collider (That represents a 2-dimensional game character) is touching the ground (Represented by a cube) for a jump check. Here's the function I'm using, complete with my original code to perform this task which is commented out (But it did work faithfully).
The behavior I experience is that the function only returns true if the collider is floating about 0.5 units above the floor. If in contact with the floor, it returns false. If it is in the floor (for testing, I turned the collider off), then it returns false. If I move it further away from the floor than about half a unit, it returns false. If I adjust the radius that is passed into the function to account for the half unit disparity, it returns false. If I swap the two capsule points, it returns false.
In short, no matter which order I pass the two points, and no matter what radius I pass, and no matter how far I sweep the test, it only returns true if the collider is around 0.5 units away from the floor. It only seems to return true if the floor is on the very edge of the test! This makes no sense at all, and after reading documentation from other posters about this function, I'm led to believe that it's been broken from some time and hasn't been fixed for several versions of Unity.
I also checked the Physics settings in the editor, and it has a tolerance of 0.01.
Can anybody help? Am I using the wrong function here? Is the documentation inaccurate?
private bool isGrounded () {
/*
float realDistance = myGameObject.collider.bounds.extents.y + 0.1f;
if ( Physics.Raycast(myGameObject.transform.position, -Vector3.up, realDistance)
|| Physics.Raycast(new Vector3(myGameObject.transform.position.x + myGameObject.collider.bounds.extents.x, myGameObject.transform.position.y, myGameObject.transform.position.z), -Vector3.up, realDistance)
|| Physics.Raycast(new Vector3(myGameObject.transform.position.x - myGameObject.collider.bounds.extents.x, myGameObject.transform.position.y, myGameObject.transform.position.z), -Vector3.up, realDistance)) {
return true;
}
else {
return false;
}
*/
CapsuleCollider collider = myGameObject.GetComponent<CapsuleCollider>();
Vector3 position = myGameObject.transform.position;
Vector3 point1 = new Vector3(position.x, position.y - collider.height*0.5f+collider.radius, position.z);
Vector3 point2 = new Vector3(position.x, position.y + collider.height*0.5f-collider.radius, position.z);
//Debug.DrawLine (point1, point2, Color.red);
if (Physics.CapsuleCast(point1, point2, collider.radius, Vector3.down, 0.1f)) {
return true;
}
else {
return false;
}
}
Answer by Owen-Reynolds · Jun 04, 2013 at 08:42 PM
The two points in a capsule cast do not include the radius. They are the top and bottom of just the cylinder part. That's from memory, but also in the docs (which I did not read back then): "The capsule is defined by the two spheres with radius around point1 and point2"
So starting 0.5 units above the floor is actually scraping the ground (assuming a standard 0.5 radius.)
The other part is that *casts don't hit things they start inside, and w/o realizing the radius went 0.5 lower, many of your casts probably started below ground. My guess is shooting it just as it skims the ground might be hitting a tiny bump, or just being a weird edge case (technically, if I scrape against something, I am hitting it, sort of.)
Fix would be not to add the radius -- just 1/2 the height. If you look at Ex in the docs, they calculate p1 and p2 using half the height, no radius .
I appreciate the response Owen, but if you look at the code I posted, your suggestion is already how I'm calculating the two starting points... center of the collider, plus half the height, and $$anonymous$$us the radius.
Also, I stated clearly that the "ground" in this case is a cube. I will add here that the cube is not rotated at all, so it is completely flat. There are no tiny bumps to hit.
Just noticed you cast a vertical capsule straight down, so it's really only a sphere cast. But still, if you test a "down" capsuleCast with actual numbers, it works fine. Forwards cast seem to have a small bug that if p1.y>p2.y then the top and bottom spheres both have to hit.
But, again, try with some solid numbers. Seems to work fine. I suspect the center of the cast, transform.position
may be at your feet?
True, I should be using the center of the collider ins$$anonymous$$d of the game object's transform, but in this case they would be equivalent (The collider is centered at the transform). I did some debugs (printed out the vectors and drew a line that showed where both points are), and the points are indeed plotted correctly. I'm then casting down only 0.1 unit, which should register a collision with the floor... but it doesn't. It only registers as a collision when not in contact with the floor, and the collider is somewhere between 0.5 and 0.6 units above the floor. The radius on the collider is 0.5, and so is the radius on the cast. No parameter I can change alters this outcome.
I'll try again today, starting from scratch. $$anonymous$$aybe I've somehow misinterpreted the documentation, and the points are supposed to be at the EDGES of the capsule, not WITHIN the capsule. I saw another post regarding that yesterday.
Alright, so I tried from scratch using a spherecast ins$$anonymous$$d of a capsulecast, and the results are worse. The spherecast doesn't seem to register anything at all!
Am I doing something wrong here, or is there just some bug I'm running afoul of? Here's my latest code.... to test this, make a scene with the following items:
1 - 1x Capsule /w rigidbody & capsule collider
2 - 1x Cube /w box collider, elongated to simulate a floor
Attach the following script to the capsule:
using UnityEngine;
using System.Collections;
public class Test : $$anonymous$$onoBehaviour {
CapsuleCollider collider;
Vector3 point1;
float radius;
float distance = 0.1f;
// Use this for initialization
void Start () {
collider = gameObject.GetComponent<CapsuleCollider>();
}
// Update is called once per frame
void FixedUpdate () {
if (isGrounded() && Input.Get$$anonymous$$eyDown("space")) {
Debug.Log("Grounded");
gameObject.rigidbody.velocity = new Vector3(0.0f, 10.0f, 0.0f);
}
}
private bool isGrounded () {
Vector3 position = gameObject.transform.position;
point1 = new Vector3(position.x, position.y - collider.height*0.5f+collider.radius, position.z);
radius = collider.radius;
RaycastHit hit;
if (Physics.SphereCast(point1, radius, Vector3.down, out hit, distance)) {
return true;
}
else {
return false;
}
}
public void OnDrawGizmos () {
Gizmos.color = Color.red;
Vector3 sweepPoint = point1 + new Vector3(0, -distance, 0);
Gizmos.DrawWireSphere(sweepPoint, radius);
}
}
If the capsule is on the ground and you press the spacebar, it should write "grounded" in the log and jump. But it doesn't. This time I'm drawing a gizmo that represents the SphereCast. It appears where I think it should... is the spherecast doing something else internally? Why oh why can't Unity perform actual debugging on these kind of things? I would LOVE to know what's actually happening behind the scenes.
FixedUpdate can sometimes miss a Get$$anonymous$$ey operation (officially, they go in update.)
Problem seems to be the physics "sink in" tolerance. The spherecast was fine for me, BUT, not when it was at rest on the ground. A height 2 capsule comes to rest at 0.98, giving the "starts in ground, so ignore it" problem when the sphere/capsule exactly fits in the lower sphere.
A fix is not to cut it so close. Ins$$anonymous$$d of starting at the feet, start in the center and shoot to feet+0.1: start at pos and use a distance of HT/2-radius+0.1.