Detecting collision

I’m messing around with unity and currently trying to get a ball rolling around and jumping on a platform. currently, i’ve found a “character controller” script from unity that allows me to control the movement of the ball. however, the “controller.isGrounded” thing it uses seems very arbitrary; i’ve added debug log in the script to log the state of “controller.isGrounded” and no matter what i do, it seems to be “False” most of the time, only seemingly randomly switching to “True” sometimes. and the part of the script that controls jumping relies on the “controller.isGrounded” to determine whether the ball is “grounded” to allow it to jump.

So I decided to try and use collisions to detect whether the ball is “grounded” instead, swapping “groundedPlayer” with “collision.gameObject.name == ground.name”. however, this just gives me a “Object reference not set to instance of an object” error. i’m assuming this is bc my implementation of collision detection is wrong, but i’m not sure what the right way to do it is.

ball control script, for reference:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    private CharacterController controller;
    private Vector3 playerVelocity;
    private bool groundedPlayer;
    public float playerSpeed = 2.0f;
    private float jumpHeight = 1.0f;
    private float gravityValue = -9.81f;
    public GameObject ground;
    private Collision collision;

    // Start is called before the first frame update
    private void Start()
    {
        controller = gameObject.AddComponent<CharacterController>();
    }

    // Update is called once per frame
    void Update()
    {
        groundedPlayer = controller.isGrounded;
        if (groundedPlayer && playerVelocity.y < 0)
        {
            playerVelocity.y = 0f;
        }

        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        controller.Move(move * Time.deltaTime * playerSpeed);

        if (move != Vector3.zero)
        {
            gameObject.transform.forward = move;
        }

        //Debug.Log(collision.gameObject.name);

        if (Input.GetButtonDown("Jump") && collision.gameObject.name == ground.name)
        {
            playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
        }

        playerVelocity.y += gravityValue * Time.deltaTime;
        controller.Move(playerVelocity * Time.deltaTime);
    }

}

Ok, so what’s your code for collision detection? The code you shared is just a copy of Unity’s sample code in the documentation. We can’t do anything to help if you don’t share the code that needs fixing.

The collision data isn’t available in Update and isn’t used with a character controller. Also, I’m guessing you’ve added a collider to your character controller that’s preventing it from detecting collisions. The character controller doesn’t need a collider as it already uses its own internal capsule collider.

i’m not sure what you mean, is “Object reference not set to instance of an object” an error saying that it can’t detect collisions?

You’ve stated that you get a exception on a line of code that you’ve not posted. Devs are guessing as to what you might be doing.

In the end, this is basic C# stuff. If you refer to a field that isn’t initialised (NULL) then you’ll get this error.

The code you refer to “finding” is simply an example snippet.

2 Likes

No, “Object reference not set” simply means there’s a reference in your code that has not been initialized to any value, so it is empty (null). This happens because your code is broken/wrong in some way.

But we can’t tell what’s wrong with your code - what’s causing this error- unless you post it! :wink:

On line 41 of your script you mistakenly try to get the name of the game object that you’ve collided with using collision data but the collision data hasn’t been set by the physics engine. The physics engine only sets the collision data for collisions involving a rigidbody. And it only passes the collision data to a script through the events like OnCollisionEnter or OnCollsionExit. These events aren’t supposed to be used with a character controller.

The character controller has its own event which can be used to get more info about collisions. Or you can do a SphereCast or Raycast to test to see if there’s ground below.

I tested your script. The reason isGrounded kept returning false is probably because you did the ground check after the first Move. And the script is a little broken as we’re not supposed to call Move twice per frame.

Here’s an updated version:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
  
public class CCTest : MonoBehaviour
{
    private CharacterController controller;
    private Vector3 playerVelocity;
    private bool groundedPlayer;
    public float playerSpeed = 2.0f;
    private float jumpHeight = 1.0f;
    private float gravityValue = -9.81f;

    void Start()
    {
        controller = gameObject.AddComponent<CharacterController>();
    }

    void Update()
    {
        groundedPlayer = controller.isGrounded;
        if (groundedPlayer && playerVelocity.y < 0)
        {
            playerVelocity.y = 0f;
        }
      
        if (Input.GetButtonDown("Jump") && controller.isGrounded)
        {
            playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
        }

        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))*playerSpeed;

        if (move != Vector3.zero)
        {
            gameObject.transform.forward = move;
        }
      
        playerVelocity.y += gravityValue * Time.deltaTime;
        controller.Move((playerVelocity+move) * Time.deltaTime);
    }
}

i’m not entirely sure what you mean by “call move twice per frame”, but it looks like the problem was that the “jump” bit was getting called later than it should’ve? bc the biggest change i see is that the “jump” bit was moved up to under the other “groundedPlayer” bit

