how to destroy a specific object when touch it - c#

hey guys I am trying to destroy a specific object coming towards me when touch it just like guitar hero.
my code right now just destroy wherever I touch
I am new to coding so I appreciate a basic explanations thx you
{
private float forcemult = 200;
private Rigidbody rb;
Ray ray;
RaycastHit hit;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
rb.AddForce(transform.forward * forcemult * Time.deltaTime);
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
{
ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
}
if (Physics.Raycast(ray, out hit ))
{
Debug.Log("hit something");
Destroy(hit.transform.gameObject);
}
else
{
if (Input.touchCount > 0)
{
Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
}
}

You can give your gameobject a specific tag and then check if the gameobject you hitted has this tag.
Something like:
if(hit.collider.tag == "Enemy") {
// Do Stuff
}
This is the simplest way to check for a collision with a specific gameobject.
You can also check if the name of the gameobject you hitted has the name you want for example:
if(hit.collider.gameobject.name == "Enemy01") {
// Do Stuff
}
I personally don't like the name checking variant, because it only works reliably in specific situations.
Another method you can use is attaching a script to the gameobject and then check if the gameobject has this script:
YourScript script = hit.collider.gameobject.GetComponent<YourScript>();
if(script != null) {
// Do stuff
}
This is probably the best way of handling this because you can instantly call an event after the collision happened.

The part of the code that is responisble for detecting objects is if (Physics.Raycast(ray, out hit )). You can add two additional inputs to this method to destory the objects you desire. The first input is maxDistance which is the max distance the ray should check for collisions, the second one is layerMask which is a Layer mask that is used to selectively ignore colliders when casting a ray.
Define a new layer in unity and assign it to the objects you wish to desotry.
click on edit layers:
Define a serialized field of type layerMask and assign the layer (Target in my case) you defined in the inspector:
// assign in the inspector
[SerializeField] private LayerMask toHit;
// your code
//..
// you can define the hit variable in the if statement if you
// don't need it somewhere else using the syntax I have used,
// otherwise remove the var keyword, the 100 is the maxDistance value,
// use any value you wish. toHit determines what physic engine selects
// and what ingores.
if(Physics.Raycast(ray, out var hit, 100, toHit))
{
Debug.Log($"hit: {hit.transform.name}");
Destroy(hit.transform.gameObject);
}
(Unity Doc)

Related

How to check whether a key is clicked Unity

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");
}
}
}

How to tweak this melee script to be more responsive?

