How to tweak this melee script to be more responsive? - c#

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

Related

how to destroy a specific object when touch it

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)

How to cast a ray from object that has got hit by other raycast? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I'm trying to implement laser beam reflection behavior. I have a laser object named "BeamPoint" with RaySenderScript attached that simply casts a ray upwards.
RaySenderScript
public class RaySenderScript : MonoBehaviour
{
void FixedUpdate()
{
if (gameObject.name == "BeamPoint" ||
gameObject.GetComponent<RayReceiverScript>().isHitByRay)
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, transform.up);
Debug.DrawLine(transform.position, hit.point);
if (hit.collider.tag == "Effector")
{
RayReceiverScript rayReceiver =
hit.collider.gameObject.GetComponent<RayReceiverScript>();
if (rayReceiver != null)
{
rayReceiver.HitWithRay();
}
}
}
}
}
Another object is "Mirror" that has to reflect a laser beam if gets hit by it. Mirror object has attached both RaySenderScript and RayReceiverScript.
RayReceiverScript
public class RayReceiverScript : MonoBehaviour
{
const float hitByRayRefreshTime = .01f;
private float rayRunsOutTime;
public bool isHitByRay = false;
void Start()
{
rayRunsOutTime = Time.time;
}
void FixedUpdate()
{
if(rayRunsOutTime < Time.time)
{
isHitByRay = false;
}
}
public void HitWithRay()
{
isHitByRay = true;
rayRunsOutTime = Time.time + hitByRayRefreshTime;
}
}
Also whole area is surrounded by a wall (tagged wall) with collider - ray always will hit something. So, in my case, when the ray from "BeamPoint" hits the "Mirror" (with tag effector) the mirror does not cast a ray at all. And also isHitByRay variable doesn't turn into false after ray stops casting on mirror - I think it goes in cycles. It is the main question: why doesn't the mirror cast a ray when is getting hit by other ray, and when not - why doesn't the isHitByRay get false?
There are a lot of small issue points in your code:
As I understood every of your objects has both scripts. So first of all I would remove the repeated GetComponent call for the objects own RayReceiver
Also add a check since it is possible that rayReceiver is simply null and therefore your method HitWithRay just never called. At least I would add a warning here.
Then using Physics2D.Raycast it is pretty sure that the first hit collider is actually the one of your sender object itself! So you should rather use Physics2D.RaycastNonAlloc, filter the result and then use the first hit that is not the object itself.
There is also no warranty in which order both FixedUpdate methods will be called. I would rather do it event based and enable a Coroutine. This saves resources while there is no hit anyway.
Also note that your hitByRayRefreshTime of 0.01f is very small and probably even smaller then the rate of FixedUpdate itself (usually 0.02f). The original post you linked rather used 1f as value here. So your rays were simply immediately turned off all the time.
Currently those are also not really "mirrors". If you really want to have a mirrored ray I would additionally pass in the exact hit position and direction and use it later for sending the new ray
So I would change your classes to something like:
public class RaySender : MonoBehaviour
{
// would be even better if you reference this via the Inspector
[SerializeField] private RayReceiver myReceiver;
// enable this for the initial sender
// (using a simple bool is more efficient than string comparing the name)
[SerializeField] private bool alwaysSends;
// store the last hit object!
private Transform _lastHit;
private RayReceiver _currentHitReceiver;
private void Start()
{
// Get your own component ONCE
if (!myReceiver) myReceiver = GetComponent<RayReceiver>();
}
private void FixedUpdate()
{
// if not always on or receiver not hit do nothing
if (!alwaysSends && !myReceiver.isHitByRay) return;
// except fot the first sender use hitpoint and mirrored direction
var laserStartPoint = alwaysSends ? transform.position : transform.TransformPoint(myReceiver.incomingHitPoint);
var laserDirection = alwaysSends ? (Vector2)transform.up : Vector2.Reflect(transform.TransformDirection(myReceiver.incomingHitDirection), transform.up);
// give space for 2 hits in case you hit yourself
var hits = new RaycastHit2D[2];
Physics2D.RaycastNonAlloc(laserStartPoint, laserDirection, hits);
// get the first one that is not yourself
var currentHit = hits.FirstOrDefault(h => h.transform != transform);
if (!currentHit.collider || !currentHit.collider.CompareTag("Effector")) return;
// something was hit
Debug.DrawLine(laserStartPoint, currentHit.point);
if (_lastHit != currentHit.transform)
{
// update currentHitReceiver
_currentHitReceiver = currentHit.collider.GetComponent<RayReceiver>(); ;
_lastHit = currentHit.transform;
}
if (_currentHitReceiver)
{
_currentHitReceiver.HitWithRay(currentHit.point, currentHit.point - (alwaysSends ? (Vector2)transform.position : myReceiver.incomingHitPoint));
}
else
{
// ouch! Hit somthing but RayReceiver not found here
Debug.LogWarning("Ouch! Something without a RayReceiver was hit ...", this");
}
}
}
and
public class RayReceiver : MonoBehaviour
{
[Header("Settings")]
public float turnOffDelay = 0.5f;
[Header("Debug")]
public bool isHitByRay;
public Vector2 incomingHitDirection;
public Vector2 incomingHitPoint;
private void Start()
{
isHitByRay = false;
}
public void HitWithRay(Vector2 hitPoint, Vector2 hitDirection)
{
// stop evtl. running routines so we don't get concurrent routines
StopAllCoroutines();
isHitByRay = true;
// get point and direction relative to this object
incomingHitPoint = transform.InverseTransformPoint(hitPoint);
incomingHitDirection = transform.InverseTransformDirection(hitDirection);
// start the turn off routine
StartCoroutine(TurnOffAfterDelay());
}
// after the given delay turn off the raycast
private IEnumerator TurnOffAfterDelay()
{
yield return new WaitForSeconds(turnOffDelay);
isHitByRay = false;
}
}

How can I make the coroutine to start and keep working while the others are already in action?

The script is attached to 3 cubes.
Each cube with another tag.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class raytest : MonoBehaviour
{
public float duration;
public string tag;
private Vector3 originalpos;
private void Start()
{
originalpos = transform.position;
}
private void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100))
{
if (hit.transform.tag == tag)
{
if (transform.position.z != originalpos.z - 1)
StartCoroutine(moveToX(transform, new Vector3(transform.position.x, transform.position.y, transform.position.z - 1), duration));
}
else
{
StartCoroutine(moveToX(transform, originalpos, duration));
}
}
else
{
//reset
StartCoroutine(moveToX(transform, originalpos, duration));
}
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
}
When the mouse is over a gameobject and shoot a ray the object start moving.
When the ray is not hitting the object the object is moving back to it's original place.
But sometimes when I move the mouse over two or even three objects quick the next object is not moving until the first one finished moving.
Sometimes the objects are moving at the same time the first one move forward while the rest still moving back to original position.
I'm not sure why sometimes when hitting another object it's waiting first for the other to back to it's original position and only then start moving the hitting one ? And not moving them at the same time one forward and one back.
The idea is that if I hit a object and start moving forward once I'm hitting another object the first one should start moving back and the one that is hitting should start moving forward parallelly.
Sorry if I don't understand the question properly, but this is what I gather:
If the raycast is hitting an object then its moving one way, if the raycast is not hitting an object then its moving back to its original place.
If this is all you need - aren't coroutines over complicating the issue? For example, you could have a CheckIfRaycast.cs script attached to each of your boxes. Inside that scripts Update()method you could check if its being hit with the raycast or not, then do your desired movement.
Multiple coroutines can cause some strange behaviour, so make sure you stop them with StopCoroutine(coroutine name); or StopAllCoroutines();.
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopAllCoroutines.html
you should identify you coroutines that way:
you have to use different coroutines on different objects
Coroutine c1;
Coroutine c2;
void runCourotines()
{
c1 = StartCoroutine(MoveToX());
c2 = StartCoroutine(MoveToX());
}
void StopCoroutines()
{
StopCoroutine(c1);
}

