Coroutines and triggers - unity3d

My problem is that I'm making a game with unity, and what I want to do is when the enemy in my game hits the obstacle, it does x damage every one second.
There's a collider and health script on the "desk" obstacle, and a collider and script for movement and attacking on the enemy.
The enemy stops when colliding with the desk object, and does damage! but, the damage is continuous... I've tried coroutines and Invoke's but all the results are the same; 10 damage per trigger detection, not per second.
Here are the scripts:
Enemy movement and attack:
private Animator anim;
public GameObject Desk;
private DeskHealth deskHealth;
//private bool inQueue = false;
private bool collidingWithDesk;
public float timeAfterStart;
[Range(0,10)]
public float attackSpeedEnemy = 1f;
[Range(10,100)]
public float walkSpeed;
[SerializeField] float damageDealtToDesk = 10f;
void Start () {
anim = GetComponent<Animator>();
deskHealth = GetComponent<DeskHealth>();
collidingWithDesk = false;
}
void Update()
{
if (collidingWithDesk == false)
{
enemyIsWalking();
}
}
void enemyIsWalking()
{
transform.Translate(0, 0, (-walkSpeed * Time.deltaTime));
}
IEnumerator enemyIsAttacking()
{
var deskHealth = Desk.GetComponent<DeskHealth>();
deskHealth.DealDamageToDesk(damageDealtToDesk);
yield return new WaitForSeconds(5f);
}
void OnTriggerStay(Collider otherCollider)
{
if (otherCollider.tag == "Desk")
{
collidingWithDesk = true;
transform.Translate(0, 0, 0);
anim.Play("enemyHit");
StartCoroutine(enemyIsAttacking());
}
}
desk Health:
[SerializeField] float deskHealth;
Animator anim;
public void DealDamageToDesk(float deskDamage)
{
deskHealth -= deskDamage;
if (deskHealth <= 0)
{
print("am ded");
//anim.Play("deskDestroyed");
}
}

For what the doc says "OnTriggerStay is called once per physics update for every Collider other that is touching the trigger." Which mean that your code :
if (otherCollider.tag == "Desk")
{
collidingWithDesk = true;
transform.Translate(0, 0, 0);
anim.Play("enemyHit");
StartCoroutine(enemyIsAttacking());
}
will be called every update then pause for 5 seconds because of yield return new WaitForSeconds(5f); You should use OnColliderEnter or it might be even better to use OnTriggerEnter instead, because this one will be called once. Then change the value inside the WaitForSeconds call to 1 if you want it once per second.
Also as specified by Draco18s your yield wait for nothing.
You should do something like that :
IEnumerator enemyIsAttacking()
{
var deskHealth = Desk.GetComponent<DeskHealth>();
while(collidingWithDesk)
{
deskHealth.DealDamageToDesk(damageDealtToDesk);
yield return new WaitForSeconds(1f);
}
}
void OnTriggerExit(Collider otherCollider)
{
if (otherCollider.tag == "Desk")
{
collidingWithDesk = false;
// rest of your code
}
}

Related

How to get OnTriggerStay to work on every frame?

