Search code examples
c#unity-game-enginecollision

Collision Detection and isGrounded checks


I'm currently working on a project in Unity and as I'm fairly new to C# and programming in general I'm having a few issues. The first issue is with the collision detection for my player - I have a modelled Rigidbody with gravity and a mesh collider on (non convex) attached. For my walls, they're models imported with box colliders attached.

If I keep my box colliders the default size of the model (about twice as thick as the player) then the player can glitch through it sometimes and fly out the other side. To get around this I simply make the collider bigger for the walls / environment but this is not ideal if I want the player to be able to walk around or be on both sides of the wall. Currently the player "climbs" up the side of the wall when it collides, which should not happen? I was wondering if I have something configured wrong or if it would be better to manage collision for the player entirely through script? I have a screenshot of both my character and player components below: Wall - http://i.imgur.com/YRdTgSh.png? Player - http://i.imgur.com/DVKOdG1.png?

My second issue is when I try to check whether my player is grounded in order to let them jump or not. The code below is my current entire movement script for managing the player moving and jumping. At the moment, the player can jump infinitely high however if I spawn the player from a high space they will only jump once, until they reach a certain height which they will then jump again from repeated until space is let go.

using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour {

// Update is called once per frame
void FixedUpdate() {

    // Creating floats to hold the speed of the plater
    float playerSpeedHorizontal = 4f * Input.GetAxis ("Horizontal");
    float playerSpeedVertical = 4f * Input.GetAxis ("Vertical");

    // Transform statements to move the player by the playerSpeed amount.
    transform.Translate (Vector3.forward * playerSpeedVertical * Time.deltaTime);
    transform.Translate (Vector3.right * playerSpeedHorizontal * Time.deltaTime);

    // Calling the playerJump function when the jump key is pressed
    if (Input.GetButton("Jump"))
    {
        playerJump();
        Debug.Log ("Can jump");
    }

}

/// Here we handle anything to do with the jump, including the raycast, any animations, and the force setting it's self.
void playerJump() {

    const float JumpForce = 1.0f;
    Debug.Log ("Should Jump");

    if(Physics.Raycast(rigidbody.position, Vector3.up, collider.bounds.extents.y + 0.1f)) {
    //  Debug.Log ("Can jump");
        rigidbody.AddForce (Vector3.up * JumpForce, ForceMode.VelocityChange);
    }
}
}

If anyone could help I'd be very appreciative as I'm getting quite confused by this


Solution

  • When moving physics bodies, use rigidbody's AddForce function. You have used it with your jump code which is good, but you must also use it with all other movement code.

    transform.Translate (Vector3.forward * playerSpeedVertical * Time.deltaTime);
    transform.Translate (Vector3.right * playerSpeedHorizontal * Time.deltaTime);
    

    Should be

    rigidBody.AddForce(transform.forward * playerSpeedVertical);
    rigidBody.AddForce(transform.right * playerSpeedHorizontal);
    

    This will allow the physics engine to properly resolve the rigidbody's movement during the physics simulation. I've omitted the ForceMode parameter, but feel free to use it as you see fit.

    By using transform.Translate, you were essentially teleporting the rigidbody into your collision geometry, because transform.Translate knows nothing about the physics simulation.

    Also note, do not use Time.deltaTime in a FixedUpdate function, instead use Time.fixedDeltaTime. However, because the physics simulation is always calculated at a fixed rate, you never actually need to use fixedDeltaTime when applying forces.

    As for the jumping, you are ray casting up. You want to ray cast down.

    Vector3 rayOrigin = transform.position;
    rayOrigin.y += collider.bounds.extents.y; //move the ray origin up into the collider, so there's no chance your ray cast will begin inside the ground
    float rayDistance = collider.bounds.extents.y + 0.1f;
    
    Ray ray = new Ray();
    ray.origin = rayOrigin;
    ray.direction = Vector3.down;
    
    if(Physics.Raycast(ray, rayDistance, 1 << 8)) {
        rigidbody.AddForce (Vector3.up * JumpForce, ForceMode.VelocityChange);
    }
    

    Please note the third parameter of the ray cast, it is 1 << 8. This is the ray's layerMask. It will only cast against objects in this layer (so that excludes your player, which is why I believe you were getting incorrect raycast results before)

    In order to know what parameter this should be for you, first you need to make a new layer (in Unity: Edit -> Project Settings -> Tags & Layers)

    Create a layer, call it ground, or terrain. Note the layer number. (i.e. User Layer 8 would mean your third parameter for the raycast would be 1 << 8, but if you were to create the ground/terrain layer as layer 10 instead you'd use 1 << 10)

    Now the final step is to assign this layer to your ground/terrain colliders.

    Now your player should be casting a ray downwards, that starts at his mid section and goes a bit below his feet. This ray will ignore his colliders and only tell you when the ray is colliding with a ground layered object.