Why is my function runs twice in OnTriggerStay? - c#

I have a simple game in Unity.
The situation is like if I kill an enemy, it drops a pickupable Health.
In the OnTriggerEnter, I handle the player collision with the health, and if the player misses health, it heals a certain amount. (70 exactly)
When player is standing on the health while at maximum health, it does nothing. But if player got damaged while standing on the health, the health should also be picked up, that is where OnTriggerStay comes in.
There I constantly check if the player needs heeling, if do, do the healing, and destroy the heal object.
My problem is that it runs twice. It heals the amount twice to the player. No matter what I do, the function I call in OnTriggerStay is gonna run twice. Why is that? Anybody knows the solution?
Here is the part of my Player.cs file, and my Heal.cs file:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Player : MonoBehaviour
{
//HP and MANA system
[SerializeField]
private int playerHealth;
[SerializeField]
private int playerMaxHealth = 100;
[SerializeField]
private Transform Healthbar;
private void OnTriggerStay(Collider other)
{
if (other.tag == "Enemy")
{
timer += Time.deltaTime;
if (timer > attackSpeed)
{
Enemy enemy = other.GetComponent<Enemy>();
if (enemy != null)
{
enemy.DamageEnemy(playerDamage);
}
timer = 0.0f;
}
}
if (other.tag == "PatrollingEnemy")
{
timer += Time.deltaTime;
if (timer > attackSpeed)
{
PatrollingEnemy enemy = other.GetComponent<PatrollingEnemy>();
if (enemy != null)
{
enemy.DamageEnemy(playerDamage);
}
timer = 0.0f;
}
}
if (other.tag == "Heal")
{
heal(other, false);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Heal")
{
heal(other, false);
}
else if (other.tag == "HealthPotion")
{
heal(other, true);
}
}
private void heal(Collider other, bool isPotion)
{
dynamic heal;
if (isPotion)
{
heal = other.GetComponent<HealthPotion>();
}
else
{
heal = other.GetComponent<Heal>();
}
if (playerHealth < playerMaxHealth && heal != null)
{
addHealthToPlayer(heal.HealAmount);
Destroy(other.gameObject);
}
}
private void addHealthToPlayer(int amount)
{
playerHealth += amount;
if (playerHealth > playerMaxHealth)
{
playerHealth = playerMaxHealth;
}
rescaleHealthBar();
}
private void rescaleHealthBar()
{
Healthbar.transform.localScale = new Vector3((float)playerHealth / (float)playerMaxHealth, 1.0f, 1.0f);
}
}
public class Heal : MonoBehaviour
{
float timer = 0.0f;
float destroyTimer = 0.0f;
float timeWhenDestroy = 15f;
Vector3 rotation = new Vector3(0, 45, 0);
private int healAmount = 70;
public int HealAmount
{
get
{
return healAmount;
}
}
void Update()
{
timer += Time.deltaTime;
destroyTimer += Time.deltaTime;
if (destroyTimer > timeWhenDestroy)
{
Destroy(this.gameObject);
}
transform.Rotate(rotation * Time.deltaTime);
if (timer < 1.0f)
{
transform.Translate(Vector3.up * Time.deltaTime);
}
if (timer > 1.0f)
{
transform.Translate(-Vector3.up * Time.deltaTime);
}
if (timer > 2.0f)
{
timer = 0.0f;
}
}
}

https://docs.unity3d.com/ScriptReference/Object.Destroy.html
Maybe both your events (Enter & Stay) are fired during that frame ?
https://docs.unity3d.com/ScriptReference/Collider.OnTriggerStay.html
OnTriggerStay is called almost all the frames for every Collider other that is touching the trigger. The function is on the physics timer so it won't necessarily run every frame.

Related

How can I make it so when my player collides with an enemy, it disables movement input until player touches the ground?

So, I want to make it so the player loses control when it collides with an enemy. I already got a thing set up to make the player fly off towards the direction of the collision, but I can't seem to be able to make them lose control. I want input to not register.
Here's my player controller script:
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public Rigidbody2D _rigidBody2d;
Animator animatorController;
public float moveSpeed;
public float jumpForce = 1f;
float maxJumpHeight;
bool isOnGround = false;
bool collidedWithEnemy = false;
private void Start()
{
_rigidBody2d = GetComponent<Rigidbody2D>();
animatorController = GetComponent<Animator>();
}
float horizontalInput;
private void Update()
{
if (collidedWithEnemy == false)
{
horizontalInput = Input.GetAxis("Horizontal");
}
// Player movement
MovementMechanics();
// Sprite Flipping
SpriteFlipping();
// Jumping
JumpingMechanics();
// Set Falling Animation
FallingMechanics();
}
void MovementMechanics()
{
if (horizontalInput != 0)
{
transform.Translate(new Vector2(1, 0) * horizontalInput * Time.deltaTime * moveSpeed);
animatorController.SetBool("isMoving", true);
}
else
{
animatorController.SetBool("isMoving", false);
}
}
void SpriteFlipping()
{
if (horizontalInput > 0)
{
transform.localScale = new Vector3(-1, 1, 1);
}
else if (horizontalInput < 0)
{
transform.localScale = new Vector3(1, 1, 1);
}
}
void JumpingMechanics()
{
if (Input.GetKeyDown(KeyCode.Space) && isOnGround)
{
_rigidBody2d.AddForce(new Vector2(0, jumpForce), ForceMode2D.Impulse);
isOnGround = false;
animatorController.SetBool("isMoving", false);
animatorController.SetBool("isJumping", true);
animatorController.SetBool("isFalling", false);
}
}
void FallingMechanics()
{
if (_rigidBody2d.velocity.y < 0f)
{
animatorController.SetBool("isFalling", true);
}
else if (_rigidBody2d.velocity.y > 0f)
{
animatorController.SetBool("isFalling", false);
animatorController.SetBool("isJumping", true);
}
}
void OnCollisionEnter2D(Collision2D other)
{
// Avoids Jump Spawn
if (other.gameObject.CompareTag("Ground"))
{
isOnGround = true;
collidedWithEnemy = false;
animatorController.SetBool("isJumping", false);
animatorController.SetBool("isMoving", false);
animatorController.SetBool("isFalling", false);
}
// On Collision With Enemy
if (other.gameObject.CompareTag("Enemy"))
{
collidedWithEnemy = true;
}
}
}
And here's the enemy script:
using UnityEngine;
public class EnemyController : MonoBehaviour
{
Vector2 otherTransform;
Rigidbody2D playerRigidBody2d;
public float impulseForce = 100f;
private void Start()
{
playerRigidBody2d = GameObject.Find("Player").GetComponent<Rigidbody2D>();
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
otherTransform = other.gameObject.transform.position;
playerRigidBody2d.AddForce((otherTransform * 1) * impulseForce, ForceMode2D.Impulse);
other.transform.Translate(Vector3.zero);
}
}
}
And here's a little video of whats happening:
https://vimeo.com/709461296
As you can see in the console, the game still registers horizontal input after collision with enemy
(Don't mind the player launching off at lightspeed, thats not my concern here, I can fix that easily)
I'd say the problem is that you don't set horizontalInput to zero if the collision happens. The way you wrote your Update method, it takes the previously assigned value of horizontalInput and processes it.
Add the condition of not colliding with the enemy in MovementMechanics():
if (horizontalInput != 0 && !collidedWithEnemy)
{
// do something..
}

Unity Mirror, Client is not executing damage Command

I have a script on the Player that gets its target by clicking on them. I check if the players auto attack cooldown is 0 and if the player is in range. After that it should run a Command and damage the enemy mob.
This only happens as it is intended on the host and not on client.
And if I remove the enemy != null check in the CmdDamage function the client just disconnects.
public class PlayerAttacker : NetworkBehaviour
public EnemyScript enemy;
public float timer = 0;
public float timerMax;
private void Start()
{
timer = timerMax;
}
private void Update()
{
if (!isLocalPlayer)
return;
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2d = new Vector2(mousePos.x, mousePos.y);
RaycastHit2D hit = Physics2D.Raycast(mousePos2d, Vector2.zero);
if(hit.collider != null && hit.collider.GetComponent<EnemyScript>() != null)
{
enemy = hit.collider.GetComponent<EnemyScript>();
enemy.target.SetActive(true);
}
}
if (timer <= 0)
{
timer = 0;
BasicAttack();
}
else if(timer > 0)
{
timer -= Time.deltaTime;
}
}
private void BasicAttack()
{
float dist = Vector3.Distance(enemy.transform.position, transform.position);
if(dist < 2.5f)
{
GetComponent<NetworkAnimator>().SetTrigger(Animator.StringToHash("sword slash")); ///SEND TRIGGERS OVER NETWORK
CmdDamage();
timer = timerMax;
}
}
[Command]
private void CmdDamage()
{
if(enemy != null)
enemy.TakeDamage(5);
}
public class EnemyScript : NetworkBehaviour
[SyncVar(hook = "OnHealthChanged")] public float currentHealth; //ADD HP BARS INGAME
public float maxHealth;
[SerializeField] public HealthBar healthBar;
public override void OnStartServer()
{
currentHealth = maxHealth;
}
public void TakeDamage(float amount)
{
if (!isServer)
return;
currentHealth -= amount;
}
Where is your OnHealthChanged -hook implemented? With SyncVars that are hooked, you have to assign changed variable inside hook method. In your case, you have to assign a new value for currentHealth inside OnHealthChanged.
Now your currentHealth is updated only on server.

