How to check whether a key is clicked Unity - c#

I have a teleport orb in my game that you can hover over and click e to use to teleport to, however when I hold my e button down and hover over the orb it teleports me anyways, I want to make it so you have to hover over the orb and click e to teleport not so you can hold e and then hover over it. Any ideas on how to do this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IsLookingAtTeleport : MonoBehaviour
{
public AudioSource teleportsound;
public Rigidbody rb;
void Start()
{
}
void FixedUpdate()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it
//inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Input.GetKey("e"))
{
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
if (hit.transform.tag == "TeleportOrb")
{
Debug.Log("Hit Teleportable Orb");
teleportsound.Play();
rb.transform.position = hit.transform.position;
Destroy(hit.transform.gameObject); // destroy the object hit
}
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}
}

Input.GetKey is fired every frame while the key stays pressed.
What you rather want to use is Input.GetKeyDown which is true only in the one frame when the button is actually pressed down.
Further note that instead of the stringed version I would always go for the ones using KeyCode as parameter.
And then make sure you do your user Input in Update not in FixedUpdate. While the later might work in some cases for getting continous inputs such as GetKey you might miss single event inputs.
And finally whenever a Rigibody is invoved you do not want to set anything through the Transform component.
// Rather configure the desired layers you want to hit here
// via the Inspector in Unity!
// actually way better than just including everything rather only
// select these layers that actually should be considered as teleport targets
// This way you wouldn't even need to check for Tags!
// just rather place all your teleport objects on a Teleport layer
// and only use a raycast on that layer
// see https://docs.unity3d.com/Manual/Layers.html
[serializeField] private LayerMask hitLayers;
private void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
// store the ray once
var ray = new Ray(rigibody.position, rigidbody.rotation * Vector3.forward);
if (Physics.Raycast(ray , out var hit, Mathf.Infinity, hitLayers))
{
Debug.DrawRay(ray.origin, ray.direction * hit.distance, Color.yellow);
Debug.Log("Did Hit");
// Rather use CompareTag instead of string based ==
if (hit.gameObject.CompareTag("TeleportOrb"))
{
Debug.Log("Hit Teleportable Orb");
teleportsound.Play();
rb.position = hit.transform.position;
// destroy the object hit
Destroy(hit.gameObject);
}
}
else
{
Debug.DrawRay(ray.origin, ray.direction * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}

Related

Rotation of a single object when script is attached to multiple objects

I have a game in Unity in which I have added three objects (Cube, Cylinder, Capsule). To these game objects, I have added a script to rotate them along an axis when they press the left click and x, y or z, rotating the object along the respective axis. But when I attempt rotate a single object, the other objects rotate too. How do I rotate each object without affecting the other objects.
if (Input.GetMouseButton(0) && Input.GetKey(KeyCode.Z))
{
transform.RotateAround(transform.position, transform.forward, Time.deltaTime * 90f);
}
if (Input.GetMouseButton(0) && Input.GetKey(KeyCode.X))
{
transform.RotateAround(transform.position, transform.right, Time.deltaTime * 90f);
}
if (Input.GetMouseButton(0) && Input.GetKey(KeyCode.Y))
{
transform.RotateAround(transform.position, transform.up, Time.deltaTime * 90f);
}
I assume what you are trying to achieve is
click on an object with the mouse Cursor and hold
then rotate this object using the according keys.
You actually need only one single script for this, not on every object but just one in your scene (like a central controller)
public class ObjectRotator : MonoBehaviour
{
[SerializeField] private Camera _camera;
//Optionally if you only want to hit objects on (a) certain layer(s)
//[SerializeField] private LayerMask layers;
private void Awake ()
{
if(!_camera) _camera = Camera.main;
}
private void Update ()
{
if(Input.GetMouseButton(0))
{
// Shoot a Raycast from the mouse position into your scene to check if you hit an object
var ray = _camera.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out var hit)
// or if using the layer mask
//if(Physics.Raycast(ray, out var hit, float.positiveInfinity, layers)
{
if (Input.GetKey(KeyCode.Z))
{
// Instead of using RotateAround rather use Rotate which rotated around this object's
// pivot position anyway. By default it is in local space so also no need for transform.forward etc
hit.transform.Rotate(Vector3.forward * Time.deltaTime * 90f);
}
if (Input.GetKey(KeyCode.X))
{
hit.transform.Rotate(Vector3.right * Time.deltaTime * 90f);
}
if (Input.GetKey(KeyCode.Y))
{
hit.transform.Rotate(Vector3.up * Time.deltaTime * 90f);
}
}
}
}
}
NOTE: Requires your target objects to have colliders (and the selected layer(s))
As an alternative if you rather want to go for a solution with one script per object you could rather use e.g.
public class Rotation : MonoBehaviour
{
// This is called by Unity when the mouse is going down while hovering this object's collider
private void OnMouseDown ()
{
// Start the RotationRoutine
StartCoroutine (RotationRoutine());
}
// This is called when the mouse button goes up while hovering this object's collider
private void OnMouseUp()
{
// To simplify things just stop any routine started by this behavior
StopAllCoroutines ();
}
// Also stop when the mouse leaves this object's collider while still being pressed
private void OnMouseExit()
{
StopAllCoroutines ();
}
private void RotationRoutine()
{
// Whut?! No worries ;) This is fine in a Coroutine as soon as you "yield" somewhere inside
while(true)
{
if (Input.GetKey(KeyCode.Z))
{
transform.Rotate(Vector3.forward * Time.deltaTime * 90f);
}
if (Input.GetKey(KeyCode.X))
{
transform.Rotate(Vector3.right * Time.deltaTime * 90f);
}
if (Input.GetKey(KeyCode.Y))
{
transform.Rotate(Vector3.up * Time.deltaTime * 90f);
}
// Basically tells Unity to "pause" here, render this frame and
// continue from here in the next frame
yield return null;
}
}
}
Try three separate monobehaviour with input condition Input.GetKey(KeyCode.X), Input.GetKey(KeyCode.Y), Input.GetKey(KeyCode.Z) respectively:
if (Input.GetMouseButton(0) && Input.GetKey(KeyCode.X)) {
transform.RotateAround(transform.position, transform.forward, Time.deltaTime * 90f);
}
The transform applies to the transform of the gameObject the Monobehvaiour is attached to, so if the you have the script attached to many gameobjects, the logic applies to all of them.
You need to make 3 different Monobehaviours and handle the input logic respectively, or make a global Monobehaviour rotation handler, where you keep a reference for each of your gameObjects so that you can handle each of the transforms respectively.

