- Home /
Player sinking into walls and floor with Raycasts
I'm pretty new to Unity, and I've been trying to make a basic 2D platformer using 2d Toolkit. I tried using the Character Controller, but I need the precision of a box collider, I can't use the capsule collider that is built into it.
I found a blog post about how to make a 2D Platformer using raycasts to simulate collisions, and I've tweaked the code considerably to get it working (the code from the blog post simply doesn't work at all, variables are missing and all sorts of stuff is wrong).
The edited code kind of works, but I'm having some problems with it. The character sometimes "sinks" into the ground more than others, and sometimes just flat out ignores the wall raycasts. In addition, whenever the wall raycast does block the character, it gives me the error "NullReferenceException: Object reference not set to an instance of an object" in the console, pointing at lines 95, 56, 79, and 36.
Does anyone have any idea what my problem might be? I've been trying to get this working for days.
var velocity = Vector2(0, 0);
var maxVelocity = Vector2(0.35, 2.35);
var gravity : float;
var onGround = false;
var onLeftWall = false;
var onRightWall = false;
function FixedUpdate () {
//Limits the speed to the maxVelocity
if (velocity.x > maxVelocity.x) velocity.x = maxVelocity.x;
else if (velocity.x < -maxVelocity.x) velocity.x = -maxVelocity.x;
if (velocity.y < -maxVelocity.y) velocity.y = -maxVelocity.y;
//Instatiate the raycast variables
var LFloorRay = new Ray(collider.bounds.center - collider.bounds.extents + Vector3(0,1,0), Vector3.down);
var LFloorHit : RaycastHit;
var LFloorWallRay = new Ray(collider.bounds.center - collider.bounds.extents + Vector3(0,1,0), Vector3.left);
var LFloorWallHit : RaycastHit;
var RFloorRay = new Ray(collider.bounds.center - Vector3(-collider.bounds.extents.x, collider.bounds.extents.y, collider.bounds.extents.z) + Vector3(0,1,0), Vector3.down);
var RFloorHit : RaycastHit;
var RFloorWallRay = new Ray(collider.bounds.center - Vector3(-collider.bounds.extents.x, collider.bounds.extents.y, collider.bounds.extents.z) + Vector3(0,1,0), Vector3.right);
var RFloorWallHit : RaycastHit;
var player : Transform;
//check to see if the Bottom Left Floor raycast hits a "Solid" platform
if (Physics.Raycast (LFloorRay.origin, Vector3.down, LFloorHit, 1))
{
if (LFloorHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (LFloorRay.origin, LFloorHit.point, Color.red);
if (onGround == false)
{
onGround = true;
velocity.y = 0; //If it hits a "Solid" platform, then make its vertical speed 0
//and place the object ontop of the platform, but only if it wasn't already on a solid platform
player.position = Vector3(player.position.x, LFloorHit.point.y - (collider.bounds.center.y + collider.bounds.extents.y), player.position.z);
}
}
else //if the object is not "Solid", then ignore it
{
onGround = false;
velocity.y -= gravity * Time.deltaTime;
}//If bottom left doesn't hit, check the bottom right
} else if (Physics.Raycast (RFloorRay.origin, Vector3.down, RFloorHit, 1))
{
if (RFloorHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (RFloorRay.origin, RFloorHit.point, Color.red);
if (onGround == false)
{
onGround = true;
velocity.y = 0; //same as above
player.position = Vector3(player.position.x, RFloorHit.point.y - (collider.bounds.center.y + collider.bounds.extents.y), player.position.z);
}
}
else
{
onGround = false;
velocity.y -= gravity * Time.deltaTime;
}
} else { //if nothing is hit by either raycast, apply gravity.
onGround = false;
velocity.y -= gravity * Time.deltaTime;
}
//Wall collision, checks to see if the player has touched a wall on their left side
if (Physics.Raycast (LFloorWallRay.origin, Vector3.left, LFloorWallHit, 0.3))
{
if (LFloorWallHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (LFloorWallRay.origin, LFloorWallHit.point, Color.red);
if (onLeftWall) {if (velocity.x < 0) velocity.x = 0;} else //if they are touching a wall, don't allow them to go
//any further in that direction
if (onLeftWall == false){
onLeftWall = true; //If they aren't touching a wall, place them beside the wall.
player.position = Vector3(LFloorWallHit.point.x + (collider.bounds.center.x + collider.bounds.extents.x),player.position.y, player.position.z);
}
} else {
onLeftWall = false;
}
} else onLeftWall = false;
//same as above except for the right side instead of the left
if (Physics.Raycast (RFloorWallRay.origin, Vector3.right, RFloorWallHit, 0.3))
{
if (RFloorWallHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (RFloorWallRay.origin, RFloorWallHit.point, Color.red);
if (onRightWall) {if (velocity.x > 0) velocity.x = 0;} else
if (onRightWall == false){
onRightWall = true;
player.position = Vector3(RFloorWallHit.point.x - (collider.bounds.center.x + collider.bounds.extents.x),player.position.y, player.position.z);
}
} else {
onRightWall = false;
}
} else onRightWall = false;
//change the player's position depending on the calculated velocities
transform.position.x += velocity.x;
transform.position.y += velocity.y;
}
Answer by 2d4Games · Aug 04, 2013 at 01:47 PM
I have figured out the problem! It turns out that the collider.bounds.center is a vector relative to the world origin (it goes from 0,0 in the world to the center of the character), while collder.bounds.extents is a vector relative to the collider origin (it goes from 0,0 in the world to something diagonally above 0,0 , not actually to the model). This made me change a few things.
First, in line 26, I moved the declaration of the player variable to the top of the script. Doing this stopped the error from happening.
Second, I created a variable called AbsVelocityY, which is the absolute value of the Y direction of the velocity, and I made the raycast use this value as its distance. This makes it so that the raycast will essentially detect the object 1 frame before the character would hit it.
Third, I changed line 40 and line 60 to change the players position to FloorHit.point.y + collider.bounds.extents (because extents is relative to the character, not to the origin).
Here is the updated working code. I hope this helps anyone who might be having the same problem (I commented out the wall colliders for the time being):
var velocity = Vector2(0, 0);
var maxVelocity = Vector2(0.35, 1.0);
var gravity : float;
var onGround = false;
var onLeftWall = false;
var onRightWall = false;
var player : Transform;
function FixedUpdate () {
//Limits the speed to the maxVelocity
if (velocity.x > maxVelocity.x) velocity.x = maxVelocity.x;
else if (velocity.x < -maxVelocity.x) velocity.x = -maxVelocity.x;
if (velocity.y < -maxVelocity.y) velocity.y = -maxVelocity.y;
//Instatiate the raycast variables
var LFloorRay = new Ray(collider.bounds.center - collider.bounds.extents + Vector3(0,1,0), Vector3.down);
var LFloorHit : RaycastHit;
var LFloorWallRay = new Ray(collider.bounds.center - collider.bounds.extents, Vector3.left);
var LFloorWallHit : RaycastHit;
var RFloorRay = new Ray(collider.bounds.center - Vector3(-collider.bounds.extents.x, collider.bounds.extents.y,0) + Vector3(0,1,0), Vector3.down);
var RFloorHit : RaycastHit;
var RFloorWallRay = new Ray(collider.bounds.center - Vector3(-collider.bounds.extents.x, collider.bounds.extents.y,0), Vector3.right);
var RFloorWallHit : RaycastHit;
var AbsVelocityY = Mathf.Abs(velocity.y) + 1;
//Debug.DrawLine (LFloorRay.origin, Vector3(LFloorRay.origin.x, LFloorRay.origin.y - AbsVelocityY, LFloorRay.origin.z), Color.red);
//check to see if the Bottom Left Floor raycast hits a "Solid" platform
if (Physics.Raycast (LFloorRay, LFloorHit, AbsVelocityY))
{
if (LFloorHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (LFloorRay.origin, LFloorHit.point, Color.red);
if (onGround == false)
{
onGround = true;
velocity.y = 0; //If it hits a "Solid" platform, then make its vertical speed 0
//and place the object ontop of the platform, but only if it wasn't already on a solid platform
player.position = Vector3(player.position.x, LFloorHit.point.y + collider.bounds.extents.y, player.position.z);
} //LFloorHit.point.y + (-collider.bounds.center.y + collider.bounds.extents.y)
}
else //if the object is not "Solid", then ignore it
{
onGround = false;
velocity.y -= gravity * Time.deltaTime;
}//If bottom left doesn't hit, check the bottom right
} else if (Physics.Raycast (RFloorRay, RFloorHit, AbsVelocityY))
{
if (RFloorHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (RFloorRay.origin, RFloorHit.point, Color.red);
if (onGround == false)
{
onGround = true;
velocity.y = 0; //same as above
player.position = Vector3(player.position.x, RFloorHit.point.y + collider.bounds.extents.y, player.position.z);
}
}
else
{
onGround = false;
velocity.y -= gravity * Time.deltaTime;
}
} else { //if nothing is hit by either raycast, apply gravity.
onGround = false;
velocity.y -= gravity * Time.deltaTime;
}
/*//Wall collision, checks to see if the player has touched a wall on their left side
if (Physics.Raycast (LFloorWallRay.origin, Vector3.left, LFloorWallHit, 0.3))
{
if (LFloorWallHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (LFloorWallRay.origin, LFloorWallHit.point, Color.red);
if (onLeftWall) {if (velocity.x < 0) velocity.x = 0;} else //if they are touching a wall, don't allow them to go
//any further in that direction
if (onLeftWall == false){
onLeftWall = true; //If they aren't touching a wall, place them beside the wall.
player.position = Vector3(LFloorWallHit.point.x + (collider.bounds.center.x + collider.bounds.extents.x),player.position.y, player.position.z);
}
} else {
onLeftWall = false;
}
} else onLeftWall = false;
//same as above except for the right side instead of the left
if (Physics.Raycast (RFloorWallRay.origin, Vector3.right, RFloorWallHit, 0.3))
{
if (RFloorWallHit.collider.gameObject.CompareTag("Solid"))
{
Debug.DrawLine (RFloorWallRay.origin, RFloorWallHit.point, Color.red);
if (onRightWall) {if (velocity.x > 0) velocity.x = 0;} else
if (onRightWall == false){
onRightWall = true;
player.position = Vector3(RFloorWallHit.point.x - (collider.bounds.center.x + collider.bounds.extents.x),player.position.y, player.position.z);
}
} else {
onRightWall = false;
}
} else onRightWall = false;*/
//change the player's position depending on the calculated velocities
transform.position.x += velocity.x;
transform.position.y += velocity.y;
}