Run code on every frame two colliders are in contact - c#

im trying to make it so that if my character continues to touch a gameobject with a damage script the player continuesly gets damaged. instead of this result i only get damaged once when touching the gameobject. there are no error messages. ive tried to replace the if with a while loop and it ended up crashing my game. is there any way to loop a if statement preferably with a way to time it.
if (other.tag == "Player")
{
healthScript.healthPoints -= damage;
}
this is the if statement im trying to loop.

First, I am under the assumption you are using Colliders as triggers with the Is Trigger attribute selected. I am also assuming this is a 2D game. If not, this same approach will work, you will just have to change the methods from 2D to 3D.
You will want to add the OnTriggerEnter2D() and OnTriggerExit2D() methods to your player health script. These will allow us to detect when the player is standing on the damaging object. From here, we will start a coroutine which can be used to deal damage in a timed manner.
using System.Collections;
using UnityEngine;
public class HealthScript : MonoBehaviour
{
public float healthPoints = 100f;
public float damage = 5f;
public bool OnDamagingObject = false;
IEnumerator DealDamage()
{
while (OnDamagingObject)
{
healthPoints -= damage;
yield return new WaitForSeconds(1f);
}
}
void OnTriggerEnter2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = true;
StartCoroutine(DealDamage());
}
}
void OnTriggerExit2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = false;
}
}
}

For those who wish to use OnTriggerStay(), I will also provide a solution for that. This time, we will keep the script on the damaging object, following the structure the question asker is using.
using UnityEngine;
public class DamageScript : MonoBehaviour
{
public HealthScript healthScript;
public float damage = 5f;
void OnTriggerStay2D(Collider2D hitInfo)
{
GameObject other = hitInfo.gameObject;
if (other.CompareTag("Player"))
{
healthScript.healthPoints -= damage;
}
}
}
As mentioned in the comments of my other answer, implementing both OnTriggerEnter() and OnTriggerExit() allow more control over dealing damage to the player. One of the downsides to using OnTriggerStay() is I am unsure how to deal the damage in a timed manner. Additionally, OnTriggerStay() limits how you can deal damage to the player. If in the future you wanted to give 5 damage to the player when they first touch the damaging object but 2 damage for every second thereafter while touching it, it is not possible to do so using OnTriggerStay().

Related

C# (Galaga Style Project) - Limit Projectile Prefab Clones based on Hierarchy

I am trying to practice creating a clone of Galaga following the same ruleset as the original. I am currently stuck trying to attempt a limit on the amount of cloned prefabs that can be in the scene at any one time, in the same way that Galaga's projectiles are limited to 2 on screen at any time. I want to make it so the player can shoot up to two projectiles, which destroy after 2 seconds or when they collide (this part is functioning), followed by not being able to shoot if two projectile clones are active and not yet destroyed in the hierarchy (Not working as I can instantiate projectiles over the limit of 2).
I have combed through Google for about 3 hours with no solutions that have worked for me, at least in the ways that I had attempted to implement them.
Thank y'all so much for the help!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class playerController : MonoBehaviour
{
public float moveSpeed = 1.0f;
public playerProjectile projectile;
public Transform launchOffset;
public int maxBullets = 0;
private GameObject cloneProjectile;
public Rigidbody2D player;
// Start is called before the first frame update
void Start()
{
player = this.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
MovePlayer();
PlayerShoot();
}
public void MovePlayer()
{
player.velocity = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) * moveSpeed;
}
public void PlayerShoot()
{
if (Input.GetKeyDown(KeyCode.Z))
{
var cloneProjectile = Instantiate(projectile, launchOffset.position, launchOffset.rotation);
maxBullets++;
if (maxBullets >= 3)
{
Destroy(cloneProjectile, 0.1f);
maxBullets --;
return;
}
}
}
}
You could change the logic up a bit. An instance of the playerController class is active as long as the game is active, so it will know and retain the value of 'maxBullets' until you die or exit the program.
So instead, every time you click "z", the first thing you should do is run the check. If the current amount of live projectiles equals the maximum, have the logic 'return' and exit out of the method.

