- Home /
Unreliability of Physics.Raycast.Distance for player movement?
I have a script which tries to detect for collisions with Raycasting being executed on the Update() function. However, my tests proved this to be unreliable, since the distance is different every time I run the game and test the Hit Distance of the Raycast.
What I'm trying to do is: cast a Ray to the ground, if a collision is detected and the minimum distance is reached, then stop vertical movement.
I've attached a picture of two different iterations of the same script being run with BIG differences on the results in-game.
As you can see, on one iteration the Raycast distance was detected when the user was still above the plane. However, on the other one, the player's feet was already underground when the distance was detected.
I've thought of doing this on FixedUpdate(), but not only this feels like a cheap workaround but also, according to this official unity video, I should be using this script on Update(), not the other way around: http://unity3d.com/learn/tutorials/modules/beginner/scripting/update-and-fixedupdate
Now, I want a fluid gameplay and a responsive script. I don't want random things happening on my game and want it to work as I tell it to. I'll attach the scripts below and I'd appreciate any input:
PlayerPhysics.cs
using UnityEngine;
using System.Collections;
public class PlayerPhysics : MonoBehaviour {
public LayerMask collisionMask;
public bool grounded;
public float _movementSpeed = 5;
public float _gravity = 4;
private float _skin = .005f;
private float deltaX;
private float deltaY;
private float deltaZ;
private Ray ray;
private RaycastHit hit;
private BoxCollider bc;
// Use this for initialization
void Start () {
bc = GetComponent<BoxCollider>();
}
public void Move (float h, float v) {
deltaX = 0;
deltaY = 0;
deltaZ = 0;
Vector3 rayOrigin = new Vector3();
rayOrigin.x = transform.position.x + bc.center.x;
rayOrigin.y = transform.position.y + bc.center.y - bc.size.y/2;
rayOrigin.z = transform.position.z + bc.center.z;
ray = new Ray(rayOrigin, Vector3.down);
Debug.DrawRay(ray.origin, ray.direction);
if (Physics.Raycast(ray,out hit, collisionMask)) {
Debug.Log(hit.distance);
if (hit.distance > _skin) {
deltaY -= _gravity;
} else {
grounded = true;
}
}
if (!grounded) {
}
// Moves the target
transform.Translate(new Vector3(deltaX, deltaY, deltaZ) * Time.deltaTime, Space.World);
}
}
PlayerController.cs
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (PlayerPhysics))]
public class PlayerController : MonoBehaviour {
private PlayerPhysics playerPhysics;
// Use this for initialization
void Start () {
playerPhysics = GetComponent<PlayerPhysics>();;
}
// Update is called once per frame
void Update () {
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
playerPhysics.Move(h, v);
}
}
What exactly are you using the raycast for? Could you explain the mechanics, what you expect, and what isn't working? Is there a specific reason/limitation that you aren't using the built-in physics to handle the player standing, stepping, walking on a ground plane?
What I'm trying to do is: cast a Ray to the ground, if a collision is detected and the $$anonymous$$imum distance is reached, then stop vertical movement.
I'm not using the physics system because my movement will be sort of grid-based and my character won't need any physics response to the world/objects on the game. The character just needs to walk around detecting map collision.
The difference you are seeing is probably caused by the Update rate at which the Raycast actually calculates new variables. The Raycast only calculates new variables each-frame if set to Update; so if the character is moving at a different rate, or the framerate of the game differs you will always get a different outcome. You could try adjusting the Fixed Timestamp inside of the Time $$anonymous$$anager options, but I don't think it will make all that much of a difference in this case. This is simply not going to work the way you want it to, it's just not how game logic is calculated.
You may have better luck also by moving the code to the FixedUpdate(). Otherwise, let's say you have a really long time between Update() calls. Your player may have been just under your threshold for distance to platform in the last frame but be past it in this frame. You will need to add some more complex code to extrapolate where the player will be next frame (again, do it all in FixedUpdate). When you deter$$anonymous$$e in frame 0 that the player should pass through the object in frame 1, and then in frame 1 your raycast returns false, you know the player must have gone too far, and you'll need to adjust the player's position to match. This may cause a slight jolt, i.e. the player mesh already got drawn below the object but the next frame is now above (or on) the object.
It's not real real complicated stuff, but Unity has already done all of this with the physics engine and even offers an Extrapolate mode for rigid bodies to calculate collision. I would try to find a way to use the built-in physics. It doesn't necessitate that you have to use all of the features of the physics, but it sure is helpful with jumping and moving onto solid objects.
Your answer
Follow this Question
Related Questions
How to determine multiple points along a ray? 1 Answer
help with AI avoidance script 0 Answers
Raycast based tile navigation 0 Answers
detect Ray Crossing? Intersecting? 1 Answer
Keep minimum distance while moving. 0 Answers