How to change default attack direction for player?

I coded my player in my platform game so that he would attack with a sword when you press space. It attacks right when I run right. It attacks left when I run left. But when I stand still by default it attacks left. How do I make it attack right instead?
Below is all the code in my player controller script, and also an image of my blend tree.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public int moveSpeed;
private Animator anim;
public int playerJumpPower = 1250;
private float moveX;
public bool isGrounded;
public float fJumpWaitTime = 0.2f;
private float fJumpWait;
private object col;
private bool attacking;
public float attackTime;
private float attackTimeCounter;
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (!attacking)
{
if (Input.GetButtonDown("Jump"))
{
Jump();
}
if (Input.GetAxisRaw("Horizontal") > 0.5f || Input.GetAxisRaw("Horizontal") < -0.5f)
{
transform.Translate(new Vector3(Input.GetAxisRaw("Horizontal") * moveSpeed * Time.deltaTime, 0f, 0f));
}
void Jump()
{
//Jumping Code
GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
isGrounded = false;
}
void OnCollisionEnter2D(Collision2D col)
{
Debug.Log("Player has collided with " + col.collider.name);
if (col.gameObject.tag == "ground")
{
isGrounded = true;
}
}
}
anim.SetFloat("MoveX", Input.GetAxisRaw("Horizontal"));
if (Input.GetKeyDown(KeyCode.Space))
{
attackTimeCounter = attackTime;
attacking = true;
anim.SetBool("Attack", true);
}
if(attackTimeCounter > 0)
{
attackTimeCounter -= Time.deltaTime;
}
if(attackTimeCounter <= 0)
{
attacking = false;
anim.SetBool("Attack", false);
}
}
}
After having another look at your Blend Tree, I would check if your Threst is the problem.
What you have right now:
PlayerAttackLeft 0 -1
PlayerAttackRight 1 1
What you should probably have:
PlayerAttackLeft -0.01 -1
PlayerAttackRight 0 1