How to Make Enemy Attack When In Range of The Player? [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 1 year ago.
So my game is a 2D top down movement game and my script does make my enemy attack but it constantly loops the attack animation because I obviously don't know what EXACTLY to put code wise to make the enemy attack when in range of the player to do damage instead of letting him constantly loop. Also i seem to be getting an error when i get close to my enemy as of right now it says
NullReferenceException: Object reference not set to an instance of an object
EnemyCombat.Attack () (at Assets/EnemyCombat.cs:36)
EnemyCombat.Update () (at Assets/EnemyCombat.cs:25)
Also, Here is the EnemyCombat script
enemy attausing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyCombat : MonoBehaviour
{
public Animator animator;
public Transform AttackPoint;
public float attackRange = 0.5f;
public LayerMask enemyLayers;
public int attackDamage = 5;
public float attackRate = 2f;
float nextAttackTime = 0f;
// Update is called once per frame
void Update()
{
if (Time.time >= nextAttackTime)
{
Attack();
nextAttackTime = Time.time + 1f / attackRate;
}
}
void Attack()
{
animator.SetTrigger("Attack");
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, attackRange, enemyLayers);
foreach (Collider2D enemy in hitEnemies)
{
enemy.GetComponent<Enemy>().TakeDamage(attackDamage);
}
}
void OnDrawGizmosSelected()
{
if (AttackPoint == null)
return;
Gizmos.DrawWireSphere(AttackPoint.position, attackRange);
}
}
To fix your endless attack loop:
// Update is called once per frame
void Update()
{
if (attackRate >= nextAttackTime) /* checks that nextAttackTime is less than or equal to the attackRate */
{
Attack();
nextAttackTime = Time.deltaTime * 5f; // adds 5 seconds to nextAttackTime
}
else
{
nextAttackTime -= Time.deltaTime; /* if nextAttackTime is greater than the attackRate, subtract one from nextAttackTime. this only happens once per second because you use Time.deltaTime */
}
}
From that NullReference Error it looks like the major problem you're having is that there is no actual point in the code or in the game hierarchy that you are telling your script which gameObject it is referring to.
Line 36 is trying to retrieve that information with .GetComponent<Enemy()>, so you need to provide that reference.
You could do this in the script fairly easily by creating a public variable that you can drag the enemy gameObject into in your hierarchy in Unity.
Try something like:
public GameObject enemyObject;
This will create a variable in the script visible in the hierarchy when you select the script which you can drag the appropriate gameObject into.
There might need to be some adjustments to it because I can't see the rest of your code, but this seems to be the issue.
Another option would be trying to manually adding it in:
void Start()
{
GameObject enemyObject = gameObject.GetComponent<Enemy>();
}
this is taken from Unity Scripting Documentation

Why does this not stop my character from jumping unlimited times?

My character just jumps continuously even in the air, I'm not sure why the boolean does not stop it and I cannot figure it out. This is what I have so far:
using UnityEngine;
public class PlayerCollision : MonoBehaviour
{
public Rigidbody rb;
bool spacePressed = false;
float upForce = 200f;
void OnCollisionEnter(Collision collisionInfo)
{
if (collisionInfo.collider.tag == "Obstacle")
{
spacePressed = false;
}
}
void Update()
{
if (Input.GetKey("space") && spacePressed == false)
{
spacePressed = true;
rb.AddForce(0, upForce * Time.deltaTime, 0, ForceMode.VelocityChange);
}
}
}
My guess is that 'space pressed' is never or instantly set to false. So a first step on debuging it can be to watch the boolean. You can do that with 'public ...'. Then you should just observe when it's set to true/false.
There is also the possiblilty of this being a totally different kind of issue. For instance that one of the colliders could be of the wrong size (too big/small)
And to complete the holy trinity: There is a free 2D platformer example in the unity hub. I think they use a different approche by using an empty GO as a 'isGrounded' checker. One of the first things I learned as a hobbist is not to be ashamed to copy code of example projects.
Good luck.
It is likely that the collider is hitting Obstacle as it makes its first jump. OnCollisionEnter is very unreliable when checking if an object is beside/on another object. This can be checked if you use Debug.Log and check to see how much times it randomly activates OnCollisionEnter.
To reliably check if an object is beside another object use raycasts or boxcasts instead.
This is some code that should work that uses raycasts:
using UnityEngine;
public class PlayerCollision : MonoBehaviour
{
public Rigidbody rb;
public Collider collider;
public float extraCheckHeight = 0.1f;
float upForce = 200f;
bool isGrounded(){
Physics2D.Raycast(collider.bounds.center, Vector3.down, collider.bounds.extents.y + extraCheckHeight);
}
void Update()
{
if (Input.GetKey("space") && isGrounded)
{
rb.AddForce(0, upForce, 0, ForceMode.VelocityChange);//Time.deltaTime is should not be here because it should be the same force regardless of the time between frames
}
}
}
This should do the trick but I recommend researching about unity raycasts and boxcasts.
Here is a video on Ground Checking that I found useful when I was approached with a similar problem: Ground Check