Unity3D - Using Raycasting to detect Game Objects. Top-down view

I'm making a top down space resource-gathering game, with 3D models.
I'm trying to cast a ray towards my mouse position, to detect collisions with the objects in my game,
for my little spaceship to know which direction to head to find resources.
The resources are in the form of asteroids the player has to break to gather.
In the code, I use the "center" vector to find the middle of the screen/player position, as the camera follows the player around. All movement happens along the X,Y axis. center.Z is set to 15 simply to counteract the -15Z position of the main camera in worldspace.
So far, the code "works" to the extent that it can detect a hit, but the ray doesn't travel in the direction of the mouse. I have to hold it way off for it to hit, and it's difficult to replicate, to the point where it almost seems random. Might the ray not be able to make sense of the mouse position?
In case I've worded myself poorly, this search ability is not meant to break the asteroid, simply locate it. The breaking ability is a separate script.
Code:
public class AbilitySearch : MonoBehaviour
{
Vector3 center;
Vector3 mousePos;
Vector3 direction;
private float range = 100f;
void Update()
{
center = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 15.0f));
mousePos = Input.mousePosition;
direction = mousePos - transform.position;
if (Input.GetMouseButton(1))
{
Search();
}
}
void Search()
{
RaycastHit hit;
if (Physics.Raycast(center, direction, out hit, range))
{
Debug.Log("You hit " + hit.transform.name);
}
}
}
Thanks in advance
When using Input.mousePosition the position is a pixel coordinate on your screen, so if your mouse was in the bottom left corner of your screen it would be 0,0. This causes the direction to be inaccurate. When what you need is to use the game space coordinates, which is done by using Camera.ScreenToWorldPoint(Input.mousePosition), as an example.
This solution allows you to click on a gameObject (provided it has a collider attached), move the current gameObject towards the clicked object, as well as gather an array of possible gameObjects (RaycastHit[s]) in the direction of the clicked one.
Vector3 destination;
RaycastHit[] possibleTargets;
bool isTravelling;
float searchDistance = 5f;
private void Update()
{
//If right mouse button has been pressed down
if (Input.GetMouseButtonDown(1))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit)) //If ray collides with something
{
Search(hit);
}
}
//If going to destination
if (isTravelling)
{
float step = 2f * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, destination, step);
//If approx arrived
if (Vector3.Distance(transform.position, destination) < 0.1f)
{
isTravelling = false; //Stop moving to destination
Debug.Log("You've arrived");
}
}
}
private void Search(RaycastHit _hit)
{
//Normalise directional vectors, so (if needed) they can be multipled as expected
Vector3 direction = (_hit.point - transform.position).Normalized();
RaycastHit secondHit;
//Grab objects in direction of clicked object (including the one clicked)
possibleTargets = Physics.RaycastAll(transform.position, direction, searchDistance);
Debug.Log($"There are {possibleTargets.Length} possible targets ahead");
Debug.Log($"You hit {_hit.transform.name}");
//Set destination, and set to move
destination = _hit.point;
isTravelling = true;
}
You used the ViewportToWorldPoint method, which expects normalized viewport coordinates in range 0 to 1, but you supplied what seems to me as world coordinates of your camera as its parameter.
You only need to cast a ray from camera to mouse pointer world position (see first line of code in method FindAsteroid) to check for collision with asteroid. The returned RaycastHit provides you with information about the collision - hit position, gameobject, collider - which you can use for other game logic, e.g. shooting a projectile from spaceship to asteroid hit point.
I edited your class and included a screenshot from my simple scene below, which shows the two different "rays":
The yellow ray goes from camera to asteroid hit point
The magenta ray goes from spaceship position to asteroid hit point.
I would also recommend filtering the raycast to affect only specific colliders -
see LayerMask (section Casting Rays Selectively)
public class AbilitySearch : MonoBehaviour
{
private float range = 100f;
private Camera mainCam;
private void Awake()
{
// TIP: Save reference to main camera - avoid internal FindGameObjectWithTag call
mainCam = Camera.main;
}
private void Update()
{
if (Input.GetMouseButton(1))
{
if (FindAsteroid(out var asteroidHit))
{
// Draw a line from spaceship to asteroid hit position
Debug.DrawLine(transform.position, asteroidHit.point, Color.magenta, Time.deltaTime);
Debug.Log($"You hit {asteroidHit.transform.name}");
}
}
}
private bool FindAsteroid(out RaycastHit hit)
{
// Create a ray going from camera position to mouse position in world space
var ray = mainCam.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, range))
{
// Draw a line from camera to world mouse position
Debug.DrawLine(ray.origin, hit.point, Color.yellow, Time.deltaTime);
// An asteroid was found
return true;
}
// An asteroid was NOT found
return false;
}
}
Sample spaceship scene