so I have this code :
public class ball_physics : MonoBehaviour
{
public Rigidbody ball;
public open2close claw;
public Vector3 offset;
Rigidbody rb;
private float forceMultiplier = 20;
private bool isShoot;
Vector3 start_position;
public path path;
void Start()
{
rb = GetComponent<Rigidbody>();
rb.Sleep();
start_position = transform.position;
}
void Update()
{
// *************
}
void Shoot(Vector3 Force)
{
if (isShoot)
{
print("isshot false");
return;
}
rb.AddForce(new Vector3(Force.x, Force.y,Force.z)* forceMultiplier);
isShoot = true;
path.Instance.HideLine();
}
private void OnTriggerStay(Collider ball)
{
if (isShoot)
{
return;
}
print("ontriggerstay");
if (claw.isClosed && claw.transform.gameObject.tag == "claw" )
{
print("claw");
//rb.Sleep();
transform.position = claw.rightClaw.transform.position + offset;
Vector3 forceInit = (start_position - transform.position);
path.Instance.UpdateTrajectory(forceInit * forceMultiplier, rb, transform.position);
}
}
private void OnTriggerExit(Collider ball)
{
if (claw.isClosed && claw.transform.gameObject.tag == "claw")
{
rb.WakeUp();
}
if (claw.isClosed == false)
{
Shoot(start_position - transform.position);
}
}
}
So on my other codes I have OnStayTrigger, which basically a clawhand grabs a ball and pulls. But this code is suppose to show a trajectory line and shoot. When I shoot the first time it works. But when I try again, The clawhand can grab the ball but it doesnt show the trajectory line nor shoots it. So im guessing this is the code that needs to be fixed. How can I make OnTriggerStay work all the time. I wouldnt want to change any code because it works perfectly. How can I just keep updating the OnTriggerstay so it can work?
At first glance it appears that You never set isShoot back to false, so it would never fire again because in OnTriggerStay you have it return if isShoot = true.

Why is my raycast not detecting the object?

