I Want it to set the material back to the material i setup when the reycast isint hitting the object anymore i only want it to be the hightlight material when its hitting it, Thanks in advance
private void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 rayPoint = ray.GetPoint(distance);
rayPoint.y = 2;
transform.position = rayPoint;
// Debug.Log("m: " + Input.mousePosition);
// Debug.Log("r: " + rayPoint);
RaycastHit hit;
Ray BoardCheck = new Ray(transform.position, Vector3.down);
if (Physics.Raycast(BoardCheck, out hit))
{
if (hit.collider.tag == "BoardBlock")
{
// Debug.Log("Hit" + hit.collider.gameObject);
RaycastHitObject = hit.collider.gameObject;
RaycastHitObject.GetComponent<Renderer>().material = Hilight;
}
}
}
//IF MORE INFO IS NEEDED JUST ASK
}
A simple if else should do. If you hit your object, change its appearance, if not, change it back. All you need to do is to keep a reference to the object. Assign it via the inspector or inside the if on first hit. You already seem to be doing it (RaycastHitObject) but don't seem to make use of it.
// Target gameobject to change material of
GameObject target;
RaycastHit hit;
Ray BoardCheck = new Ray(transform.position, Vector3.down);
// Perform raycast
if (Physics.Raycast(BoardCheck, out hit))
{
// Compare collider tag
if (hit.collider.tag == "BoardBlock")
{
// Keep reference
target = hit.collider.gameObject;
// Change material
target.GetComponent<Renderer>().material = highlightMat;
}
else
{
// Change back material
target.GetComponent<Renderer>().material = nonHighlightMat;
}
}
You need to store the hit target
Before coming to that personally I would use a dedicated component like e.g.
public class Focusable : MonoBehaviour
{
[SerializeField] private Renderer _renderer;
private Material idleMaterial;
[SerializeField] private Material focusedMaterial;
private void Awake ()
{
if(!_renderer) _renderer = GetComponent<Renderer>();
idleMaterial = _renderer.material;
}
private bool isFocused;
public bool IsFocused
{
get => _isFocused;
set
{
_isFocused = value;
_renderer.material = value ? focusedMaterial : idleMaterial;
}
}
}
And then in your controller do
// Stores the currently hit object
private Focusable currentTarget;
private void OnMouseDrag()
{
// I will just trust that you know what you do with those rays
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
var rayPoint = ray.GetPoint(distance);
rayPoint.y = 2;
transform.position = rayPoint;
var BoardCheck = new Ray(transform.position, Vector3.down);
if (Physics.Raycast(BoardCheck, out var hit)
// instead of the tag you can now directly check for the component
// as this is optional you could of course also just stick to the tag if you want to
&& hit.collider.TryGetComponent<Focusable>(out var focusable))
{
// -> we are hitting one of our objects
// if it is still the same as before we don't need to do anything
if (currentTarget != focusable)
{
if(currentTarget)
{
// did we hit another one before? -> unfocus
currentTarget.IsFocused = false;
}
// now focus and store this hit target once
currentTarget = focusable;
currentTarget.IsFocused = true;
}
}
else
{
// we don't hit a focusable at all
if (currentTarget)
{
// if there was a previous hit unfocus it once
currentTarget.IsFocused = false;
currentTarget = null;
}
}
}
Related
So, i have a script, and two objects, one copy of the another, exactly what one does, the other does too, but in other position. The script do the following:
get the actual value of the heat variable of the object and use this for the second part;
the second part is get the heat value and check if it's higher or equal than 850,
if it is, check if the player pressed the button to transform the object mesh in another mesh
but, when the button is pressed, only the first object has it mesh changed, i already tried raycast, a lot of additional code, ineficient code and don't worked. I know i can just make two similar scripts, but i have plans to create more objects when the player wants, so, it's not going to work...
the script:
using UnityEngine;
using DG.Tweening;
public class Iron : MonoBehaviour
{
private float Heat;
private bool Heating;
[Header("Game Objects")]
[SerializeField] private GameObject WeaponCanvas;
[Header("Materials")]
[SerializeField] private Material HotIron;
[SerializeField] private Material MediumIron;
[SerializeField] private Material CoolIron;
[Space]
[Header("Meshs")]
[SerializeField] private Mesh SimpleSwordMaterial;
[Space]
[Header("Text Mesh Pro")]
[SerializeField] private TMPro.TMP_Text TemperatureText;
private bool Hot;
void Update()
{
if (Heating && Heat <= 1500)
{
Heat += 1.5f;
}
if (Heat >= 850)
{
GetComponent<Renderer>().material = HotIron;
Hot = true;
}
if (Heat >= 600 && Heat <= 849)
{
GetComponent<Renderer>().material = MediumIron;
Hot = false;
}
if (Heat <= 400)
{
GetComponent<Renderer>().material = CoolIron;
Hot = false;
}
}
void OnCollisionStay(Collision other)
{
if (other.gameObject.tag == "HeatSource")
{
Heating = true;
}
if (!(other.gameObject.tag == "HeatSource"))
{
Heating = false;
}
}
public void SimpleSword()
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 3.5f))
{
hit.transform.gameObject.GetComponent<MeshFilter>().mesh = SimpleSwordMaterial; // i have problems from here (i guess ;-;)
hit.transform.gameObject.GetComponent<MeshCollider>().sharedMesh = SimpleSwordMaterial;
if (hit.transform.gameObject.GetComponent<BoxCollider>() != null)
{
Destroy(hit.transform.gameObject.GetComponent<BoxCollider>());
}
if (hit.transform.gameObject.GetComponent<SphereCollider>() != null)
{
Destroy(hit.transform.gameObject.GetComponent<SphereCollider>());
}
if (hit.transform.gameObject.GetComponent<CapsuleCollider>() != null)
{
Destroy(hit.transform.gameObject.GetComponent<CapsuleCollider>());
}
transform.localScale = new Vector3(1, 1, 1);
}
WeaponCanvas.SetActive(false);
Player.onMenu = false;
Cursor.lockState = CursorLockMode.Locked;
}
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1) && Hot)
{
WeaponCanvas.SetActive(true);
Player.onMenu = true;
Cursor.lockState = CursorLockMode.None;
}
TemperatureText.text = ((int)Heat).ToString() + "°";
TemperatureText.DOFade(1, 0.4f);
}
void OnMouseExit()
{
TemperatureText.DOFade(0, 0.4f);
}
}
Similar to sharedMaterial
sharedMesh is not what you want to use...
It is recommended to use this function only for reading mesh data and not for writing, since you might modify imported assets and all objects that use this mesh will be affected.
Also, be aware that is not possible to undo the changes done to this mesh.
I'm having a bit of trouble understanding the problem... are all objects with the Iron.cs getting a new mesh? if yes, it's the .sharedmesh =.
Furthermore, it's worth noting that each monobehaviour in Unity has a .GetInstanceId() method. You can compare the id of the instance you hit with your raycast and that of the one with the correct temporature.
You should also consider simply instantiating a prefab of a sword instead.
I am trying to disable a component/script on a game object when the Raycast has not hit but enable it when it does hit. I can enable the script and that part works when the Raycast hits, however, when I move away from the object, it is still enabled and I cant seem to figure this out.
The entire code to do this is in my update function.
// Update is called once per frame
void Update()
{
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
float distance = 100f;
Debug.DrawRay(ray.origin, ray.direction * distance, Color.green);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
// hit!
Debug.Log("Raycast is hitting " + hit.transform.gameObject);
if (hit.transform.tag == "Crate")
{
crossair.color = Color.red;
hit.transform.GetComponent<Outline>().enabled = true; // this works
}
else
{
crossair.color = Color.white;
hit.transform.GetComponent<Outline>().enabled = false; //Does not work
}
}
}
You will need to store the currently active one in order to deactivate it in case
private const float distance = 100f;
private Outline currentHit;
void Update()
{
var ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
if (Physics.Raycast(ray, out var hit))
{
// hitting something
// Actually checking this way makes you tag redundant ;)
if (hit.transform.TryGetComponent<Outline>(out var outline))
{
// Hitting and outline!
Debug.DrawLine(ray.origin, hit.point, Color.red);
if(outline != currentHit)
{
// Hitting a different outline!
crossair.color = Color.red;
if(currentHit)
{
currentHit.enabled = false;
}
currentHit = outline;
currentHit.enabled = true;
}
}
else
{
// Hitting something that is not an outline
crossair.color = Color.white;
Debug.DrawLine(ray.origin, hit.point, Color.green);
if(currentHit)
{
currentHit.enabled = false;
currentHit = null;
}
}
}
else
{
// Not hitting anything
crossair.color = Color.white;
Debug.DrawRay(ray.origin, ray.direction * distance, Color.green);
if(currentHit)
{
currentHit.enabled = false;
currentHit = null;
}
}
}
In my Unity game (top-down 2D shooter) there are some enemies flying along a path (via DOTween). My player casts a ray into the scene in order to get an enemy-object right in front of the player. So far so good.
Now the problem is that I want only one enemy-object as a result, i.e. the raycast should stop when it hits an enemy-object for the very first time. How do I achieve this?
I need only one enemy-object hit by the raycast because there is a crosshair in my game and when the raycast starts and the enemies fly along the path the crosshair jumps forth and back (and I don't want this - the crosshair should stay at the first enemy hit by the raycast).
Raycast video
This is my code (attached to the player):
void FixedUpdate() {
//crosshair: Cast a ray straight up.
float _size = 12f;
Vector2 _direction = this.transform.up;
RaycastHit2D _hit = Physics2D.Raycast(this.transform.position, _direction, _size);
if (_hit.collider != null && _hit.collider.tag == "EnemyShipTag") {
// We touched something!
Vector2 target = new Vector2(_hit.collider.gameObject.transform.position.x, _hit.collider.gameObject.transform.position.y);
const float moveTime = 0.1f;
float step;
step = Vector2.Distance(crosshairGO.transform.position, target);
crosshairGO.transform.position = Vector2.MoveTowards(crosshairGO.transform.position, target, step / moveTime * Time.deltaTime);
Vector2 _pos3 = new Vector2(this.transform.position.x, crosshairGO.transform.position.y);
crosshairGO.transform.position = _pos3;
crosshairBegin = false;
} else {
// Nothing hit
Vector2 _pos2 = new Vector2(this.transform.position.x, 4.5f);
if (crosshairBegin) {
crosshairGO.transform.position = _pos2;
} else {
Vector2 _pos4 = new Vector2(this.transform.position.x, crosshairGO.transform.position.y);
crosshairGO.transform.position = _pos4;
}
}
}
If I understand you right. You could create a bool value and set it true after you hit something.
For example:
Vector2 _pos3 = new Vector2(this.transform.position.x, crosshairGO.transform.position.y);
crosshairGO.transform.position = _pos3;
crosshairBegin = false;
youHitSomething = true;
Before you create the ray
you could write an if
if(!youHitSomething)
{
float _size = 12f;
Vector2 _direction = this.transform.up;
RaycastHit2D _hit = Physics2D.Raycast(this.transform.position, _direction, _size);
if (_hit.collider != null && _hit.collider.tag == "EnemyShipTag")
{
// We touched something!
// your Code
youHitSomething = true;
}
else
{
// Nothing hit
// your code
}
}
To let the ray cast again you could create a new method
public void ActivateRay()
{
youHitSomething = false;
}
I need to make somehow, that when a player runs through raycast, the score value updates by one. But instead, it updates every frame.
I tried to fix it with boolean check, but anyway, it's work as before.
Where did I make a mistake?
Here's the code:
public class ScoreBehavior : MonoBehaviour{
public delegate void ScoreUpOnPass();
public static event ScoreUpOnPass onRayHit;
private int score = 0;
private bool hitOnce = false;
void Start()
{
}
void Update()
{
RaycastHit hit;
Ray checkRay = new Ray(transform.position, Vector3.down);
if(Physics.Raycast(checkRay, out hit))
{
if (hit.collider.tag == "Player" && !hitOnce)
{
hitOnce = true;
Debug.Log(score); // updates every frame
onRayHit?.Invoke();
}
else
{
hitOnce = false;
}
}
}
}
I would try moving your if check a bit in case it hits nothing or hits an object that is not tagged player.
void Update()
{
RaycastHit hit;
Ray checkRay = new Ray(transform.position, Vector3.down);
if (Physics.Raycast(checkRay, out hit))
{
if (hit.collider.tag == "Player" && !hitOnce)
{
hitOnce = true;
Debug.Log(score); // updates every frame
onRayHit?.Invoke();
}
}
else if (hitOnce)
{
hitOnce = false;
}
}
I would recommend to use OnTriggerEnter(). Raycasting is just not a correct choice of instrument in this case.
Add a collider at the score point, set it to isTrigger = true and use OnTriggerEnter(Collider col) to check who has entered the trigger area. It will be triggered only once for each object entering the area, and will be triggered again if the object leaves the area and then goes back, so it's the best instrument for this task.
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
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);
}
}
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.
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;
}
}
}
}