Is there a way to rotate an object that is a child of a parent with animator?

The animator make the object to rotate so the script I'm using with a raycast is not working.
Only if I disable the animator then when the raycast hit an item the head of the character will rotate look at the item.
but is there a way to make that the player head will rotate look at the item even if the animator is still working ?
The script that attach to the player :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Interactable : MonoBehaviour
{
public Transform objToRotateLookAT;
private bool raycastSucceed;
// Start is called before the first frame update
void Start()
{
}
void FixedUpdate()
{
int layerMask = 1 << 8;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
if (!raycastSucceed)
Debug.Log("Did Hit");
raycastSucceed = true;
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.red);
Vector3 relativePos = hit.transform.position - objToRotateLookAT.position;
// the second argument, upwards, defaults to Vector3.up
Quaternion rotation = Quaternion.LookRotation(relativePos, Vector3.up);
objToRotateLookAT.rotation = rotation;
}
else
{
if (raycastSucceed)
Debug.Log("Did not Hit");
raycastSucceed = false;
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.yellow);
}
}
}
The player head will rotate facing the item detected only if the animator is disabled. Is there a way to keep the animator active and also to rotate the head facing the detected item ?
Well, you can't make that in Player's animator because you disabled it. But you can change its rotation in another game object. For example, you can put a HeadManager gameObject with an Animator inside. And whenever you want to make the head rotate, you can just make the Bool in animator of HeadManager.
If you have more than 1 object that you want to rotate their head, you can make this HeadManager child of that game object. And when you hit, you can just simply get it's children's animator and make the bool true.
https://docs.unity3d.com/ScriptReference/Component.GetComponentInChildren.html
You can check this to get the children's animator.

