- Home /
Raycast in Update Incorrect Results When Moving Object
Hi! So I am trying to make a card in Unity. The card has a backSide and a frontSide in the canvas. The gameObject also has an empty FacePoint as child.
The card has a Box Collider and FacePoint is moved slightly to the front of the card. I am Raycasting from my camera to the FacePoint with this code and if the ray did not collide with my BoxCollider I am hiding the back of the card and showing the front, else vice versa.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//SUMMARY: Shows the back and front of the card based on the cards rotation using raycasting
[ExecuteInEditMode] //Makes it so that this script is not only executed in play mode but also in the Unity editor
public class CardRotation : MonoBehaviour
{
//Variables
public RectTransform CardFront; //Parent game object for the card face graphics
public RectTransform CardBack; //Parent game object for the card back graphics
public Transform FacePoint; //Empty game object that is placed in the center and a bit above of the face of the card
public Collider col; //3D collider attached to the card
private bool isBack = false; //True when front view of the card is seen by the camera
// Update is called once per frame
void Update()
{
//transform.Rotate(Vector3.up * 50.0f * Time.deltaTime); //Uncomment to rotate card object
RaycastHit[] hits; //Raycast from camera to a target point on the face of the card. If it passes through the cards collider we should show the back of the card
hits = Physics.RaycastAll( origin: Camera.main.transform.position, //origin of raycast (camera)
direction: (-Camera.main.transform.position + FacePoint.position).normalized, //direction of raycast (towards the face of the card from the camera)
maxDistance: (-Camera.main.transform.position + FacePoint.position).magnitude); //maximum distance of raycast (From camera until the face of the card)
bool passedThroughCollider = false;
foreach(RaycastHit h in hits)
{
if(h.collider == col) //if the collider that was hit was col
{
passedThroughCollider = true;
}
}
if(passedThroughCollider != isBack)
{
isBack = passedThroughCollider;
if (isBack)
{
CardFront.gameObject.SetActive(false);
CardBack.gameObject.SetActive(true);
}
else
{
CardFront.gameObject.SetActive(true);
CardBack.gameObject.SetActive(false);
}
}
}
}
While this code works when I am rotating my card, it flickers between showing the front and the back, whenever I am moving the card, especially in the Z axis. You can see it in the video here, I cut some of the segments where this happens. https://youtu.be/5TBKUtQmXTM
Any help on this would be appreciated, as I am stuck on this for the past 48 hours :((
Answer by jkpenner · Apr 21, 2020 at 12:15 AM
You can actually check if the back or front is facing forward without using ray casts. The method I've used in the past is to use vectors and a dot product.
Vector3 toCamera = new Vector3(0f, 0f, -1f);
bool isFront = Vector3.Dot(transform.forward, toCamera) < 0;
The dot product will return a value between 1 and -1, where 1 is both vectors are pointing in the same direction and -1 is when the vectors are pointing in opposite directions. One thing to remember is both vectors must be normalized (have a length of 1), or else the value returned from the dot product can be outside the range of 1 and -1.
Also depending on your own setup you may have to flip the condition or change the toCamera vector's value.
Answer by Hisoka2000 · Apr 21, 2020 at 07:39 AM
Hey, thank you for the help! I did actually think of this but the problem is that while this works when the card is facing the camera, whenever the card is off center it does not immediately shift to the correct side when flipped (since I am using ortho camera). However, I DID come up with a solution for this.
void Update()
{
Vector3 pos = Camera.main.transform.position;
Vector3 dir = (this.transform.position - pos).normalized;
//Debug.DrawLine(pos, pos + dir * 10, Color.red, Mathf.Infinity);
isBack = Vector3.Dot(transform.forward, dir) < 0;
if (isBack)
{
CardFront.gameObject.SetActive(false);
CardBack.gameObject.SetActive(true);
}
else
{
CardFront.gameObject.SetActive(true);
CardBack.gameObject.SetActive(false);
}
}
And now it works! :)