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();
}
}
}
}
Related
Alright so I have a collision script which works as in if I collide a gameobject with another object with the script attached it does register a collision.
The collision script I am using:
private void OnCollisionEnter(Collision collision)
{
Debug.Log("collision registered");
}
However when I try to collided my instantiated prefabs with my object with my script attached a collision does not get registered.
Does anyone know why this is? I think it's to do with with the prefabs being a "Moveable" but I can't quite get to the bottom of the problem.
Script attached to the prefabs:
public class Moveable : MonoBehaviour
{
public float _speedMetersPerSecond = 50f;
public float resistance = 10f;
public float current = 0f;
public List<Moveable> moveables = new List<Moveable>();
private Vector3? _destination;
private Vector3 _startPosition;
private float _totalLerpDuration;
private float _elapsedLerpDuration;
private Action _onCompleteCallback;
public GameObject Electron;
public Transform Lightbulb;
public SliderChange SliderScript;
public VMT2Counter script2;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// THE LIGHTBULB PART IS FOR THE SPAWN POINT SO DO NOT DELETE
Moveable NextOnPath = Instantiate(Electron, Lightbulb.position, Quaternion.identity).GetComponent<Moveable>();
moveables.Add(NextOnPath.GetComponent<Moveable>());
MoverController.instance.targets.Add(NextOnPath.GetComponent<Moveable>());
}
if (_destination.HasValue == false)
return;
if (_elapsedLerpDuration >= _totalLerpDuration && _totalLerpDuration > 0)
return;
_elapsedLerpDuration += Time.deltaTime;
float percent = (_elapsedLerpDuration / _totalLerpDuration);
transform.position = Vector3.Lerp(_startPosition, _destination.Value, percent);
if (_elapsedLerpDuration >= _totalLerpDuration)
_onCompleteCallback?.Invoke();
// resistance = SliderScript.slider.value;
// current = script2.PotentialDifference / resistance;
}
public void MoveTo(Vector3 destination, Action onComplete = null)
{
var distanceToNextWaypoint = Vector3.Distance(transform.position, destination);
_totalLerpDuration = distanceToNextWaypoint / _speedMetersPerSecond;
_startPosition = transform.position;
_destination = destination;
_elapsedLerpDuration = 0f;
_onCompleteCallback = onComplete;
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "Resistor")
{
Debug.Log("lol");
_speedMetersPerSecond = 25f;
}
}
}
Another script which goes hand in hand with the Moveable script but is not attached to the prefab:
public class MoverController : MonoBehaviour
{
public List<Moveable> targets;
[SerializeField] private Moveable target;
private List<Transform> _waypoints;
private int _nextWaypointIndex;
public static MoverController instance;
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
}
private void OnEnable()
{
MoveToNextWaypoint();
}
private void MoveToNextWaypoint()
{
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] == null) continue;
_waypoints = GetComponentsInChildren<Transform>().ToList();
_waypoints.RemoveAt(0);
var targetWaypointTransform = _waypoints[_nextWaypointIndex];
targets[i].MoveTo(targetWaypointTransform.position, MoveToNextWaypoint);
targets[i].transform.LookAt(_waypoints[_nextWaypointIndex].position);
_nextWaypointIndex++;
if (_nextWaypointIndex >= _waypoints.Count)
_nextWaypointIndex = 0;
}
}
}
[![A picture of the prefab][1]][1]
[1]: https://i.stack.imgur.com/uyuge.png*emphasized text*
Video which shows situation: https://clipchamp.com/watch/MXjnTKl2cqg
The collider is a trigger:
Collider A
Collider B
Event triggered
Collider
Collider
Collision
Collider
Trigger
Trigger
Trigger
Collider
Trigger
Trigger
Trigger
Trigger
To fix this problem, either uncheck the box for it to collide normally, or change your event handler to detect triggers instead of colliders:
private void OnTriggerEnter(Collider other) {
Debug.Log("collision registered");
}
See here for more https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html :)
It is also worth pointing out that if neither of your objects have rigidbodies on, the event will not fire. If you need it to fire but don't want all of the rigidbody physics, you can add one but tick the isKinematic box.
I've tried 2/3 different ways of removing my gameObject from my List but none are working. When I debug the method the debug log is showing up as it should yet the gameobject still isn't removed from the list.
When my teammates kill an enemy I want the enemy to be removed from the list and then destroyed so I can continue to iterate through the List to find the closest enemy to begin attacking. Because the gameObject's are not being removed I get a null reference and i can loop through my for loop to check.
1st Script: List is created and used in a for loop, removing and destroying the enemy also occurs in here.
public class FriendlyManager : MonoBehaviour
{
public NavMeshAgent navMeshAgent;
public Transform player;
public static FriendlyManager singleton;
public float health;
public float minimumDistance;
public int damage;
public List<GameObject> enemies;
private GameObject enemy;
private GameObject enemyObj;
// animations
[SerializeField] Animator animator;
bool isAttacking;
bool isPatroling;
// attacking
[SerializeField] Transform attackPoint;
[SerializeField] public GameObject projectile;
public float timeBetweenAttacks;
bool alreadyAttacked;
private void Awake()
{
navMeshAgent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
enemyObj = new GameObject();
}
private void Start()
{
singleton = this;
isAttacking = false;
isPatroling = true;
animator.SetBool("isPatroling", true);
}
private void Update()
{
for(int i = 0; i < enemies.Count; i++)
{
if(Vector3.Distance(player.transform.position, enemies[i].transform.position) <= minimumDistance)
{
enemy = enemies[i];
Attacking(enemy);
}
}
}
private void Attacking(GameObject enemy)
{
// stop enemy movement.
navMeshAgent.SetDestination(transform.position);
enemyObj.transform.position = enemy.transform.position;
transform.LookAt(enemyObj.transform);
if (!alreadyAttacked)
{
isAttacking = true;
animator.SetBool("isAttacking", true);
animator.SetBool("isPatroling", false);
Rigidbody rb = Instantiate(projectile, attackPoint.position, Quaternion.identity).GetComponent<Rigidbody>();
rb.AddForce(transform.forward * 32f, ForceMode.Impulse);
alreadyAttacked = true;
Invoke(nameof(ResetAttack), timeBetweenAttacks);
}
}
private void ResetAttack()
{
alreadyAttacked = false;
animator.SetBool("isAttacking", false);
}
public void DestroyEnemy(GameObject enemy)
{
enemies.Remove(enemy);
Debug.Log("AHHHHHHH M GOING CRAZY");
Destroy(gameObject);
}
}
}
2nd Script: Deals with the damage and checks enemy's currentHealth. (I have to post it as an image because for Stack Overflow is being annoying.) ._.
Shouldn't it just be
Destroy(enemy);
and not
Destroy(gameObject);
I would like the Enemy(Monster) to shoot players in Shooting Range of the Enemy and return closest players within the Range
I'm quite new to Unity and currently using Unity 2017,2D at the moment, for now.
Thanks so much for your Help Guys!
public class Monster : MonoBehaviour {
[SerializeField]
GameObject Bullet;
public float fireRate;
float nextFire;
// Use this for initialization
void Start () {
fireRate = 1f;
nextFire = Time.time;
}
// Update is called once per frame
void Update () {
CheckIfTimeToFire ();
}
void CheckIfTimeToFire()
{
if (Time.time > nextFire)
{
Instantiate (Bullet, transform.position, Quaternion.identity);
nextFire = Time.time + fireRate;
}
}
}
Here is the Bullet Script
public class Bullet : MonoBehaviour {
public float moveSpeed = 3f;
Rigidbody2D rb;
PlayerAI target;
Vector2 moveDirection;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D> ();
target = GameObject.FindObjectOfType<PlayerAI>();
moveDirection = (target.transform.position - transform.position).normalized * moveSpeed;
rb.velocity = new Vector2 (moveDirection.x, moveDirection.y);
Destroy (this.gameObject, 2);
}
void OnTriggerEnter2D (Collider2D col)
{
if (col.gameObject.name.Equals ("Player"))
{
Debug.Log ("Hit!");
Destroy (gameObject);
}
}
}
Assuming you have a small number of players, a convenient method would be to hold a reference to players as a collection and iterate to get transform.position of each player. Then you can check the distance to the monster to get the minimum distance.
I also suggest doing the logical check on Monster rather than on Bullet, and pass target to Bullet. So that you can choose not to instantiate a bullet if no player is in range.
public class Monster : MonoBehaviour
{
...
public float range;
private PlayerAI[] players;
...
private void Awake()
{
players = FindObjectsOfType<PlayerAI>();
}
...
private void CheckIfTimeToFire()
{
if (Time.time < nextFire)
{
return;
}
PlayerAI closestPlayer = null;
float closestPlayerDistance = float.MaxValue;
foreach (PlayerAI player in players)
{
float playerDistance = Vector3.Distance(transform.position, player.transform.position);
if (playerDistance > range ||
playerDistance > closestPlayerDistance)
{
// Skip to next player.
continue;
}
closestPlayer = player;
closestPlayerDistance = playerDistance;
}
if (closestPlayer == null)
{
// No player in range. Do nothing.
return;
}
GameObject bullet = Instantiate (Bullet, transform.position, Quaternion.identity);
bullet.GetComponent<Bullet>().Initialize(closestPlayer.transform);
nextFire = Time.time + fireRate;
}
...
}
public class Bullet : MonoBehaviour
{
...
private Transform target;
...
public void Initialize(Transform target)
{
this.target = target;
}
...
}
In the example, I get the players on Awake() of a Monster, but if you are instantiating them in runtime you can hold the PlayerAI collection in a separate manager class. In this case a list could be more useful.
public void PlayerManager : MonoBehaviour
{
public List<PlayerAI> Players { get; private set; }
...
private void Awake()
{
while (playerCount < totalPlayerCount)
{
SpawnPlayer();
}
}
private void SpawnPlayer()
{
PlayerAI player = Instantiate(player, ...);
Players.Add(player);
}
// Remove a dead player, etc.
private void OnPlayerDied(PlayerAI player)
{
Players.Remove(player);
}
...
}
public class Monster : MonoBehaviour
{
...
private PlayerManager playerManager;
...
private void Awake()
{
playerManager = FindObjectOfType<PlayerManager>();
}
void CheckIfTimeToFire()
{
...
foreach (PlayerAI player in playerManager.Players)
{
...
}
...
}
}
I am doing a school project in Unity. My team and I decided to make an endless runner 2D game. However, because it is the first time I use C#, I don't know how to make my player's speed accelerate when collide with a game object in Unity. I only know how to destroy the player's health when a collision happens. Please help me! Thank you!
it's quite hard to give answers without seeing any code you've written, but as it's 2D and you've already got the collision damage working, you've probably used an OnCollisionEnter(), well it's very similar: you test if you've collided (which you've already done), then you add force to your player using a rigidbody2d, probably something along the lines of:
public Rigidbody2D rb;
void OnCollisionEnter2D(Collision2D collision)
{
rb.AddForce(direction * force, ForceMode2D.Impulse); // the ForceMode2D is
// optional, it's just so that
// the velocity change is sudden.
}
This should work.
If you have a GameManager that stores the game speed you can also do that too:
private float gameSpeedMultiplier = 0.5f;
void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.CompareTag("Tag of the collided object")
{
GameManager.Instance.gameSpeed += gameSpeedMultiplier;
}
}
public class PlayerContoler : MonoBehaviour
{
public static PlayerContoler instance;
public float moveSpeed;
public Rigidbody2D theRB;
public float jumpForce;
private bool isGrounded;
public Transform GroundCheckPoint;
public LayerMask whatIsGround;
private bool canDoubleJump;
private Animator anim;
private SpriteRenderer theSR;
public float KnockbackLength, KnockbackForce;
private float KnockbackCounter;
public float bonceForce;
public bool stopImput;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
theSR = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(!PauseMenu.instance.isPause && !stopImput)
{
if (KnockbackCounter <= 0)
{
theRB.velocity = new Vector2(moveSpeed * Input.GetAxis("Horizontal"), theRB.velocity.y);
isGrounded = Physics2D.OverlapCircle(GroundCheckPoint.position, .2f, whatIsGround);
if (isGrounded)
{
canDoubleJump = true;
}
if (Input.GetButtonDown("Jump"))
{
if (isGrounded)
{
theRB.velocity = new Vector2(theRB.velocity.x, jumpForce);
AudioManager.instance.PlaySFX(10);
}
else
{
if (canDoubleJump)
{
theRB.velocity = new Vector2(theRB.velocity.x, jumpForce);
canDoubleJump = false;
AudioManager.instance.PlaySFX(10);
}
}
}
if (theRB.velocity.x < 0)
{
theSR.flipX = true;
}
else if (theRB.velocity.x > 0)
{
theSR.flipX = false;
}
} else
{
KnockbackCounter -= Time.deltaTime;
if(!theSR.flipX)
{
theRB.velocity = new Vector2(-KnockbackForce, theRB.velocity.y);
} else
{
theRB.velocity = new Vector2(KnockbackForce, theRB.velocity.y);
}
}
}
anim.SetFloat("moveSpeed", Mathf.Abs(theRB.velocity.x));
anim.SetBool("isGrounded", isGrounded);
}
public void Knockback()
{
KnockbackCounter = KnockbackLength;
theRB.velocity = new Vector2(0f, KnockbackForce);
anim.SetTrigger("hurt");
}
}
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.