[SOLVED]Detecting what side of s box collider is hit through ray casts

I am trying to do exactly what this name of this thread says. I found this on unity answers: Anyway to tell which side of a collider is hit? - Questions & Answers - Unity Discussions and followed the instructions given. Right now it only kind of works but it is very inaccurate and seems like it just gives me a random output in the console when I test it. I would like to request help fixing my code.
This is my code:

if (hit.transform.tag == "Enemy") {
                        selectedEnemy = hit.collider.transform.gameObject;

                        BoxCollider box = hit.collider as BoxCollider;
                        if (box == null) Debug.LogWarning("Collider is not a BoxCollider!");
                        Vector3 localPoint = hit.transform.InverseTransformPoint(hit.point);
                        Vector3 localDir = localPoint.normalized;
                        float upDot = Vector3.Dot(localDir, Vector3.up);
                        float fwdDot = Vector3.Dot(localDir, Vector3.forward);
                        float rightDot = Vector3.Dot(localDir, Vector3.right);
                        float upPower = Mathf.Abs(upDot);
                        float fwdPower = Mathf.Abs(fwdDot);
                        float rightPower = Mathf.Abs(rightDot);
                        if(Mathf.Sign(upDot)==-1 && upPower < fwdPower && upPower < rightPower){
                            Debug.Log ("Sneak Attck Below Enemy");
                        }else if(Mathf.Sign(upDot) == 1 && upPower > fwdPower && upPower > rightPower){
                            Debug.Log("Arial Attack Above Enemy");
                        }
                        else if(Mathf.Sign(fwdDot)==-1 && fwdPower < upPower && fwdPower < rightPower){
                            Debug.Log("Unaware Attack Behind Enemy");
                        }else if (Mathf.Sign(fwdDot) == 1 && fwdPower > upPower && fwdPower > rightPower){
                            Debug.Log("Direct Attack Infront of Enemy");
                        }
                        else if(Mathf.Sign(rightDot)==-1 && rightPower < upPower && rightPower < fwdPower){
                            Debug.Log("Flank Attack To The Left");
                        }else if(Mathf.Sign(rightDot)==1 && rightPower > upPower && rightPower > fwdPower){
                            Debug.Log("Flank Attack To The Right");
                        }

get the point where the ray cast hit.

transform the point to the local space of the collider (transform.worldToLocalMatrix).

Compare point to the ā€˜centerā€™ and ā€˜sizeā€™ properties to figure out which side itā€™s on. What should happen is one of the x,y,z values of (point - center) should be slightly equal (given float error) to the absolute value of the size x,y,z valuesā€¦ the sign will denote which one of the two sides on the axes.

As for labeling sidesā€¦ thatā€™s up to you. Thereā€™s no given ā€œlabelā€ to a side.

2 Likes

You lost me at "Compare point to the ā€˜centerā€™ and ā€˜sizeā€™ properties to figure out which side itā€™s on. "
I am still trying to figure out what all is happening. I am not used to working with colliders.

a BoxCollider has 2 properties that define the extents of the box.

center - the position of the center of the box relative to the GameObject itā€™s attached to:

size - the extents of the box relative to center:

This of course is in LOCAL space to the GameObject the BoxCollider is attached. Your Raycast hit point is in GLOBAL space, so you need to transform it to LOCAL space. This is done with the ā€˜worldToLocalMatrixā€™ property of the transform on the GameObject the BoxCollider is attached to.

By doing this youā€™ll have the point relative to the origin of the GameObject, just like the ā€˜centerā€™ and ā€˜sizeā€™ properties.

Nowā€¦ if you subtract ā€˜centerā€™ from the point, you have the point relative to the center of the box.
(point = point - box.center)

If the point is truly on the surface of the box, then one of its x,y,z values will be equal (ish, considering float error) to one of the size x,y,z values because itā€™s on the surface. Just figure out which one it is.

1 Like

considering that I donā€™t have a clue as to how collider calculations work. What I am getting from this is you convert the point where the ray hits to local space. Then subtract that local point from the center point of collider box and I should get a value that is close to the face of one side of the collider. I then compare that point coords on the face to the size coords. Is this correct or am I just rambling nonsense. I am trying to also learn and understand what is happening so I am not just copying code. I am more of a visual person so I could also use a coded example so I can visually see what you are trying to say.

Forget raycasting. Thatā€™s the equivalent of checking the tyre pressure to see if you are driving a car.

You know your hit point. You know where the centre of the collider is. Subtract the two vectors. Thatā€™s the direction of the hit.

1 Like

what? how do I subtract vectors? Also dont I need to cast a ray to get the hit point? I am using the Ray cast to Select what enemy I want to attack, so I figured I might as well kill two birds with one stone and get the side of the box collider hit to determine how the character attacks the enemy.

Vectors are subtracted just like floats:

var result = vectorA - vectorB;

This gives a vector pointing from B to A.

Also, what exactly is the purpose of this? Why do you need to know exactly which side of the collider was hit?

If you have ever heard of the game ā€œFire Emblemā€, keep its game play mechanics in mind. for example, when I move a character near an enemy and click It, I want to know what kind of attack the player intends to do. Given that the enemy has not spotted the character attacking, If the character is close enough they can click the back side of the box collider of the enemy and preform a sneak attack from behind for a critical hit.

OK, so yeah, you donā€™t really need to do all thisā€¦ you just need to know in what direction they clicked. Like BoredMormon suggested.

But so far it appears you donā€™t understand how vectors work at allā€¦ so even resolving that information is going to take a bit of splainingā€¦ you aught to go and learn about vectors before proceeding. Theyā€™re kind of CRITICAL to game programming.

Alsoā€¦ is this 2D or 3D???

1 Like

It is 3D. Also arenā€™t vector2ā€™s used for 2D x,y coordinates and vector3ā€™s used for 3D x,y,z coordinates. I somewhat know how they work. For example I use an array of vector3ā€™s to instantuate my characters into the scene upon loading.

After a lot of trial and error I finally got it to work. This is my solution:

if (Input.GetMouseButtonDown (0)) {
                Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);

                RaycastHit hit;
           
                if (Physics.Raycast (ray, out hit, DetectCharacterSelect.SelectedPlayer.GetComponent<CharacterSheet>().CurrentClass.Range)) {
                    if (hit.transform.tag == "Enemy") {
                        selectedEnemy = hit.collider.transform.gameObject;

                        BoxCollider box = hit.collider as BoxCollider;
                        if (box == null) Debug.LogWarning("Collider is not a BoxCollider!");
                   
                        Vector3 Normal = hit.normal;
                        Normal = hit.transform.TransformDirection( Normal );

                        if( Normal == hit.transform.up ){
                            Debug.Log("Arial Attack Above Enemy");
                        }else if( Normal == -hit.transform.up ){
                            Debug.Log ("Sneak Attck Below Enemy");
                        }else if( Normal == hit.transform.forward ){
                            Debug.Log("Direct Attack Infront of Enemy");
                        }else if( Normal == -hit.transform.forward ){
                            Debug.Log("Unaware Attack Behind Enemy");
                        }else if( Normal == hit.transform.right ){
                            Debug.Log("Flank Attack To The Right");
                        }else if( Normal == -hit.transform.right ){
                            Debug.Log("Flank Attack To The Left");
                        }else
                    }
                }
            }