Pause collider in Unity

I would like that collider is paused for few seconds at the beginning of the game. I'm using this script on object. Thanks.
public class PickUpObject : MonoBehaviour {
void OnCollisionEnter (Collision Col)
{
if (Col.gameObject.name== "Player")
{
Debug.Log("collision detected");
Destroy(gameObject);
}
}
}
Use a timer to check it.
public class PickUpObject : MonoBehaviour {
public float timer = 10f; //seconds
private void Update()
{
timer -= Time.deltaTime;
}
void OnCollisionEnter (Collision Col)
{
if (Col.gameObject.name== "Player" && timer <= 0f)
{
Debug.Log("collision detected");
Destroy(gameObject);
}
}
}
My prefered method would be to set a _startTime variable with the time the scene started and then use that to check against when needed rather than incremementing a value each frame in a Update method.
private float _delay = 2f;
private float _startTime;
private void Start()
{
_startTime = Time.time;
}
void OnCollisionEnter (Collision col)
{
if(Time.time - _startTime < _delay)
{
//Exit if the time passed is less than the _delay
return;
}
//Else run check against player
if (col.gameObject.name == "Player")
{
Debug.Log("collision detected");
Destroy(gameObject);
}
}
public class PickUpObject : MonoBehaviour {
public float delayTime = 2.0f;
public float currentTime = 0.0f;
private void Update()
{
currentTime += Time.deltaTime;
}
void OnCollisionEnter (Collision Col)
{
if (Col.gameObject.name== "Player" && currentTime > delayTime)
{
delayTime += currentTime;
Destroy(gameObject);
delayTime -= currentTime;
currentTime = 0.0f;
}
}
}
You can use https://docs.unity3d.com/ScriptReference/Time-realtimeSinceStartup.html
and check if it's bigger than 5 :D