2D Raycast does not correctly detect overlapping colliders and ignores Sorting Layer

I want to be able to interact with my gameobjects while organising my sprites with Sorting Layer.
Right now, Sorting Layer do not affect my Raycast/collider situation and only change the visual appearance of my order.
In my Test scene I have two sprites: a Background and one Object.
Both have a 2D Box Collider component.
My Raycast script is suppose to detect on which Object my mouse is hovering on; either the Background (BG) or the Object, which lays on top of the BG.
As long as the BG collider is enabled in the inspector, my script will ONLY recognize the BG, even if the other object lays on top of my BG (visually and according to the Sorting Layer) (both Z-positions = 0, Sorting Layer Default (BG: 0, Object 1)).
The collider have the correct size and if I disable or remove the Collider from the BG, my script will detect the other Object just fine.
The only way to make sure that my Object gets detected, while my BG collider is activated is by changing the Objects Z position to a position between my BG and my Camera (between smaller 0 and greater -10)
I tried to:
this is a fresh 2D project, so my cameras Projection is set to Orthograph, my BG and Object use Sprite Renderer and is use 2D collider
change the "Order in Layer", back and forth, including negative numbers.
add a Foreground (and Background) "Sorting Layer"
(both options only had a visual effect and did not affect the Raycast output)
change the positions in the hierarchy.
removing and re-adding the colliders. (!) I observed that the 2D Box collider I added last, will always be behind every other colliders. This is something I can work with for now. But will become a problem later in the future, when the project gets bigger.
I want to be able to interact with items and doors etc. (clicks and reactions to mouse hovering) in my 2D click adventure. If 2D Raycast is not the way to go to archive this, I would be glad to know about the proper technique to use in this situation.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayCast : MonoBehaviour
{
[SerializeField] private Vector2 mousePosWorld2D;
RaycastHit2D hit;
void Update()
{
mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
hit = Physics2D.Raycast(mousePosWorld2D, Vector2.zero);
if (hit.collider)
{
Debug.Log("Found collider: " + hit.collider.name);
}
else
{
Debug.Log("No collider found.");
}
}
}
I expected the "Sorting Layer" or "Order in Layer" to have an impact on my Raycast, while changing the Z position does nothing. But it is the other way around. Sorting Layer only have a visual effect and moving my Object closer to the camera (Z position) helps to detect the right collider.
How do I solve the problem?
What gets hit first from a Ray has only to do with how close the collider is to the camera, and nothing with the order in the Hierarchy or the SortinLayers.
To find the "highest" RaycastTarget based on the SortingLayer, I created the Script below.
We fire a Raycast and iterate over all the Colliders that we hit, by using a foreach-loop. Then the function will return the GameObject with the highest SortingLayer. Note: If there is more than one GameObject with the highest SortingLayer, the function will return the last one it found.
using UnityEngine;
public class Raycast : MonoBehaviour
{
// The name of the relevant Layer
[SerializeField] string layerToCheck = "Default";
void Update()
{
Vector2 mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject result = GetHighestRaycastTarget(mousePosWorld2D);
// Do Stuff example:
Debug.Log($"Highest Layer: {result}");
if (Input.GetMouseButtonDown(0)
&& result != null)
{
Destroy(result);
}
}
// Get highest RaycastTarget based on the Sortinglayer
// Note: If multiple Objects have the same SortingLayer (e.g. 42) and this is also the highest SortingLayer, then the Function will return the last one it found
private GameObject GetHighestRaycastTarget(Vector2 mousePos)
{
GameObject topLayer = null;
RaycastHit2D[] hit = Physics2D.RaycastAll(mousePos, Vector2.zero);
foreach (RaycastHit2D ray in hit)
{
SpriteRenderer spriteRenderer = ray.transform.GetComponent<SpriteRenderer>();
// Check if RaycastTarget has a SpriteRenderer and
// Check if the found SpriteRenderer uses the relevant SortingLayer
if (spriteRenderer != null
&& spriteRenderer.sortingLayerName == layerToCheck)
{
if (topLayer == null)
{
topLayer = spriteRenderer.transform.gameObject;
}
if (spriteRenderer.sortingOrder >= topLayer.GetComponent<SpriteRenderer>().sortingOrder)
{
topLayer = ray.transform.gameObject;
}
}
}
return topLayer;
}
}
Have you tried with the script that it's in the Unity official documentation?
https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
using UnityEngine;
// C# example.
public class ExampleClass : MonoBehaviour
{
void Update()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}