Im currently on a project where i need to detect if an object is in front of an other, so if it's the case the object can't move, because one is in front of it, and if not the object can move.
So im using here a raycast, and if the ray hit something I turn a bool to true. And in my scene, the ray hits but never turning it to true.
I precise that both of my objects as 2D colliders and my raycast is a 2DRaycast, I previously add to tick "Queries Start in colliders" in physics 2D so my ray won't detect the object where I cast it.
Please save me.
Here is my code :
float rayLength = 1f;
private bool freeze = false;
private bool moving = false;
private bool behindSomeone = false;
Rigidbody2D rb2D;
public GameObject cara_sheet;
public GameObject Monster_view;
public GameObject monster;
void Start()
{
rb2D = GetComponent<Rigidbody2D>();
}
void Update()
{
Movement();
DetectQueuePos();
}
private void Movement()
{
if(freeze || behindSomeone)
{
return;
}
if(Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Move");
moving = true;
//transform.Translate(Vector3.up * Time.deltaTime, Space.World);
}
if(moving)
{
transform.Translate(Vector3.up * Time.deltaTime, Space.World);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (!collision.CompareTag("Bar"))
{
return;
}
StartCoroutine(StartFreeze());
}
public void DetectQueuePos()
{
RaycastHit2D hit = Physics2D.Raycast(this.transform.position, this.transform.position + this.transform.up * rayLength, 2f);
Debug.DrawLine(this.transform.position, this.transform.position + this.transform.up * rayLength, Color.red, 2f);
if (hit.collider != null)
{
print(hit.collider.name);
}
if(hit.collider != null)
{
Debug.Log("Behind true");
//Destroy(hit.transform.gameObject);
behindSomeone = true;
}
}
IEnumerator StartFreeze()
{
yield return new WaitForSeconds(1f);
rb2D.constraints = RigidbodyConstraints2D.FreezeAll;
freeze = true;
moving = false;
cara_sheet.SetActive(true);
Monster_view.SetActive(true);
}
While Debug.DrawLine expects a start position and an end position a Physics2D.Raycast expects a start position and a direction.
You are passing in what you copied from the DrawLine
this.transform.position + this.transform.up * rayLength
which is a position, not a direction (or at least it will a completely useless one)! Your debug line and your raycast might go in completely different directions!
In addition to that you let the line have the length rayLength but in your raycasts you pass in 2f.
It should rather be
Physics2D.Raycast(transform.position, transform.up, rayLength)

Unity - Destroying one duplicated enemy leads to can't attack anymore

I've created a "game" where you can attack enemies with a sword. All damages applies to only one enemy, even if i attack the other one. Also when I destroy the first I get this message
MissingReferenceException: The object of type 'RectTransform' has been destroyed but you are still trying to access it.
How do I fix it?
Here's my code:
Attached to the enemy
public class EnemyHealth : MonoBehaviour
{
private Transform healthBar;
private Transform canvas;
public float maxHealth = 100f;
public float damage = 10f;
float currentHealth;
float barLength;
private void Start()
{
currentHealth = maxHealth;
GameObject gfx = gameObject.transform.Find("GFX").gameObject;
canvas = gfx.transform.Find("Health Bar");
GameObject canv = canvas.gameObject;
healthBar = canv.transform.Find("Green Bar");
barLength = canvas.GetComponent<RectTransform>().rect.width * canvas.localScale.x;
}
public void GetStruck()
{
currentHealth -= damage;
if (currentHealth < 0) currentHealth = 0;
healthBar.localScale = new Vector3(currentHealth / maxHealth, 1f, 1f);
Vector3 p = healthBar.position;
p -= damage / (maxHealth * 2) * barLength * transform.right * -1;
healthBar.position = p;
if(currentHealth == 0)
{
Destroy(gameObject);
}
}
}
And the sword attack
using UnityEngine;
public class SwordAttack : MonoBehaviour
{
public Animator animator;
private EnemyHealth enemyHealth;
private bool isAttacking;
private void Start()
{
enemyHealth = GameObject.FindGameObjectWithTag("Enemy").GetComponent<EnemyHealth>();
}
void Update()
{
if(Input.GetMouseButtonDown(0))
{
isAttacking = true;
animator.Play("swordSwing", -1, 0f);
}
else if(animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1)
{
isAttacking = false;
}
animator.SetBool("isAttacking", isAttacking);
}
private void OnTriggerEnter(Collider other)
{
if(isAttacking && other.CompareTag("Enemy"))
{
enemyHealth.GetStruck();
}
}
}
private void Start()
{
enemyHealth = GameObject.FindGameObjectWithTag("Enemy").GetComponent<EnemyHealth>();
}
This is your mistake. You are only getting 1 enemy's health at your start method and you are always decreasing from that enemy's health. Instead you can get the hitted enemy's health in your OnTriggerEnter method and apply damage to that enemy.

Physics2D.OverlapBox shows inconsistent behaviour

Recently I started working with Unity, this is the first time I'm trying to build a 2d platformer.
For some reason, when I press the jump button, there is a random chance that it will actually make the player jump. Probably around 1 in 50 that it actually jumps.
I just can't figure out why its doing that. Do you know what I'm doing wrong here?
using UnityEngine;
public class Player : MonoBehaviour
{
public float movespeed = 5f;
public float jumpforce = 5f;
public Rigidbody2D player;
public LayerMask layerMaskPlatforms;
private float movementHorizontalInput;
private bool jumpInput;
private float lastTimeOnGroundInSeconds = 0f;
private float lastTimePressedJump = 0f;
void OnBecameInvisible()
{
// todo: restart game
}
void Update()
{
movementHorizontalInput = Input.GetAxisRaw("Horizontal");
jumpInput = Input.GetButtonDown("Jump");
}
void FixedUpdate()
{
if (jumpInput)
{
AttemptJump();
lastTimePressedJump = 0.2f;
}
else if (lastTimePressedJump > 0)
{
AttemptJump();
lastTimePressedJump -= Time.deltaTime;
}
if (IsOnGround())
{
lastTimeOnGroundInSeconds = 0.2f;
}
else if (lastTimeOnGroundInSeconds > 0)
{
lastTimeOnGroundInSeconds -= Time.deltaTime;
}
player.velocity = new Vector2(movementHorizontalInput * movespeed * Time.deltaTime * 50f, player.velocity.y);
}
private void AttemptJump()
{
if (lastTimeOnGroundInSeconds > 0)
{
player.AddForce(new Vector2(0, jumpforce), ForceMode2D.Impulse);
lastTimeOnGroundInSeconds = 0;
}
}
private bool IsOnGround()
{
Vector2 groundedCheckPosition = (Vector2)transform.position + new Vector2(0, -0.01f);
var overlapBox = Physics2D.OverlapBox(groundedCheckPosition, transform.localScale, 0, layerMaskPlatforms);
return overlapBox;
}
}
One major issue is that the jump input is being polled every frame (Update), but the jump code is done every several frames (FixedUpdate), so if you press jump, it is most likely the FixedUpdate method will never see that jump, explaining why it happens so rarely.
You'd need to save the jump state (maybe in a boolean), so by the time Fixed Update happens, it knows that that a jump occurred. Then set that jump to false, and do the jump logic.

How do you remove health of multiple enemies?

using System.Collections;
public class TowerAttack : MonoBehaviour {
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
GameObject player;
PlayerHealth playerHealth;
bool playerInRange;
float timer;
void Awake()
{
player = GameObject.FindGameObjectWithTag ("Player");
playerHealth = player.GetComponent<PlayerHealth> ();
}
void OnTriggerEnter(Collider other)
{
print (other);
if (other.gameObject == player)
playerInRange = true;
}
void OnTriggerExit(Collider other)
{
if (other.gameObject == player)
playerInRange = false;
}
// Update is called once per frame
void Update () {
timer += Time.deltaTime;
if (timer >= timeBetweenAttacks && playerInRange)
Attack ();
if (playerHealth.currentHealth <= 0)
print ("Player's dead");
}
void Attack()
{
timer = 0f;
print (playerHealth);
if (playerHealth.currentHealth > 0)
playerHealth.TakeDamage (attackDamage);
}
}
I am using this code on an object called tower and it has box collider with istrigger active. There are multiple objects spawn on the field with "PlayerHealth" script attached and tagged as "Player". However only the first "Player" which enter the box collider get its health remove and the rest of the "Player" objects stay healthy.
There are multiple objects spawn on the field with "PlayerHealth"
script attached and tagged as "Player". However only the first
"Player" which enter the box collider get its health remove and the
rest of the "Player" objects stay healthy.
The playerHealth = player.GetComponent<PlayerHealth> (); line of code will only get the PlayerHealth from one object only.
To remove health from multiple enemies, you need to perform other.GetComponent<PlayerHealth> () each time OnTriggerEnter is called.
void OnTriggerEnter(Collider other)
{
print(other);
//Remove health only if the Object is tagged as Player
if (other.CompareTag("Player"))
{
other.GetComponent<PlayerHealth>().TakeDamage(attackDamage);
playerInRange = true;
}
}
player = GameObject.FindGameObjectWithTag ("Player");
This is for setting one player. If there are multiple player, one of players is set to the variable. playerHealth = player.GetComponent<PlayerHealth> (); is also only the player. You should do like below.
using System.Collections;
public class TowerAttack : MonoBehaviour
{
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
GameObject[] players;
bool playerInRange;
float timer;
void Awake()
{
players = GameObject.FindGameObjectsWithTag ("Player");
// FindGameObjectWithTag => FindGameObjectsWithTag
}
void OnTriggerEnter(Collider other)
{
print (other);
if (other.tag == "Player")
playerInRange = true;
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
playerInRange = false;
}
void Update ()
{
timer += Time.deltaTime;
if (timer >= timeBetweenAttacks && playerInRange)
Attack ();
for (int i = 0; i < players.Length; i++)
{
if (players [i].GetComponent<PlayerHealth> ().currentHealth <= 0)
print ("Player's dead")
}
}
void Attack()
{
timer = 0f;
print (playerHealth);
if (playerHealth.currentHealth > 0)
playerHealth.TakeDamage (attackDamage);
}
}
If you want to make the TowerAttack Attack() all players in range at the same time, you can apply the code above. But, if you want to attack players one after another, move the function in Update() to OnTrigger* methods. Then you may need this method OnTriggerStay.