Object is being spammed when it comes to my timeBetweenShot variable, can anyone take a look?

I Have created a 2d stealth game where the enemy fires on the player, only problem is that although the bullets are created and deleted fine on another script, the script itself spams the program with bullets every frame, creating the unwanted result
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HurtPlayer : MonoBehaviour
{
public float timeToShoot;
private float timeToShootCounter;
private bool shot;
private Vector3 moveDirection;
public float timeBetweenShot;
public float timeBetweenShotCounter;
public Transform firePoint;
public GameObject Bullet;
// Use this for initialization
void Start()
{
shot = false;
timeToShootCounter = timeToShoot;
}
// Update is called once per frame
void Update()
{
while (shot == true)
{
StartCoroutine(Delay());
Destroy(GameObject.Find("Bullet"));
timeBetweenShot -= Time.deltaTime;
timeToShoot -= Time.deltaTime;
}
}
IEnumerator Delay()
{
yield return new WaitForSeconds(0.5f);
}
void OnTriggerStay2D(Collider2D other)
{
if (other.gameObject.tag == "player")
{
if (shot == false)
{
if (timeToShoot >= 0f)
{
shot = true;
if (shot == true)
{
shot = false;
Instantiate(Bullet, firePoint.position, firePoint.rotation);
Delay();
if (timeBetweenShot <= 0f)
{
shot = false;
timeToShoot = timeToShootCounter;
timeBetweenShot = timeBetweenShotCounter;
}
}
}
}
}
}
}
What I want is the time betweenshot to work and for the enemy to only shoot once every one or half a second, thanks.
Is this what you are looking for?
IEnumerator ContinuousShoot()
{
// Continuously spawn bullets until this coroutine is stopped
// when the player exits the trigger.
while (true)
{
yield return new WaitForSeconds(1f); // Pause for 1 second.
Instantiate(Bullet, firePoint.position, firePoint.rotation);
}
}
void OnTriggerEnter2D(Collider2D other)
{
// Player enters trigger
if (other.gameObject.CompareTag("player"))
{
StartCoroutine(ContinuousShoot());
}
}
void OnTriggerExit2D(Collider2D other)
{
// Player exits trigger
if (other.gameObject.CompareTag("player"))
{
StopCoroutine(ContinuousShoot());
}
}

Categories