Activating animation when within radius

I am trying to create a script for my enemy turret, but it is not going well. I have a couple animations of the turret being activated and deactivated. What I need is that based on the distance from the player, it plays either animation. So once it moves inside the detection radius it plays the activation animation and once it is outside it plays the deactivation animation. Most of the other ways I try require me to create an Animation Controller, which I have little experience in using. I want a simple way to play one animation once it is inside and play a different one when it is outside. I think there was a way to store the animation clip in the script, and then play it. I have attached my current script, so you know what I mean.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyTurret : MonoBehaviour
{
public GameObject Player;
public float DistanceToPlayer;
public float DetectionRadius = 75;
// Start is called before the first frame update
void Start()
{
Player = GameObject.FindGameObjectWithTag("PlayerTank");
}
// Update is called once per frame
void Update()
{
DistanceToPlayer = Vector3.Distance(transform.position, Player.transform.position);
if (DistanceToPlayer<=DetectionRadius)
{
Debug.Log("Within Radius");
}
if (DistanceToPlayer >= DetectionRadius)
{
Debug.Log("Outside Radius");
}
}
}
Calculating and checking the distance of the player for every update() is not ideal. It will work, but it will do more work than it needs to when the player isn't even near it. Its not efficient.
What you may want to do if your player is a Rigidbody, is add a SphereCollider to the turret, set isTrigger=true, set the radius to be your detection radius, and handle the OnTriggerEnter() and OnTriggerExit() events to play or stop animations.
You can also add two public Animiation objects to the script, drag and drop your animations in the editor, then you can use animation.Play() and .Stop() etc. to control the animations.
Something similar to this. Not tested fully, but you can get the idea.
public float detectionRadius = 75;
public Animation activateAnimation;
public Animation deactivateAnimation;
void Start()
{
SphereCollider detectionSphere = gameObject.AddComponent<SphereCollider>();
detectionSphere.isTrigger = true;
detectionSphere.radius = detectionRadius;
detectionSphere.center = Vector3.zero;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "PlayerTank")
{
activateAnimation.Play();
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "PlayerTank")
{
deactivateAnimation.Play();
}
}
Your animations must not loop otherwise you will have to add more logic to check if animation.isPlaying and do your own animation.Stop() etc.

OnCollisonEnter(Collision other) doesn`t detect my object?

I'm making a game in Unity VR in which I punch numbers and get points.
The gloves are responsible for detecting collisions between the numbers that they hit. On my gloves I have a PunchScript component and the numbers each have a rigidBody and collider.
The problem is that no collisions seem to ever occur. I placed a Debug.LogError inside of the collision detection code to assert this.
I tried switching on/off kinematics on all objects and used different collision systems to no avail.
Here's my PunchScript component:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PunchScript : MonoBehaviour
{
public SteamVR_TrackedObject hand;
private Rigidbody rBody;
private bool visible = true;
// Start is called before the first frame update
void Start()
{
rBody = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
rBody.MovePosition(hand.transform.position);
rBody.MoveRotation(hand.transform.rotation);
// print(rBody.velocity.magnitude* 1000);
}
void OnCollisonEnter(Collision other)
{
Rigidbody otherR = other.gameObject.GetComponentInChildren<Rigidbody>();
if (other.gameObject.name == "frpnchbg") {
Debug.LogError("Hit!");
}
if (other == null)
return;
Vector3 avgPoint = Vector3.zero;
foreach (ContactPoint p in other.contacts) {
avgPoint += p.point;
}
avgPoint /= other.contacts.Length;
Vector3 dir = (avgPoint - transform.position).normalized;
otherR.AddForceAtPosition(dir *50f* rBody.velocity.magnitude, avgPoint);
}
}
Here's how the glove object looks in the Unity inspector.
It is very important to write the name of Unity callback methods correctly, otherwise Unity will not be able to detect them on the object (and as a result, can never execute them).
In your case, you have misspelled the OnCollisionEnter callback.
Instead of OnCollisonEnter it should be OnCollisionEnter.

Categories