Unity Physics2D.Raycast hits itself

I'm trying to use Physics2D.Raycast in order to check if the player is on the ground (I know that there are other approaches to check if the player is on the ground but I think that the raycast is the most reliable).
The problem is that in my scenario it returns the player itself as hit and I really don't understand why and what should I do.
My code (in PlayerController) is the following:
public bool IsGrounded () {
Bounds bounds = this.playerCollider.bounds;
Vector3 rayOrigin = bounds.center;
rayOrigin.y -= bounds.extents.y;
RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.down, 0.1f);
if (hit.collider != null) {
Debug.Log ("Collider is: " + hit.collider.name);
}
return hit.collider != null;
}
And I can debug the casted ray using:
Debug.DrawLine (rayOrigin, new Vector3 (rayOrigin.x, rayOrigin.y - 0.1f, rayOrigin.z), Color.magenta);
...and it gets casted as expected, while the Debug.Log always reports "Player" which is itself and I don't know how it's possible. So what's wrong?
ps. I'm using Unity 5.3
The problem is occurring because your Player is overlapping at the start of the raycast. There are few ways to fix this:
1.Disable Queries Start In Colliders.
Go to Edit->Project Settings->Physics 2D then make sure that Queries Start In Colliders is NOT checked. Your code ran fine after changing that. Here is a screenshot:
2.Another solution is to use layers.Raycasting but ignoring the Player layer.Before you do that, make sure to create a layer called Ground and put your ground GameObject to the Ground layer then create another layer called Player and put your player in the Player layer. We can now use bitwise operator to exclude Player layer from the raycast.
Now, lets assume that Player layer number is 9. The code below should fix your problem.
public int playerLayer = 9;
int layerMask = ~(1 << playerLayer); //Exclude layer 9
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.down, 0.1f, layerMask);
That's it. Both of these were able to solve your problem.
For other people reading, below are other ways to easily detect when Player is touching the floor without using Physics2D.Raycast or doing all those things above.
Simply attach to the Player.
public class Player : MonoBehaviour
{
public LayerMask groundLayer;
Collider2D playerCollider;
bool grounded;
void Start()
{
playerCollider = gameObject.GetComponent<Collider2D>();
}
public bool IsGrounded()
{
grounded = Physics2D.OverlapCircle(playerCollider.transform.position, 1, groundLayer);
return grounded;
}
}
Or you can use IsTouchingLayers.
public bool IsGrounded()
{
grounded = grounded = playerCollider.IsTouchingLayers(groundLayer.value);
return grounded;
}
Vector2.down vector does not present the object space's down vector all the time. You can use -transform.up.
Also, if it is acceptable you can add some padding to start point of the ray, like rayOrigin.y += 0.0001f;.

Categories