anyways, “jump” works consistently now, but it doesn’t work when the ball is stationary. i think i remember seeing on some other forum post that “isGrounded” only runs a check when the object is moving. is that why it doesn’t work?

the code i posted is my code. i just made some changes to the original sample from unity

Yep, isGrounded will be updated every time Move is called/executed. You just need to make sure Move is called every Update and you’re adding gravity to the ball so that it’s always being pushed into the ground.

You could consider switching to using a rigidbody so then you can have a ball that actually rolls around. The character controller doesn’t roll as it’s meant to be used with upright characters.

Your code does not contain any collision callbacks such as OnCollisionEnter or OnCollisionStay?

Edit: just realized that you were being literal here:

You can’t just declare a reference to a Collision object and then use it, it will of course result in a runtime error since it is not initialized.

This is a basic programming concept, variables come in two flavors: values and references. Value variables are always valid, for instance when you declare an integer like this:

int myNumber;

It will always have a valid value, even if you don’t initialize it (in that case, a default value like zero) ,so it is safe to use it.

However references may have invalid values, when they’re not referencing anything. In these cases they point to a special value called NULL. If you try to use a reference that’s NULL, you’ll get this error: “Object reference not set to an instance of an object”, which tells you the reference isn’t referencing any object so you can’t do anything with it. For instance:

public class MyObject{} // declare a class
.
.
.
private MyObject obj; // declare a reference to an instance of my class
.
.
.
obj.doSomething(); //<---- error, we can't tell obj to do anything since it is uninitialized.

This is what you’re doing in your code:

private Collision collision; // declare a reference to an instance of the Collision class
.
.
.
if (collision.gameObject.name == ground.name) //<---error, we can't access gameObject since collision is NULL.

Whether a variable is a reference or a value is determined by its type. Basic types like int or float are value-types. Structs are also value-types. However Classes are reference-types.

Your code declares a reference to a Collision (which is a class), doesn’t initialize it, and then proceeds to access it which triggers the error. My advice would be to find some C# programming tutorials and get yourself familiar with the basics, since this isn’t really a Unity thing.

If you want to use the Collision class, you must have a valid Collision reference. You can get one from Unity using OnCollisionEnter.

switching to a rigidbody
ig that’s something i should look into. i just used character controller bc it seemed like a simpler way to achieve what i was trying to do

i’m not sure if i completely understand what you’re saying, but it sounds like the problem is that i was using “private Collision collision” without first initializing some “collision reference”?

find some C# programming tutorials
that’s probably a smarter idea, but i have a tendency to be patient and unfocused, so i figured it was easier to just get my hands dirty and learn on the way. though evidently, that comes with its own problems… like asking for help on smth that’s apparently c# 101…

“private Collision collision” means hey, I want to declare a box that can contain objects of type Collision, and call this box “collision”.

So now you have an empty box with the word “collision” written on its lid, it can store Collision objects.

Then you do: “if (collision.gameObject.name == ground.name)”
hey, check if the Collision contained in the box matches the name of the ground.

And the computer goes: sorry, but this box is empty! I can’t check anything about the object inside it because there’s nothing in it! : “object reference (box) not set to an instance of an object (has no object in it)

Unless you put a Collision object in the box, your code will fail. You can’t just create a Collision object yourself out of thin air either, as you need it to be populated with data about a collision you know nothing about. Unity will give you a proper box with a Collision in it in the OnCollisionEnter method, which gets called when a collision takes place:

void OnCollisionEnter(Collision collision) //<-- Unity hands a proper Collision to you.
{
   if (collision.gameObject.name == ground.name)
   {
   // we've collided with the ground
   }
}

This is more like programming 101 :p… not even specific to C#. But don’t be discouraged!. It takes time to learn, and stuff that seems confusing now will become crystal clear later on.

1 Like

in complete honesty, i think i’ve noticed most/all the references for collision indeed use “OnCollisionEnter” (or smth of the sort), but i wasn’t sure how to tie that in with my “if” statement. in my attempt to “throw things at the wall and see what sticks”, i’ve apparently completely neglected to connect the dots between “void Public”, “void Update”, and “void OnCollisionEnter”, in that they’re functions called by unity to perform the tasks contained within the function.
anyways, i appreciate your efforts in making this stuff easier for me to understand. it’s not like i’m completely new to coding, but clearly i haven’t exactly internalized all the essentials yet either

Look in the docs for MonoBehaviour (as well as its parent classes) to learn ALL the methods that might get called.

This mechanism is absolutely fundamental to 100% of the interoperability between Unity and your scripts.

And consequently, if you misspell it as OoopDate(), guess what? Unity doesn’t call it. Same goes for any of those.

1 Like

Monobehavior
good to know, noted