Is there a way to fix it if the collider is for example turned 90 degrees on the y axis? I tried testing it to see if it still works with the collider rotated on the y axis and only the top and bottom work. I should also say that in my game it is only going to spin on the y axis so a fix for x and z is not necessary.

Iā€™m actually amazed if this works without problems, and doesnā€™t run into floating point precision issues at some point.

What you want to do is calculate the dot product between the normal and the up vector.

float dot = Vector3.Dot(Normal, hit.transform.up;

If the normal and the up vector are pointing in the same direction, this value will be 1. You should probably give it a fudge factor in case theyā€™re not completely equal, so for example if dot > 0.99, you consider them to point the same direction.

http://docs.unity3d.com/ScriptReference/Vector3.Dot.html
https://en.wikipedia.org/wiki/Dot_product

EDIT: This should also allow you to rotate the collider any way you want.

1 Like

Ok if I do this how would I correctly compare things to determine the side? By going back to comparing dots this puts me back where I started.
This is what I have:

float dotUp = Vector3.Dot(Normal, hit.transform.up);
float dotForward = Vector3.Dot(Normal, hit.transform.forward);
float dotRight = Vector3.Dot(Normal, hit.transform.right);

If I where to put this in an If statement what would I be comparing it to? For example: If I compare the dot like this

if(dotUp < 0){
//Below
}else if(dotUp > 0){
//Above
}else if (dotForward < 0){
//behind
}else if(dotForward > 0){
//front
}

it will either come out with an output of above or below because dotUp will always have a value. I would have to compare it to the other dots to make sure I am getting the correct ouput. This I believe is the problem I originally had. How would I go about comparing the dots?

EDIT
Actually this works because if the dot is zero if itā€™s no the correct face, but it breaks after I try testing the rotation of the collider.

                        if(dotUp < 0){
                            //Below
                            Debug.Log ("Sneak Attck Below Enemy");
                        }else if(dotUp > 0){
                            //Above
                            Debug.Log("Arial Attack Above Enemy");
                        }else if (dotForward < 0){
                            //behind
                            Debug.Log("Unaware Attack Behind Enemy");
                        }else if(dotForward > 0){
                            //front
                            Debug.Log("Direct Attack Infront of Enemy");
                        }else if (dotRight < 0){
                            //Left
                            Debug.Log("Flank Attack To The Left");
                        }else if (dotRight > 0){
                            //Right
                            Debug.Log("Flank Attack To The Right");
                        }

bump

Donā€™t compare like that. Remember that the dot product is 1 when they are pointing in the same direction, so you should be comparing it to 1. But as I said, they might not be exactly in the same direction, so check if itā€™s greater than for example 0.99.

And when itā€™s in the opposite direction, itā€™s -1, so donā€™t just check if itā€™s below 0, but check if it is for example below -0.99.

The exact value you compare with will depend on your game, and how precisely you need them to be in the same (or opposite) direction.

The dot product is 0 when they are exactly perpendicular, so when you compare with <> 0, it will be true almost always, just not when they are exactly perpendicular.

EDIT: perpendicular

I think you were looking for perpendicularā€¦ :wink:

Yeah, thanks. Still early morning apparently.

3 Likes

I am sorry to once again bother you. It does work when rotated, but say I click the back of the enemy then it turns 180 degrees. I click the enemy again but it still says I clicked the back although it now should be the front. Is there a way to mark and store the data of a box collider face on say Awake() and use those whenever the enemy rotates so I get the correct face? I have also come to another idea of having 6 colliders for each side of the enemy, but although it is simple, I really donā€™t think it is practical .