Im a beginner in Unity and im actually making a horror game,I recently added reticle into my game.
I want to change the reticle if u can interact w an object (im using raycasts).
Can someone check my code pls?
public Camera mainCam;
public LayerMask interactableLayerMask;
UnityEvent onInteract;
[SerializeField] GameObject reticle;
[SerializeField] GameObject interactReticle;
void Update()
{
RaycastHit hit;
if (Physics.Raycast(mainCam.transform.position, mainCam.transform.forward, out hit, 2, interactableLayerMask))
{
if (hit.collider.GetComponent<Interactable>() != false)
{
onInteract = hit.collider.GetComponent<Interactable>().onInteract;
interactReticle.SetActive(true);
reticle.SetActive(false);
if (Input.GetKeyDown(KeyCode.Mouse0))
{
onInteract.Invoke();
}
}
if (hit.collider == null)
{
interactReticle.SetActive(false);
reticle.SetActive(true); }
}
}
I would not rely on if (hit.collider == null). Physics.Raycast returns a bool to indicate if it hit something, see docs
bool Returns true if the ray intersects with a Collider, otherwise false.
Try something like this:
if (Physics.Raycast(...) {
// your code for interaction
}
else {
interactReticle.SetActive(false);
reticle.SetActive(true);
}
This could be then further optimised with a flag like hasInteractiveFocus to not set the active state of the game objects all the time.
Related
THIS POST IS NOW IRRELEVANT PLEASE CHECK MY NEWER ONE
I am making a horror game with next bots on VR (Quest/Quest2)
the player has 1 "Mixed" light on it and the bots use pathfinding AI with navmeshes and stuff but it seems that when the next bot gets within I'll say like 10 feet of the player and the player can see it and all the game just decides it wants to freeze and stop working!
It seems to happen before the player "Dies" but I'm not fully sure.
public class Kill : MonoBehaviour
{
// Start is called before the first frame update
public GameObject tes;
public GameObject LocoToOff;
public Vector3 SPOT;
public GameObject SprintLoco;
public GameObject Dead;
public AudioSource audioSource;
private bool Using;
public GameObject Safe;
public LocomotionControllery Loco;
private bool ChillOutItsGoing;
IEnumerator TurnOff()
{
ChillOutItsGoing = true;
LocoToOff.SetActive(false);
SprintLoco.SetActive(false);
Dead.SetActive(true);
Loco.Disabled = true;
audioSource.Play();
yield return new WaitForSeconds(3);
//foreach (var pls in WallsNStuff)
//{
// pls.SetActive(false);
//}
tes.transform.position = SPOT;
//yield return new WaitForSeconds(0.05f);
Loco.Disabled = false;
Dead.SetActive(false);
//foreach (var pls in WallsNStuff)
//{
// pls.SetActive(true);
//}
Safe.SetActive(true);
ChillOutItsGoing = false;
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "BOT")
{
if (ChillOutItsGoing == false)
{
Debug.Log(other.gameObject.name);
Using = LocoToOff.active;
StartCoroutine(TurnOff());
}
}
}
}
I believe it ended up just being because the thing was set to "Cutout" instead of opaque and Cutout I think is in alpha only 2 things I changed that fixed it for sure was setting it to opaque and adding a lil bit of smoothness (just did that idk if it was part of the fix or not)
I created a transparent cube trigger and I placed it in front of closed doors, so whenever the player walks near the door a message appears saying "this door is locked".
However, I want the message to be gone whenever the player is Not pointing to the door. currently, it shows even when I turn around, the player needs to walk away from the door to make the message disappear.
Here is my code:
public class DoorsTrigger : MonoBehaviour
{
public GameObject partNameText;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
partNameText.SetActive(true);
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
{
partNameText.SetActive(false);
}
}
}
How can I modify it to achieve my goal?
Here is a simple example using Vector3.Angle() to get the direction the player is facing relative to that trigger.
public class DoorsTrigger : MonoBehaviour
{
public GameObject partNameText;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
//Assuming 'other' is the top level gameobject of the player,
// or a child of the player and facing the same direction
Vector3 dir = (this.transform.position - other.gameObject.transform.position);
//Angle will be how far to the left OR right you can look before it doesn't register
float angle = 40f;
if (Vector3.Angle(other.gameObject.transform.forward, dir) < angle) {
partNameText.SetActive(true);
}
}
}
private void OnTriggerExit(Collider other)
{
//Make sure the text is actually showing before trying to disable it again
if (other.CompareTag("Player") && partNameText.activeSelf)
{
partNameText.SetActive(false);
}
}
}
Alternatively, you can keep your trigger mostly as is, and do a ray cast from the player to see if they are looking at the door.
//Put this snippet in a function and call it inside the player's Update() loop
RaycastHit hit;
Ray ray = new Ray(this.transform.position, this.transform.forward);
if(Physics.Raycast(ray, out hit))
{
//Tag the gameObject the trigger is on with "Door"
if(hit.collider.isTrigger && hit.collider.CompareTag("Door"))
{
//Here is where you would call a function on the door trigger to activate the text.
// Or better would be to just have that code be on the player.
// That way you can avoid unnecessary calls to another gameObject just for a UI element!
}
}
Whenever my character collides with an object, i add a rigidbody to that object; and i also would like to reapply the same force it should have received with the collision on the same point, so it reacts with physics.
This is my attempt so far, but force is way too much as all objects fly from the scene:
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.GetComponent<Rigidbody>() == null) {
collision.gameObject.AddComponent<Rigidbody>().AddForceAtPosition(collision.impulse, collision.contacts[0].point);
}
}
I also tried with:
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.GetComponent<Rigidbody>() == null) {
collision.gameObject.AddComponent<Rigidbody>().AddForceAtPosition(collision.impulse / Time.fixedDeltaTime, collision.contacts[0].point);
}
}
What i'm doing wrong/missing?
Your point of collision might be to far from the origin of the object.
Check:
if the collision box is proportionnal to the object.
the colision point makes sense with the actual position of the object.
the mass of the rigidbody is adequate.
Note that when position is far away from the center of the rigidbody
the applied torque will be unrealistically large.
from unity doc page
as for the colision interpretation in the code, i found this piece of code in this article describing How to get the impact force of a collision in Unity
public class Character : MonoBehaviour {
private void OnCollisionEnter (Collision collision) {
float collisionForce = collision.impulse.magnitude / Time.fixedDeltaTime;
if (collisionForce < 100.0F) {
// This collision has not damaged anyone...
}
else if (collisionForce < 200.0F) {
// Auch! This will take some damage.
}
else {
// This collision killed me!
}
}
}
I make game with isometric view. When player comes into the house the roof of it hides and player can interact with NPCs, items, etc. But now it can interact with it even when roof is visible. How to detect that item is hidden by house roof or wall or another object?
void Update() {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);
if (Input.GetMouseButtonDown(0)) {
foreach (RaycastHit hit in hits) {
if (hit.collider.tag != "NPC") {
continue;
}
//Interact ...
}
}
}
You can simply check the distance between the hit wall/roof and NPC, from the ray-cast origin (camera). Like so:
private Camera cameraRef;
private void Awake() {
// P.S: Cache the 'Camera.main', calls to it can be expensive.
cameraRef = Camera.main;
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = cameraRef.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);
foreach (RaycastHit hit in hits) {
if (hit.collider.tag != "NPC") {
continue;
} else if (RaycastHitRoofOrWallFirst(hits, hit.collider.gameObject)) {
// This NPC is hidden behind a roof/wall.
continue;
}
// Interaction...
}
}
}
/// <summary>
/// Check if a target object is being hidden behind a roof/wall.
/// </summary>
/// <param name="hits">The hits that the raycast gotten.</param>
/// <param name="targetObject">The gameobject to check against.</param>
/// <returns>Return true if the target object is hidden, false if not.</returns>
private bool RaycastHitRoofOrWallFirst(RaycastHit[] hits, GameObject targetObject) {
foreach (RaycastHit hit in hits) {
if (hit.collider.CompareTag("roof") || hit.collider.CompareTag("wall")) {
float distanceFromCameraToObstacle = Vector3.Distance(cameraRef.transform.position, hit.collider.transform.position);
float distanceFromCameraToNPC = Vector3.Distance(cameraRef.transform.position, targetObject.transform.position);
// Check if the NPC is closer to the camera (raycast origin)
// compared to the roof or wall.
if (distanceFromCameraToObstacle < distanceFromCameraToNPC) {
// The roof/wall is closer to the camera (raycast origin)
// compared to the NPC, hence the NPC is blocked by the roof/wall
return true;
}
}
}
return false;
}
Here is a small visual diagram of what it should check for:
Or just use simple raycast...
If possible depending on the context, instead of using Physics.RaycastAll, you can use Physics.Raycast.
It returns the first object that the ray-cast hits.
Adding to this answer an alternative could maybe also be using OnBecameVisible
OnBecameVisible is called when the object became visible by any Camera.
This message is sent to all scripts attached to the Renderer.
and OnBecameInvisible
OnBecameInvisible is called when the Renderer is no longer visible by any Camera.
This message is sent to all scripts attached to the Renderer.
OnBecameVisible and OnBecameInvisible are useful to avoid computations that are only necessary when the object is visible.
For activating and deactivating the according NPC's colliders so the Raycast anyway will only work on visible objects in the first place.
Like on the NPCs have a script
public class InteractableController : MonoBehaviour
{
// you can also reference them via the Inspector
public Collider[] colliders;
private void Awake()
{
// pass in true to also get inactive components
if(colliders.Length = 0) colliders = GetComponentsInChildren<Collider>(true);
}
private void OnBecameInvisible()
{
foreach(var collider in colliders)
{
collider.enabled = false;
}
}
private void OnBecameVisible()
{
foreach(var collider in colliders)
{
collider.enabled = true;
}
}
}
However
Note that object is considered visible when it needs to be rendered in the Scene. It might not be actually visible by any camera, but still need to be rendered for shadows for example. Also, when running in the editor, the Scene view cameras will also cause this function to be called.
Game characters are going somewhere that is clicked with the mouse
My problem : the code only works over a GameObject. I want to use one in the ground in the box, it can not.
I use a line of code :
public GameObject obj;
void Update() {
if(Input.GetMouseButton(0)) {
RaycastHit rayHit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (obj.collider.Raycast (ray, out rayHit, Mathf.Infinity)) {
transform.position = rayHit.point;
renderer.material.color = Color.green;
}
}
}
Map as the only model so I need to do. prevents me from making changes to my map
I need to call the individual sub-objects. but I could not.
Translating google translate :)
Try using Physics.Raycast(ray, out rayHit). This will raycast from your screen into the world. Then you can check the type of the object:
if (rayHit.collider.GetType() == typeof(YourTypeHere))
{
doSomething();
}