Unity - Laser activated objects using raycast

I am trying to recreate a simple laser puzzle mechanic like seen in The Talos Principle - where i have a laser emitter that i can move and rotate, and when the beam (raycast and LineRenderer) hits a specific object, that object will become "active". However when the object is no longer being hit by a laser it should "deactivate".
I am having troubles with the Deactivate part. Is there a way to tell the object that it is no longer being hit by a raycast, or add a collider to the LineRenderer maybe? Or some third way to tell the object that it is no longer being hit by any lasers.
When your target is hit by a raycast, you could use the RaycastHit reference to acquire a script and update the cooldown.
Lets say we have RaySender and RayReceiver.
RaySenderScript
public class RaySenderScript{
RaycastHit Hit;
void FixedUpdate(){
//SendRaycast and store in 'Hit'.
if (Hit.collider != null)
{ //If raycast hit a collider, attempt to acquire its receiver script.
RayReceiverScript = Hit.collider.gameObject.GetComponent<RayReceiverScript>();
if (RayReceiverScript != null)
{ //if receiver script acquired, hit it.
RayReceiverScript.HitWithRay();
}
}
}
}
RayReceiverScript
public class RayReceiverScript{
public float HitByRayRefreshTime = 1f;
float RayRunsOutTime;
public bool IsHitByRay = false;
void Start()
{ //Initialize run out time.
RayRunsOut = Time.time;
}
void Update()
{
if (Time.time > RayRunsOutTime)
{ //check if time run out, if it has, no longer being hit by ray.
IsHitByRay = false;
}
}
public void HitWithRay(){ //method activated by ray sender when hitting this target.
IsHitByRay = true;
RayRunsOutTime = Time.time + HitByRayRefreshTime;
}
}
Sender strikes Receiver with a ray.
Sender has a reference to Receiver, it uses GetComponent() to access it. It can then say receiverScript.HitWithRay();
Receiver keeps checking if it no longer is receiving, if it isnt, it stops being hit by ray.
Add all objects hit by laser to collection and check if current target is in this collection. If it is not there, then it is "deactivated".

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.

Categories