- Home /
Raycasting Collision for player movement in a maze
I'm using various methods for movement physics in a Pacman clone, to learn some different techniques (yes i know its probably overkill but its all for the sake of learning ;) ).
I am using 4 Rays for determining where the player can and cannot move, and when they can change direction.
The effect I am aiming for is best explained with an example:
Pacman is in a horizontal corridor which has T junctions at both ends, if the user presses right or left theyll start travelling along, and if they then press up or down this will be stored till they actual reach a valid turning point, then start moving in the new direction.
I have extended the draw of the above rays for debugging purposes, their actual length reaches the edge of the Players Collider.
I have got this mostly working, the only problem I have encountered is that at corners it seems to count it as a valid turning when only one of the Rays has passed the corner; as shown in the picture above, rather than waiting till both Rays are clear and Pacman is fully in the vertical corridor. My position correcting means that when this happens pacman will just halt rather than actually clipping the wall.
here is Update()
void Update() { #region Controls
// Set what the next direction the player intends to move next
if (Input.GetButtonDown("Up"))
{
nextDirection = Vector3.up;
}
if (Input.GetButtonDown("Right"))
{
nextDirection = Vector3.right;
}
if (Input.GetButtonDown("Down"))
{
nextDirection = Vector3.down;
}
if (Input.GetButtonDown("Left"))
{
nextDirection = Vector3.left;
}
// If pressing the direction youre already going in cancel it
if (nextDirection != Vector3.zero && nextDirection == currDirection)
{
nextDirection = Vector3.zero;
}
#endregion
#region Reposition
// If moving in a direction
if (currDirection != Vector3.zero)
{
// Check if reached a wall
wallAhead = AnyWalls(currDirection);
// if im travelling and there are walls ahead stop
if (wallAhead)
{
// correct position to prevent sinking into wall
if (currDirection == Vector3.left || currDirection == Vector3.right)
{
myTransform.position = new Vector3(Mathf.Round(myTransform.position.x), myTransform.position.y);
}
if ((currDirection == Vector3.up || currDirection == Vector3.down))
{
myTransform.position = new Vector3(myTransform.position.x, Mathf.Round(myTransform.position.y));
}
// then stop moving
nextDirection = Vector3.zero;
currDirection = Vector3.zero;
}
// Move in Direction
if (currDirection != Vector3.zero)
{
myTransform.position = Vector3.Lerp(myTransform.position, myTransform.position + currDirection * speed, Time.deltaTime);
}
// Correct minor misplacement
if (currDirection == Vector3.left || currDirection == Vector3.right)
{
myTransform.position = new Vector3(myTransform.position.x, Mathf.Round(myTransform.position.y));
}
if ((currDirection == Vector3.up || currDirection == Vector3.down))
{
myTransform.position = new Vector3(Mathf.Round(myTransform.position.x), myTransform.position.y);
}
}
#endregion
#region Turning
// Has user pressed a direction
if (nextDirection != Vector3.zero)
{
// Check for walls when Player wants to turn
wallInNextDirection = AnyWalls(nextDirection);
// If not moving and walls in pressed direction ignore the direction
if (wallInNextDirection && currDirection == Vector3.zero)
{
nextDirection = Vector3.zero;
}
// Check if it is possible to change direction
if (!wallInNextDirection)
{
Debug.Log("Turning");
currDirection = nextDirection;
}
else Debug.Log("Cant Turn");
}
#endregion
}
And this is the function called to cast rays
bool AnyWalls(Vector3 dir) { Vector3 origin1 = new Vector3(); Vector3 origin2 = new Vector3();
if (dir == Vector3.up | dir == Vector3.down)
{
origin1.x = (myTransform.position.x + ( (myCollider.size.x / 2)) - 0.05f);
origin1.y = myTransform.position.y;
origin2.x = (myTransform.position.x - ( (myCollider.size.x / 2)) + 0.05f);
origin2.y = myTransform.position.y;
}
else /* Direction == Left / Right */
{
origin1.x = myTransform.position.x;
origin1.y = (myTransform.position.y + ( (myCollider.size.x / 2)) - 0.05f);
origin2.x = myTransform.position.x;
origin2.y = (myTransform.position.y - ( (myCollider.size.x / 2)) + 0.05f);
}
Debug.DrawRay(origin1,dir * myCollider.size.x);
Debug.DrawRay(origin2,dir * myCollider.size.x);
return (Physics.Raycast(origin1, dir, (myCollider.size.x / 2) + 0.1f, kDefaultRaycastLayers) &&
Physics.Raycast(origin2, dir, (myCollider.size.x / 2) + 0.1f, kDefaultRaycastLayers));
}
As you can see that should return True when both rays are clear. It is likely I have just made some stupid mistake in there, and simply need a fresh pair of eyes to find it.
Again theres probably an approach more suitable for Pacman, but I'm just trying to learn all the different methods of doing things using simple projects.
Thanks for any replies!
Edit 1: Corrected typos and updated the variable names in the Update() snippet to be more easily readable Edit 2: I have included in the Update() snippet some of the Debug messages I am using which output "Can't Turn" and "Turning" these demonstrated to me that it was indeed attempting to turn early when a corner is reached.
Answer by Magnus Wolffelt · Jul 25, 2010 at 10:05 PM
Looking at the code briefly, I was unable to find an apparent error in your implementation. The AnyWalls() function looks correct.
However, some thoughts:
Thought #1: Comparing Vectors with == is risky at best, and can result in really annoying bugs. This is due to floating point errors that arise in computations and/or values defined that don't necessarily reflect an exact representable 32-bit float value. Even more subtle, is that 32-bit floats are put in high precision CPU registers, so you can have different results depending on whether the value goes by memory or not during computation.
This can generate VERY weird bugs, because usually what happens, when you don't get the results you expect, is that you add logging to track the value. This potentially alters the results of the computation because the value goes through memory and back into registers at lower precision. When you remove your debug logging, computation is probably performed in high precision registers all the way, which gives subtly different results. While Unity probably implements == operator with some sort of epsilon, you can still end up with it returning false for very similar vectors. I recommend you write a little function to make a coarse equality check, so that you can be confident that it isn't flaky for your needs.
Thought #2: I recommend you always put {} brackets around if/while/else/etc blocks. Not doing so can result in mistakes, resulting in bugs that are usually subtle and/or hard to find.
I'm sorry I couldn't help you more with the specific bug.
Thanks for the advice, I know missing out brackets is a bit questionable, I was trying to cut down the conde length to make it reasonable to embed here ;)
Also how would I go about overloading the == operator for Vector3, in the cleanest way?
Just use a local function to compare:
void IsAlmostEqual(Vector3 v0, Vector3 v1) { return $$anonymous$$ath.Abs(v0.x - v1.x) < 0.1f && ... (for all components) }
Didn't think of that, I actually looked into overriding the == operator of the Vector3 class, but i guess thats unnecessary thanks again...
I doubt it will make any difference to this project, the only Vector3 comparisons I'm currently doing are using single unit directional vectors, eg Vector3.up but I will keep this in $$anonymous$$d for the future.
Answer by sovalopivia · Jul 27, 2010 at 01:02 PM
There should be a wall if either ray is true not if both are true facepalm
fixed by changing the return line in the AnyWalls()
function &&
to a ||
Before:
return (Physics.Raycast(origin1, dir, (myCollider.size.x / 2) + 0.1f, kDefaultRaycastLayers) &&
Physics.Raycast(origin2, dir, (myCollider.size.x / 2) + 0.1f, kDefaultRaycastLayers));
After:
return (Physics.Raycast(origin1, dir, (myCollider.size.x / 2) + 0.1f, kDefaultRaycastLayers) ||
Physics.Raycast(origin2, dir, (myCollider.size.x / 2) + 0.1f, kDefaultRaycastLayers));
Answer by Vincent 1 · Oct 13, 2010 at 06:10 PM
probably a faster way to determin colission like this would be to us an array map of 0s and 1s 0 beign no wall 1 beign a wall
I wasn't looking for an ideal method for collision detection this was an exercise for myself on doing raycasting.