Reputation: 11
i have a problem understanding how to keep a reference to an object that has previously been hit by a raycast .
for example i can have a raycast script put on the camera of my 1rst person controller going from the camera position to the forwad vector * some value
this script is attached to the camera
public class raycast : MonoBehaviour {
float lenthRay = 10.0f;
Vector3 originePos;
Vector3 dir;
RaycastHit hitinfo;
GameObject hitten;
bool isHitting;
Color beforC;
int selectionLayer = 9;
void Update () {
originePos = Camera.main.transform.position;
dir = Camera.main.transform.forward * lenthRay;
Debug.DrawRay(originePos, dir, Color.blue);
if (Physics.Raycast(originePos, dir, out hitinfo, lenthRay , selectionLayer)) {
hitten = hitinfo.transform.gameObject;
MeshRenderer tmp = hitten.transform.GetComponent<MeshRenderer> ();
beforC = tmp.material.color;
tmp.material.color = Color.black;
}
//hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
print(hitten.name);
}
}
it is working great , except if i try to access the GameObject Hitten outside my if condition (like the print print(hitten.name)
)
i get this error before hitting an object from the right layer :
NullReferenceException: Object reference not set to an instance of an object
raycast.Update () (at Assets/raycast.cs:30)
then when i hit the object it is ok
but the problem is , i dont understand how i can change back the object color to its original color (beforC)
after turning it to Color.black
when the ray exit the object
this is what i try to do in the commented line , but i just get the same error than with the print , and nothing is turning black .
i have tried this :
originePos = Camera.main.transform.position;
dir = Camera.main.transform.forward * lenthRay;
Debug.DrawRay(originePos, dir, Color.blue);
isHitting = Physics.Raycast (originePos, dir, out hitinfo, lenthRay, selectionLayer);
if (isHitting) {
hitten = hitinfo.transform.gameObject;
MeshRenderer tmp = hitten.transform.GetComponent<MeshRenderer> ();
beforC = tmp.material.color;
tmp.material.color = Color.black;
}
if(!isHitting){
hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
print(hitten.name);
}
but it is not working either
can you help me understand the logic i should be using thanks in advance
Upvotes: 1
Views: 10615
Reputation: 151
I had this same need, when I was trying to detect when a wall was obstructing the player. I had never used a Raycast (or Linecast) before, and was surprised there wasn't a built in method to detect 'Enter', 'Stay', and 'Leave' events.
So I created this simple class to handle the details for me. I didn't bother creating any class constructors, and just exposed the properties as public. It handles both Raycasts and Linecasts.
This class tracks your objects each frame for you once you set it up using the properties.
Here's some sample code to demonstrate potential usage (the code I use for my wall detection):
private RayCaster wallRay;
void Start() {
wallRay = new RayCaster();
wallRay.OnRayEnter += WallRay_OnEnter;
wallRay.OnRayExit += WallRay_OnExit;
wallRay.LayerMask = RayCaster.GetLayerMask("Wall");
wallRay.StartTransform = camera.transform;
wallRay.EndTransform = PlayerManager.Player.transform;
}
void Update() {
wallRay.CastLine();
}
void WallRay_OnEnter(Collider collider) {
// Fade OUT wall section [Needs DOTween (free) installed]
collider.gameObject.renderer.material.DOFade(0.65f, 0.2f);
}
void WallRay_OnExit(Collider collider) {
// Fade IN wall section
collider.gameObject.renderer.material.DOFade(1f, 0.2f);
}
Here's the RayCaster class:
using System;
using System.Collections;
using UnityEngine;
public class RayCaster {
public Transform StartTransform;
public Transform EndTransform;
public Vector3 Direction;
public float RayLength;
public int LayerMask = 0;
public event Action<Collider> OnRayEnter;
public event Action<Collider> OnRayStay;
public event Action<Collider> OnRayExit;
Collider previous;
RaycastHit hit = new RaycastHit();
public bool CastRay() {
Physics.Raycast(StartTransform.position, Direction, out hit, RayLength, LayerMask);
ProcessCollision(hit.collider);
return hit.collider != null ? true : false;
}
public bool CastLine() {
Physics.Linecast(StartTransform.position, EndTransform.position, out hit, LayerMask);
ProcessCollision(hit.collider);
return hit.collider != null ? true : false;
}
private void ProcessCollision(Collider current) {
// No collision this frame.
if (current == null) {
// But there was an object hit last frame.
if (previous != null) {
DoEvent(OnRayExit, previous);
}
}
// The object is the same as last frame.
else if (previous == current) {
DoEvent(OnRayStay, current);
}
// The object is different than last frame.
else if (previous != null) {
DoEvent(OnRayExit, previous);
DoEvent(OnRayEnter, current);
}
// There was no object hit last frame.
else {
DoEvent(OnRayEnter, current);
}
// Remember this object for comparing with next frame.
previous = current;
}
private void DoEvent(Action<Collider> action, Collider collider) {
if (action != null) {
action(collider);
}
}
public static int GetLayerMask(string layerName, int existingMask=0) {
int layer = LayerMask.NameToLayer(layerName);
return existingMask | (1 << layer);
}
}
Upvotes: 5
Reputation: 11
so i did it with the mouse and it works you have to put your object on the right layer first (9th here)
you middle mouse click on a object to change its color (here black), and right click anywhere (on the object or not) to change back its original color
only one object has its color changed at any moment , (lets call this state "selected") when you "select" the next object by middle mouse clicking on it wile one is already "selected", it'll change the first one to its original color , as it is now "unselected"
public class raycast : MonoBehaviour {
float lenthRay = 10.0f;
Vector3 originePos;
Vector3 dir;
RaycastHit hitinfo;
GameObject hitten;
bool isHitting;
Color beforC;
int selectionLayer = 9;
bool alreadyHitten =false;
void Update () {
originePos = Camera.main.transform.position;
dir = Camera.main.transform.forward * lenthRay;
Debug.DrawRay(originePos, dir, Color.blue);
if (Input.GetMouseButtonDown (2)) {
isHitting = Physics.Raycast (originePos, dir, out hitinfo, lenthRay, selectionLayer);
if(isHitting) {
if(hitinfo.transform.gameObject == null){
hitten= null;
}
if(hitten != null && hitinfo.transform.gameObject == hitten){
alreadyHitten = true;
}
if(hitten != null && hitinfo.transform.gameObject != hitten){
alreadyHitten = false;
hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
hitten = hitinfo.transform.gameObject;
}
hitten = hitinfo.transform.gameObject;
if(hitten != null && !alreadyHitten){
print (hitten.name);
MeshRenderer tmp = hitten.transform.GetComponent<MeshRenderer> ();
beforC = tmp.material.color;
tmp.material.color = Color.black;
}
}
}
if (Input.GetMouseButtonDown (1)) {
if(hitten != null){
alreadyHitten = false;
hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
hitten = null;
}
}
}
}
Upvotes: 0
Reputation: 884
If your question is how to access the last object hit by your raycast then I suggest creating a global variable where you can store it.
Instead of setting the local variable in your method you can then set the global variable when you raycast. This way you can always access the object until you hit a new one(because this one is now stored in your global variable)
EDIT: In the event that you want to be able to keep track of all the targets that you have ever raycast I suggest making an global array where you store each item, appending them as you hit a new target.
Upvotes: 0