I followed this video to get a damage script : https://www.youtube.com/watch?v=aSeE7u8Cs-I&list=PLZ1b66Z1KFKiaTYwyayb8-L7D6bdiaHzc&index=11
The problem is, it's for a gun and for some reason even when the raycast is dead ahead sometimes the knife hit won't register. Is there any way to maybe just make it so that anything a unit in front of me takes damage on click/attack?
This is the current code I have:
void Start()
{
animator = GetComponentInChildren<Animator>();
}
// Update is called once per frame
void Update()
{
if (Input.GetButtonDown("Attack"))
{
StartCoroutine(Poke());
}
else
{
}
IEnumerator Poke()
{
RaycastHit knife;
//this line was causing weird delay but I can't remember 100% what it was for and if it's still needed
//isAttacking = true;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out knife))
{
knife.transform.SendMessage("DamageZombie", DamageAmount, SendMessageOptions.DontRequireReceiver);
}
animator.SetBool("isAttacking", true);
yield return new WaitForSeconds(.1f);
animator.SetBool("isAttacking", false);
}
}
} ```
Try this:
bool isAttacking = false;
void Start() {
animator = GetComponentInChildren<Animator>();
}
void Update() {
if (Input.GetButtonDown("Attack")) {
StartCoroutine(Poke());
}
}
IEnumerator Poke() {
if(!isAttacking){
isAttacking = true;
RaycastHit knife;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out knife)) {
knife.transform.SendMessage("DamageZombie", DamageAmount, SendMessageOptions.DontRequireReceiver);
}
animator.SetBool("isAttacking", true);
yield return new WaitForSeconds(.1f);
animator.SetBool("isAttacking", false);
isAttacking = false;
}
}
Also have in mind that Physics.Raycast may use LayerMask to detect objects, if nothing works, take a look at that.
Well you are probably hitting some other collider, some GameObject that doesn't have any component attached that has a method called DamageZombie.
There are multiple ways to limit the Raycast to specific targets:
Physics.Raycast takes additional parameters
maxDistance
The max distance the ray should check for collisions.
layerMask
A Layer mask that is used to selectively ignore Colliders when casting a ray.
So putting your target enemies on a specific Layer you can already limit the Raycast to only hit these and only when they are close enough:
// Adjust here which layer(s) you want to hit via the Inspector
public LayerMask targetLayers;
...
if (Physics.Raycast(new Ray(transform.position, transform.forward), out var hit, 1f, (int)targetLayers))
{
// Do something
}
So this already filters the raycast and only hits the first object found within the range of 1 unit and only on the layer(s) configured in targetLayers.
As you said you wanted to hit everything you can actually use Physics.RaycastAll and then iterate through all hits
var hits = Physics.RaycastAll(new Ray(transform.position, transform.forward), 1f, targetLayers);
foreach(var hit in hits)
{
// Do something
}
Now this basically does the same as before but now hits every object within a range of 1 unit and on the layer(s) configured in targetLayers.
Using Tags you can limit even further and check after the Raycast what you have hit.
foreach(var hit in hits)
{
if(!hit.CompareTag("Zombie")) continue;
// Do something
}
Finally in general do NOT use SendMessage! It is extremely inefficient and insecure .. you will never know if the object you are hitting even has a component implementing such a method at all ... or if you changed this string in every place after renaming the method ...
Rather actually get the component you want to hit!
var damageScript = hit.GetComponent<YOURSCRIPT>();
if(damageScript)
{
damageScript.DamageZombie(DamageAmount);
}

unity restrict moving object on a plane

I want to restrict a moving object on a plane( the plane may be or may not be a square) here is the prototype:
I attached a mesh collider to the plane and hope to use that to restrict the movement of the sphere the sphere will move randomly, the idea is that when the ball moved to the edge the velocity will be reflected so it will not fall off the plane with this:
public class BallScript : MonoBehaviour {
// Use this for initialization
void Start () {
GetComponent<Rigidbody>().velocity = new Vector3(0.1f,0,0.1f);
}
private void OnCollisionExit(Collision collision)
{
GetComponent<Rigidbody>().velocity = -GetComponent<Rigidbody>().velocity;
}
}
but the ball kept fall at the edge,
I can't make edge colliders cause the shape of the plane will not be decided until runtime.
Is there a way to do that?
The OnCollisionExit function is called when the object is no longer colliding with the other object. In, your case, it will be called not when the ball is on the edge of the plane but when the ball is no longer touching the plane.
The safest way to do this is to use a BoxCollider. I suggest you just use Unity's Cube primitive which comes with a BoxCollider. The mesh on it will be useful and should act as a visual guide when resizing the BoxCollider.
1.Go to GameObject ---> 3D Object ---> Cube and create new Cube Objects. Looking at the shape of your plane, you will need 6 BoxColliders so this means that you need 6 cubes.
2.Resize, move and rotate each one until you they cover each side of the plane.
3.Create new layer and call it "Boundary". Select each cube and set the layer to "Boundary".
4.Got to Edit ---> Project Settings ---> Physics and use the Layer Collision Matrix to make sure that "Boundary" cannot collide with "Boundary".
5.Disable or remove MeshRenderer of each cube and that's that.
The code in your question is unnecessary but if you still want it to reflect then use OnCollisionEnter and check when the ball hits the wall.
Attach to the ball:
Rigidbody rb;
public float force = 50;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void OnCollisionEnter(Collision col)
{
if (col.collider.CompareTag("Boundary"))
rb.AddForce(col.contacts[0].normal * force, ForceMode.Impulse);
}
So here's just an Idea as the OP agreed to put this as an answer
public Layer[] layerPriorities =
{
Layer.Enemy,
Layer.Walkable
};
[SerializeField] float distanceToBackground = 100f;
public delegate void OnLayerChange(Layer newLayer); // declare new delegate type
//lets use event for the protection of the layerchanges
public event OnLayerChange onlayerChange; // instantiate a observer set
//Look for and return priority layer hit
foreach (Layer layer in layerPriorities)
{
var hit = RaycastForLayer(layer);
if (hit.HasValue)
{
raycastHit = hit.Value;
if(layerHit != layer){ //if layer has changed
layerHit = layer;
onlayerChange(layer); //call the delegate
}
layerHit = layer;
return;
}
}
// otherwise return background hit
raycastHit.distance = distanceToBackground;
layerHit = Layer.RaycastEndStop;
}
//? is a nullable parameter
RaycastHit? RaycastForLayer(Layer layer)
{
/*(Use a bitshift) <<*/
int layerMask = 1 << (int)layer; // lets do masking formation
Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit; //used as an out parameter
bool hasHit = Physics.Raycast(ray, out hit, distanceToBackground, layerMask);
if (hasHit)
{
return hit;
}
return null;
}
and create an enum for that
public enum Layer
{
Walkable = 8,
RaycastEndStop = -1 //lowest priority
}
As the Layer.Enemy, You could set it to NotWalkable something like well it depends you.
So the small missing part here is just calculate the distance of the ball from the edge of the plane instead of clicking it . That's what I am telling you about the SqrMagnitude

Unity multiple enemies, raycast dedection?

I got a script like this to create a raycast from maincamera and hit the enemy.
void FixedUpdate(){
if (fire) {
fire = false;
RaycastHit hit;
if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit, range)){
if (Enemy.distance < 80) {
if (hit.collider.tag == "body") {
Debug.Log ("Bullet in the body.");
Enemy.bodyshot = true; //Changing other script variable.
} else if (hit.collider.tag == "head") {
Debug.Log ("Bullet in the head.");
Enemy.headshot = true; //Changing other script variable.
}
}
}
}
}
Enemy Script in Update;
if (headshot) { //headshot variable is static to reach from other script.
anim.SetTrigger ("isDying");
speed = 0;
death = true;
}
if (bodyshot) { //bodyshot variable is static to reach from other script.
anim.SetTrigger ("isSore");
}
So, when I shoot an enemy, all enemies are dying at the same time. Because these scripts are attached to all enemies. I need to change bodyshot and headshot variables without using static. What can I do to separate them ?
When it comes to raycast, you only need the raycast script attached to an empty GameObject. It does not have to be attached to each individual Object that you want to perform the raycast on.
The only time you have to attach script to a GameObject you want to detect clicks on is when using the Unity EventSystem to detect clicks on the objects.
So, remove this script from other GameObjects and just attach it to one GameObject.
Note:
If you want to access or do something to the GameObject that the ray just hit, the GameObject is stored in the hit variable.
RaycastHit hit;...
Destroy(hit.collider.gameObject);
You can also access scripts attached to the Object hit:
hit.collider.gameObject.GetComponent<YourComponent>().doSomething();
Instead of checking to see what the tag of the collider is, you can first check to make sure the collider is one of the colliders attached to your object, and then check the tag for body location. For example:
public Collider[] coll;
void Start() {
coll = GetComponents<Collider>();
}
void FixedUpdate(){
if (fire) {
fire = false;
RaycastHit hit;
if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit, range)){
if (Enemy.distance < 80) {
if (hit.collider == coll[0] || hit.collider == coll[1]) {
if (hit.collider.tag == "head"){
or to simplify you can make coll[0] the head and [1] the body and ignore checking for a tag.
edit: As Programmer mentioned, this is not an efficient way to do this since you will be casting a ray for every object that has this script and you really only want to be casting a single ray.

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