I want to stop enemies from spawning when one of them hit the "finish" tag.
Here's the script which spawn enemies:
public class spawn : MonoBehaviour {
public GameObject enemy;
private float spawnpoint;
public float xlimit = 12f;
float spawnNewEnemyTimer = 1f;
void Start()
{
}
public void Update()
{
spawnNewEnemyTimer -= Time.deltaTime;
if (spawnNewEnemyTimer <= 0 )
{
spawnNewEnemyTimer = 3;
GameObject nemico = Instantiate(enemy);
}
}
Here's the script which make the enemies appear at random point and move:
public class nemici : MonoBehaviour {
float speed = 4f;
public GameObject enemy;
public float xlimit = 12f;
private float currentPosition;
public GameObject spawn;
bool endGame = false;
void Start()
{
if (endGame == false)
{
Vector3 newPosition = transform.position;
newPosition.x = Random.Range(-xlimit, xlimit);
transform.position = newPosition;
}
else if (endGame == true)
{
return;
}
}
void Update()
{
if (endGame == false)
{
//per farlo muovere verso il basso
Vector3 movimento = new Vector3(0f, -speed, 0f); //(x, y, z)
transform.position += movimento * Time.deltaTime;
}
else if (endGame == true)
{
return;
}
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
Destroy(enemy);
}
else if (other.tag == "Finish")
{
Debug.Log("hai perso!");
endGame = true;
}
}
}
What's wrong with this code? Thanks!
The logic that spawns new enemies is in the spawn class. The logic that decides whether you should stop spawning enemies, resides on the enemy class.
You are succesfully setting the endgame boolean to true whenever you want enemies to stop spawning when they hit an object with the finish tag.
void OnTriggerEnter(Collider other){
if (other.tag == "Finish"){
Debug.Log("hai perso!");
endGame = true;
}
}
Great! But you are not actually using it to stop spawns.
So far this variable is local to each enemy. So if you include checks in the start and update functions, they will only activate for the specific enemy that touched touched the finish object. This is not what you want.
You want the Spawn class to check the endgame variable, and if it is active, to stop spawning enemies.
To do this you will have to somehow be able to access the endgame variable from Spawn, What I recommend is that you make the endGame variable a member of te Spawn class. And that you include a reference to the Spawn in each enemy.
All together it might look something like this:
public class Spawn: MonoBehaviour{
public boolean endgame = false;
GameObject nemico = Instantiate(Enemy);
nemico.ParentSpawn = this
}
public class Enemy: MonoBehaviour{
public Spawn spawn;
void OnTriggerEnter(Collider other){
if (other.tag == "Finish"){
Spawn.endGame = true;
}
}
}
And then you can use the endgame boolean from within Spawn to check if you should keep spawning enemies.
Related
So I'm developing my first game in Unity currently (following a series by Mister Taft on YouTube called "Make a game like Zelda using Unity and C#" and just finished the 19th video).
I've currently got a "Log" enemy and am working on a knockback feature. I've followed the way that it is approached in the series is by creating a state machine that changes the Log's state to "stagger" for a couple of seconds, then return it to "idle" so it can change itself to "walk."
Whenever I hit the Log, however, it gets stuck in the stagger state and drifts without stopping until it hits a collider (note it doesn't change back to idle/walk even when it does hit the collider). If I can hit the Log correctly, sometimes, the player will actually also drift in the opposite direction.
Relevant code:
public class Log : Enemy {
private Rigidbody2D myRigidBody;
public Transform target;
public float chaseRadius;
public float attackRadius;
public Transform homePosition;
public Animator anim;
// Use this for initialization
void Start () {
currentState = EnemyState.idle;
myRigidBody = GetComponent<Rigidbody2D> ();
anim = GetComponent<Animator> ();
target = GameObject.FindWithTag ("Player").transform;
}
// FixedUpdate is called by physics.
void FixedUpdate () {
CheckDistance ();
}
//Log finds and walks towards Player.
void CheckDistance(){
if (Vector3.Distance (target.position, transform.position) <= chaseRadius && Vector3.Distance (target.position, transform.position) > attackRadius
&& currentState != EnemyState.stagger)
{
Vector3 temp = Vector3.MoveTowards (transform.position, target.position, moveSpeed * Time.deltaTime);
myRigidBody.MovePosition (temp);
ChangeState (EnemyState.walk);
}
}
private void ChangeState(EnemyState newState){
if (currentState != newState)
{
currentState = newState;
}
}
}
public class Knockback : MonoBehaviour {
public float thrust;
public float knockTime;
private void OnTriggerEnter2D(Collider2D other){
if (other.gameObject.CompareTag ("breakable"))
{
other.GetComponent<Pot>().Smash();
}
if(other.gameObject.CompareTag("enemy"))
{
Rigidbody2D enemy = other.GetComponent<Rigidbody2D>();
if (enemy != null)
{
enemy.GetComponent<Enemy> ().currentState = EnemyState.stagger;
Vector2 difference = enemy.transform.position - transform.position;
difference = difference.normalized * thrust;
enemy.AddForce (difference, ForceMode2D.Impulse);
StartCoroutine (KnockCo (enemy));
}
}
}
private IEnumerator KnockCo(Rigidbody2D enemy){
if (enemy != null)
{
yield return new WaitForSeconds (knockTime);
enemy.velocity = Vector2.zero;
enemy.GetComponent<Enemy>().currentState = EnemyState.idle;
}
}
}
public enum EnemyState{
idle,
walk,
attack,
stagger
}
public class Enemy : MonoBehaviour {
public EnemyState currentState;
public int enemyHealth;
public string enemyName;
public int baseAttack;
public float moveSpeed;
}
I've tried to add an else statement after the void CheckDistance in Log.cs, but that resulted in the Log having jittery movement and jumping back along its path. I'm at a loss. Any help would be greatly appreciated!
Well this is my Enemy Script:
public class Enemy : MonoBehaviour
{
public GameObject explosion;
public float speed;
public float shotDelay;
public GameObject enemyBomb;
public bool canShot;
public int pointValue = 30;
IEnumerator Start()
{
GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
if (canShot == false) yield break;
while (true)
{
for (int i = 0; i < transform.childCount; i++)
{
Instantiate(enemyBomb, transform.position, transform.rotation);
}
yield return new WaitForSeconds(shotDelay);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
FindObjectOfType<Score>().AddPoint(pointValue);
Instantiate(explosion, transform.position, transform.rotation);
Destroy(gameObject);
}
private void OnBecameInvisible()
{
Destroy(gameObject);
}
}
The problem is, that the enemy shoots before he is visible so there is no chance to fight him. Is there a way how to check if the Enemy is visible or not?
maybe you should try to add a trigger - so when the enemy touches the trigger that's set around the player vision something (let's say isvisible = true) should be set to true and every time the enemy wants to shoot it checks if (isvisible == true){//shoot}. And when the enemy goes out of the trigger OnTriggerExit2D(Collider2D collision) it sets isvisible = false
I hope it will work for you.
I'm making a game right now where the enemies become alerted to the player and chase them indefinetely until the player stand on a "safe plate" in game. When the player stands here the enemies should then return to their original position.
The issue is that whenever the player stands on the plate I get a null reference exception error and I can see the original guard position has been reset to 0,0,0 in the console. I'm assuming the reason for the null reference exception is because the world origin isn't on the nav mesh I'm using, although I could easily be wrong about that. I just can't seem to figure out why the vector3 value has changed at all since the guardPosition variable is initiated on Start and never touched again.
I've included both my enemyAi class (script attached to enemy) and my class associated with stepping on plates. If there's anything more needed to include let me know. If anyone has any ideas, help would be appreciated. Cheers.
Screenshot on console after stepping on plate
public class EnemyAI : MonoBehaviour
{
DeathHandler dh;
[SerializeField] Transform target;
[SerializeField] float chaseRange = 5f;
[SerializeField] float killRange = 2f;
[SerializeField] GameObject explosionEffect;
NavMeshAgent navMeshAgent;
float distanceToTarget = Mathf.Infinity;
bool isDead = false;
bool isAlert = false;
Vector3 guardPosition;
void Start()
{
GameObject gob;
gob = GameObject.Find("Player");
dh = gob.GetComponent<DeathHandler>();
guardPosition = transform.position;
navMeshAgent = GetComponent<NavMeshAgent>();
}
void Update()
{
distanceToTarget = Vector3.Distance(target.position, transform.position);
print(guardPosition + " guard position during update");
//alert a dude
if (distanceToTarget <= chaseRange && !isDead)
{
isAlert = true;
}
//chase a dude
if (isAlert == true)
{
ChasePlayer();
print(isAlert);
}
}
public void ChasePlayer()
{
navMeshAgent.SetDestination(target.position);
//explode a dude
if (distanceToTarget <= killRange)
{
Explode();
dh.KillPlayer();
}
}
public void SetAlertStatus(bool status)
{
isAlert = status;
}
public void ReturnToPost()
{
//isAlert = false;
print(guardPosition + " guard position after stepping on plate");
navMeshAgent.SetDestination(guardPosition);
}
void Explode()
{
Instantiate(explosionEffect, transform.position, transform.rotation);
isDead = true;
Destroy(gameObject);
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, chaseRange);
}
}
public class SafeSpots : MonoBehaviour
{
EnemyAI enemyAi;
void Start()
{
GameObject gob;
gob = GameObject.FindGameObjectWithTag("Enemy");
enemyAi = gob.GetComponent<EnemyAI>();
}
public void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player")
{
enemyAi.SetAlertStatus(false);
enemyAi.ReturnToPost();
}
}
}
You are getting a disabled instance of EnemyAI. Instead, use FindGameObjectsWithTag then iterate through all of them and add their EnemyAI to a list which you can iterate through when necessary. By the way, it's better to use CompareTag when possible to reduce garbage:
public class SafeSpots : MonoBehaviour
{
List<EnemyAI> enemyAis;
void Start()
{
GameObject[] enemies= GameObject.FindGameObjectsWithTag("Enemy");
enemyAis = new List<EnemyAI>();
foreach (GameObject enemy in enemies)
{
enemyAis.Add(enemy.GetComponent<EnemyAI>());
}
}
public void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
{
foreach(EnemyAI enemyAi in enemyAis)
{
enemyAi.SetAlertStatus(false);
enemyAi.ReturnToPost();
}
}
}
}
I have 2 script, playerMachanics and enemyBehavior. My enemyBehavior has a boolean that when the boolean is true it moves away from the player. Instead i'm getting the error: "object reference not set to an instance of an object".
I'm sure it means the script can't find the component but i can't quite figure out what's wrong.
public class enemyBehavior : MonoBehaviour
{
public bool evade = false;
public GameObject Player;
public float movementSpeed = 4;
// Start is called before the first frame update
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
// Update is called once per frame
void Update()
{
transform.LookAt(Player.transform);
transform.position += transform.forward * movementSpeed * Time.deltaTime;
if (evade == true)
{
movementSpeed = -4;
}
}
}
public class playerMechanics : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void OnCollisionEnter(Collision collision)
{
enemyBehavior evade = gameObject.GetComponent<enemyBehavior>();
if (collision.gameObject.name == "coin")
{
Destroy(collision.gameObject);
enemyBehavior script = GetComponent<enemyBehavior>();
script.evade = script.evade == true;
}
}
}
I expected that the movementSpeed would go to -4 but now i'm just getting an error.
Calling getComponent by itself will look for the component attached to the parent object of the script, which is the player in this case I think. So it will always return null.
Add
Public GameObject enemy;
to the playerMechanics class and then go into the designer and drag the game object that has the enemyBehavior script attached into it. There are several problems with the onCollisionEnter method. Something like this
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "coin")
{
Destroy(collision.gameObject);
enemyBehavior script = enemy.GetComponent<enemyBehavior>();
script.evade = false;
}
}
should get you going in the right direction.
Is the enemy behavior on the player object? See here
enemyBehavior evade = gameObject.GetComponent<enemyBehavior>();
and here
enemyBehavior script = GetComponent<enemyBehavior>();
You need to implement a way to track which enemy instance you are grabbing. Do this by making a variable to hold the enemy script, or by using a singleton on the enemy script (if there is one enemy).
Variable:
public enemyBehaviour enemy;
Singleton:
(enemyBehaviour)
public static enemyBehaviour instance = null;
private static readonly object padLock = new object();
void Awake(){
lock(padLock){
if(instance == null)
instance = this;
}
}
(player)
enemyBehaviour.instance.evade = false;
Look up singletons if you want to learn more.
If I'm right, I think your game mechanics works like this: The player's objective is to collect coins. When they collect one, the enemy near it will come and evade the player.
If that's the case, you should use this:
public class enemyBehavior : MonoBehaviour
{
public bool evade = false;
public GameObject Player;
public float movementSpeed = 4;
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
void Update()
{
transform.LookAt(Player.transform);
transform.position += transform.forward * movementSpeed * Time.deltaTime;
if (evade)
{
movementSpeed = -4;
}
}
}
public class playerMechanics : MonoBehaviour
{
[SerializeField] enemyBehvaior enemy;
void OnCollisionEnter(Collision collision)
{
if (collision.collider.name == "coin")
{
Destroy(collision.collider.gameObject);
enemy.evade = true;
}
}
}
In your code, you wrote 'collision.gameObject.' This refers to the object the script is attached to. If you want to reference to the object that we hit, use 'collision.collider'.
'[SerializeField]' is a unity attribute, which is used to make a field show up in the inspector without making it public.
Just a heads up, if you're using 2D, make sure the method is signatured 'OnCollisionEnter2D(Collision2D collision)'.
I hope I answered your question. :)
What is wrong with this code? When i fire the Rocket, it does not effect the character.
I have tried Looking other places etc but nothing really helps. I already got help so now at least the code actually runs.
using UnityEngine;
using System.Collections;
public class Rocket : MonoBehaviour
{
//Public changable things
public float speed = 20.0f;
public float life = 5.0f;
public float explosionForce = 1.0f;
public float explosionRadius = 1.0f;
public bool isGrounded;
public Rigidbody rb;
// Use this for initialization
void Start()
{
Invoke("Kill", life);
}
// Update is called once per frame
void Update()
{
transform.position += transform.forward * speed * Time.deltaTime;
if (isGrounded)
{
Kill();
}
}
void OnCollisionEnter(Collision other)
{
if (other.gameObject.tag == "Ground")
{
isGrounded = true;
}
}
//
void OnCollisionExit(Collision other)
{
if (other.gameObject.tag == "Ground")
{
isGrounded = false;
}
}
//Explosion code
void Kill()
{
Vector3 explosionCenterPosition = transform.position;
rb.AddExplosionForce(explosionForce, explosionCenterPosition, explosionRadius);
Destroy(gameObject);
}
}
I am making a game where you rocket jump like TF2. It should also move other rigidbodys like described here:
https://docs.unity3d.com/ScriptReference/Rigidbody.AddExplosionForce.html
I'm new to unity and have no clue why this won't work.
#bobshellby
you are destroying the rocket object in the same frame that you are applying the explosion force. so before you see the explosion effects the rocket will be destroyed.
Try using coroutine for the kill function, that way you can add wait time.
void Start()
{
//Invoke("Kill", life);
}
// Update is called once per frame
void Update()
{
transform.position += transform.forward * speed * Time.deltaTime;
if (isGrounded)
{
StartCoroutine( Kill());
}
}
void OnCollisionEnter(Collision other)
{
if (other.gameObject.tag == "Ground")
{
isGrounded = true;
}
}
//
void OnCollisionExit(Collision other)
{
if (other.gameObject.tag == "Ground")
{
isGrounded = false;
}
}
//Explosion code
IEnumerator Kill()
{
Vector3 explosionCenterPosition = transform.position;
rb.AddExplosionForce(explosionForce, explosionCenterPosition, explosionRadius);
yield return new WaitForSeconds(2f);
Destroy(gameObject);
}
The other thing is you are invoking the kill function after 5second in start itself which again destroy the rocket. Remove that, as you are already checking it in update if the rocket has hit the ground or not.