In my survival shooter tutorial, I just finished adding the ScoreManager. I copied the scripts exactly as is, but the ScoreManager will never recognize that the score has increased. I've added debugging on the ScoreManager and EnemyHealth scripts to see if the score is actually increasing, and believe me, it isn't.
Here's the EnemyHealth script:
using UnityEngine;
using UnityEngine.AI;
public class EnemyHealth : MonoBehaviour
{
public int startingHealth = 100; // The amount of health thee nemy starts the game with.
public int currentHealth; // The current health the enemy has.
public float sinkSpeed = 2.5f; // The speed at which the enemy sinks through the floor when dead.
public int scoreValue = 10; // The amount added to the player's score when the enemy dies.
public AudioClip deathClip; // The sound to play when the enemy dies.
Animator anim; // Reference to the animator.
AudioSource enemyAudio; // Reference to the audio source.
ParticleSystem hitParticles; // Reference to the particle system that plays when the enemy is damaged.
CapsuleCollider capsuleCollider; // Reference to the capsule collider.
bool isDead; // Whether the enemy is dead.
bool isSinking; // Whether the enemy has started sinking through the floor.
void Awake()
{
// Setting up the references.
anim = GetComponent<Animator>();
enemyAudio = GetComponent<AudioSource>();
hitParticles = GetComponentInChildren<ParticleSystem>();
capsuleCollider = GetComponent<CapsuleCollider>();
// Setting the current health when the enemy first spawns.
currentHealth = startingHealth;
}
void Update()
{
// If the enemy should be sinking...
if (isSinking)
{
// ... move the enemy down by the sinkSpeed per second.
transform.Translate(-Vector3.up * sinkSpeed * Time.deltaTime);
}
}
public void TakeDamage(int amount, Vector3 hitPoint)
{
// If the enemy is dead...
if (isDead)
// ... no need to take damage so exit the function.
return;
// Play the hurt sound effect.
enemyAudio.Play();
// Reduce the current health by the amount of damage sustained.
currentHealth -= amount;
// Set the position of the particle system to where the hit was sustained.
hitParticles.transform.position = hitPoint;
// And play the particles.
hitParticles.Play();
// If the current health is less than or equal to zero...
if (currentHealth <= 0)
{
// ... the enemy is dead.
Death();
}
}
void Death()
{
// The enemy is dead.
isDead = true;
// Turn the collider into a trigger so shots can pass through it.
capsuleCollider.isTrigger = true;
// Tell the animator that the enemy is dead.
anim.SetTrigger("Dead");
// Change the audio clip of the audio source to the death clip and play it (this will stop the hurt clip playing).
enemyAudio.clip = deathClip;
enemyAudio.Play();
}
public void StartSinking()
{
// Find and disable the Nav Mesh Agent.
GetComponent<NavMeshAgent>().enabled = false;
// Find the rigidbody component and make it kinematic (since we use Translate to sink the enemy).
GetComponent<Rigidbody>().isKinematic = true;
// The enemy should no sink.
isSinking = true;
// Increase the score by the enemy's score value.
ScoreManager.score += scoreValue;
// After 2 seconds destory the enemy.
Destroy(gameObject, 2f);
}
}
Here is the ScoreManager:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace CompleteProject
{
public class ScoreManager : MonoBehaviour
{
public static int score; // The player's score.
Text text; // Reference to the Text component.
void Awake()
{
// Set up the reference.
text = GetComponent<Text>();
// Reset the score.
score = 0;
Debug.Log("score reset");
}
void Update ()
{
// Set the displayed text to be the word "Score" followed by the score value.
Debug.Log(score);
text.text = "Score: " + score;
if (Input.GetKeyDown(KeyCode.V))
{
score += 10;
}
}
}
}
The Score should increase when enemy dies, but for some reason it wont. I know this because in the ScoreManager I've added Debug.Log to print out the value of score every frame. I can press V (added for debug) and my score value increases, but normally it does not.
Related
I am trying to do a multiplayer game and I have a problem with spawning prefabs. I want this prefabs to be spawn in 2 fix position but I don't understand why my script doesn't work because when I start the game the objects are spawn in one position. I created an empty game object( I named it Spawner and added the script) and added 2 game objects ( Position1 , Position2 ) as Childs. The prefab is spawn in the position of the Spawner and not position 1 and 2 .
Here is the script I used. Also do I have to add to it PhotonView and Photon Transform ? and something with PunRPC?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnPosition : MonoBehaviour
{
public GameObject[] powersPrefab;
public Transform[] points;
public float beat= (60/130)*2;
private float timer;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (timer > beat)
{
GameObject powers = Instantiate(powersPrefab[Random.Range(0, 2)]);
powers.transform.localPosition = Vector2.zero;
timer -= beat;
}
timer += Time.deltaTime;
}
}
You always set
powers.transform.localPosition = Vector2.zero
The object is instantiated on root level without a parent this equals setting its absolute position .... you always set it to the Unity origin.
You probably wanted to spawn it at the position of on of the elements in points like e.g.:
var powers = Instantiate(
powersPrefab[Random.Range(0, powersPrefab.Length)],
points[Random.Range(0, points.Length)].position,
Quaternion.identity
);
see Instantiate for available overloads.
However as you also state this is for multiplayer so you shouldn't use Instantiate at all since this only spawns this object on this client but not on others. You should probably rather make sure that this spawner is only running on one of your clients and use PhotonNetwork.Instantiate instead.
Something like e.g.
public class SpawnPosition : MonoBehaviour
{
public GameObject[] powersPrefab;
public Transform[] points;
public float beat= (60/130)*2;
private float timer;
// Update is called once per frame
void Update()
{
// only run this if you are the master client
if(!PhotonNetwork.isMasterClient) return;
if (timer > beat)
{
// spawn the prefab object over network
// Note: This requires you to also reference the prefabs in Photon as spawnable object
Instantiate(
powersPrefab[Random.Range(0, 2)].name,
points[Random.Range(0, points.Length)].position,
Quaternion.identity,
0
);
timer -= beat;
}
timer += Time.deltaTime;
}
}
This should work
void Update() {
if (timer > beat) {
GameObject powers = Instantiate(powersPrefab[Random.Range(0, 2)]);
powers.transform.localPosition = points[Random.Range(0, points.Length)].position;
timer -= beat;
}
timer += Time.deltaTime;
}
}
You are not assigning right position, and since they are parentless power.transform.position = Vector2.zero means that power's global position would always 0,0,0. So you must assign it as I wrote above, and it's also randomized.
Im making a wave survival game where you can build up defenses and I want to make it so the enemies will attack the objects you placed down to break them and come attack you. I want to know if theres a way to find if the enemy is not moving at a certain speed, therefore they are stuck behind a wall trying to get to the player. And use that to make them attack the nearest wall if they are stuck.
using UnityEngine;
using System.Collections;
namespace CompleteProject
{
public class EnemyMovement : MonoBehaviour
{
// Reference to the player's position.
Transform player;
Transform wall;
// Reference to the player's health.
PlayerHealth playerHealth;
// Reference to this enemy's health.
EnemyHealth enemyHealth;
// Reference to the nav mesh agent.
UnityEngine.AI.NavMeshAgent nav;
void Awake ()
{
// Set up the references.
player = GameObject.FindGameObjectWithTag ("Player").transform;
playerHealth = player.GetComponent <PlayerHealth> ();
enemyHealth = GetComponent <EnemyHealth> ();
nav = GetComponent <UnityEngine.AI.NavMeshAgent> ();
wall = GameObject.FindGameObjectWithTag("Wall").transform;
}
void Update ()
{
// If the enemy and the player have health left...
if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)
{
// ... set the destination of the nav mesh agent to the player.
nav.SetDestination(player.position);
}
// Otherwise...
else
{
// ... disable the nav mesh agent.
nav.SetDestination(player.position);
}
}
}
}
I added the health script to the walls used on the player as well as a rigidbody
Assuming you have rigidbodies attached to your enemies, you can use the magnitude of the velocity component of the rigidbody to determine its speed. With this you can do something like...
Rigidbody2D rbody;
void Start()
{
rbody = GetComponent<Rigidybody2D>();
}
void Update()
{
if (rbody.velocity.magnitude < 1f)
{
// change target to wall or do some logic here
}
else
{
// target is player,
}
}
I have a very simple 2D game where the player is placed on the left, while the enemies are placed on the right and they move towards the player.
The player would attack the enemies with a sword, and I have this current function for it:
[SerializeField] int damage = 1;
[SerializeField] Transform attackPos;
[SerializeField] LayerMask whatIsEnemy;
[SerializeField] float attackRangeX;
[SerializeField] float attackRangeY;
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
DamageEnemies();
}
}
private void DamageEnemies()
{
Collider2D[] enemiesToDamage = Physics2D.OverlapBoxAll(attackPos.position, new Vector2(attackRangeX, attackRangeY), 0, whatIsEnemy);
for (int i = 0; i < enemiesToDamage.Length; i++)
{
enemiesToDamage[i].GetComponent<EnemyController>().TakeDamage(damage);
}
}
attackPos game object is just placed in front of the player.
AttackRangeX and Y resemble a square in front of the player.
The problem with this setup is that when the attack key is pressed, it will interact with the enemies inside at that point in time only, which is a single frame.
How can I have the collision active for 5 seconds, but only damage the new enemies once. I wouldn't want it to keep damaging the enemies it already damaged.
You could store the already damaged enemies in a List and skip them if you already damaged them.
For the longer active damage some people prefer using Coroutines (IEnumerator) but for keeping it simple for now I'ld use a simple timer.
[SerializeField] int damage = 1;
[SerializeField] Transform attackPos;
[SerializeField] LayerMask whatIsEnemy;
[SerializeField] float attackRangeX;
[SerializeField] float attackRangeY;
// Adjust here how long the damage should take on (in seconds)
[SerializeField] float damageDuration = 5;
// This is the countdown timer for the damage
private float damageTimer;
// Flag that controls the damage mode
private bool isDamaging;
// Here you will store the already damaged enemies to skip them later
private List<Collider2D> alreadyDamagedEnemies = new List<Collider2D>();
private void Start()
{
// Initialize the timer
damageTimer = damageDuration;
}
private void Update()
{
// only take keyboard input if not already damaging
// to prevent a peanant press
if(!isDamaging)
{
// Hint: If you want you could with a second timer at this point add
// a cooldown so you can not directly atack again right after the damage duration
// Just thought it might be interesting for you so I leave it as a homework ;)
if (Input.GetKeyDown(KeyCode.A))
{
// Only activate the damaging
isDamaging = true;
}
}
else // We are currently in damage mode
{
// If end of timer reset and leave damage mode
if(damageTimer <= 0)
{
// Reset the timet
damageTimer = damageDuration;
// Switch off damage mode
isDamaging = false;
// Reset the list
alreadyDamagedEnemies.Clear();
}
// else make damage and redue the timer
else
{
DamageEnemies();
// Reduce the timer by the time passed since last frame
damageTimer-= Time.deltaTime;
}
}
}
private void DamageEnemies()
{
Collider2D[] enemiesToDamage = Physics2D.OverlapBoxAll(attackPos.position, new Vector2(attackRangeX, attackRangeY), 0, whatIsEnemy);
foreach (var currentEnemy in enemiesToDamage)
{
// Skip if you already damaged this enemy
if(alreadyDamagedEnemies.Contains(currentEnemy) continue;
currentEnemy.GetComponent<EnemyController>().TakeDamage(damage);
// Add the damaged enemy to the list
alreadyDamagedEnemies.Add(currentEnemy);
}
}
This example assumes that you press A once but perform the damage for the full 5 seconds also if the key is released meanwhile.
If you rather want it to only take place during the key is pressed and with a maximum of 5 seconds instead, I'm sure you will be able to figure it out on your own using
if(Input.GetKey(KeyCode.A)){ ... }.
(This is a 2D project)
I have a jumping issue where if my character WALKS into an X platform, she won't jump, but when she JUMPS ONTO the X platform, she can perform a jump.
For the platforms I am currently using 2 Box Collider 2Ds (one with "is trigger" checked)
For the character I am currently using 2 Box Collider 2Ds (one with "is trigger" checked) and Rigidbody 2D.
Below is the code for jumping and grounded I am currently trying to use.
{
public float Speed;
public float Jump;
bool grounded = false;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
if (grounded)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, Jump);
}
}
}
void OnTriggerEnter2D()
{
grounded = true;
}
void OnTriggerExit2D()
{
grounded = false;
}
}
Issue arises on the same part of every platform. (Each square represents a single platform sprite and they have all the same exact characteristics, since I copy pasted each one of them). Please check the photo on this link: https://imgur.com/a/vTmHw
It happens because your squares have seperate colliders. Imagine this:
There are two blocks : A and B. You are standing on block A. Now you try to walk on block B. As soon as your Rigidbody2D collider touches block B, your character gets an event OnTriggerEnter2D(...). Now you claim, that you are grounded.
However, at this moment you are still colliding with block A. As soon as your Rigidbody2D no longer collides with block A, your character receives OnTriggerExit2D(...). Now you claim, that you are no longer grounded. But in fact, you are still colliding with block B.
Solution
Instead of having bool variable for checking if grounded, you could have byte type variable, called collisionsCounter:
Once you enter a trigger - increase the counter.
Once you exit a trigger - decrease the counter.
Do some checking to make sure you are actually above the collider!
Now, once you need to check if your character is grounded, you can just use
if (collisionsCounter > 0)
{
// I am grounded, allow me to jump
}
EDIT
Actually, after investingating question further, I've realized that you have totally unnecessary colliders (I'm talking about the trigger ones). Remove those. Now you have only one collider per object. But to get the calls for collision, you need to change:
OnTriggerEnter2D(...) to OnCollisionEnter2D(Collision2D)
OnTriggerExit2D(...) to OnCollisionExit2D(Collision2D)
Final code
[RequireComponent(typeof(Rigidbody2D))]
public sealed class Character : MonoBehaviour
{
// A constant with tag name to prevent typos in code
private const string TagName_Platform = "Platform";
public float Speed;
public float Jump;
private Rigidbody2D myRigidbody;
private byte platformCollisions;
// Check if the player can jump
private bool CanJump
{
get { return platformCollisions > 0; }
}
// Called once the script is started
private void Start()
{
myRigidbody = GetComponent<Rigidbody2D>();
platformCollisions = 0;
}
// Called every frame
private void Update()
{
// // // // // // // // // // // // // //
// Need to check for horizontal movement
// // // // // // // // // // // // // //
// Trying to jump
if (Input.GetKeyDown(KeyDode.UpArrow) && CanJump == true)
Jump();
}
// Called once Rigidbody2D starts colliding with something
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions++;
}
// Called once Rigidbody2D finishes colliding with something
private void OnCollisionExit2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions--;
}
// Makes Character jump
private void Jump()
{
Vector2 velocity = myRigidbody.velocity;
velocity.y = Jump;
myRigidbody.velocity = velocity;
}
}
Here can be minor typos as all the code was typed inside Notepad...
I think there are a couple of issues here.
Firstly, using Triggers to check this type of collision is probably not the best way forward. I would suggested not using triggers, and instead using OnCollisionEnter2D(). Triggers just detect if the collision space of two objects has overlapped each other, whereas normal collisions collide against each otehr as if they were two solid objects. Seen as though you are detecting to see if you have landed on the floor, you don't want to fall through the floor like Triggers behave.
Second, I would suggest using AddForce instead of GetComponent<Rigidbody2D>().velocity.
Your final script could look like something like this:
public class PlayerController : MonoBehaviour
{
public float jumpForce = 10.0f;
public bool isGrounded;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void OnCollisionEnter2D(Collision2D other)
{
// If we have collided with the platform
if (other.gameObject.tag == "YourPlatformTag")
{
// Then we must be on the ground
isGrounded = true;
}
}
void Update()
{
// If we press space and we are on the ground
if(Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
// Add some force to our Rigidbody
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
// We have jumped, we are no longer on the ground
isGrounded = false;
}
}
}
Iam trying to load my "PlayerHealth" Script to my "GameOverManager" to check my currentHealth from the PlayerHealth-Script.
If the current health is "0" - I want to trigger an animation.
The problem is, that Unity gives me an error with following message:
"NullReferenceException: Object reference not set to an instance of an object
GameOverManager.Update () (at Assets/GameOverManager.cs:32)"
Here is the piece of Code of my GameOverManager:
public class GameOverManager : MonoBehaviour {
public PlayerHealth playerHealthScript;
public float restartDelay = 5f;
Animator anim;
float restartTimer;
private void Awake()
{
anim = GetComponent<Animator>();
}
private void Update()
{
playerHealthScript = GetComponent<PlayerHealth>();
if (playerHealthScript.currentHealth <= 0) {
anim.SetTrigger("GamerOver");
restartTimer += Time.deltaTime;
if (restartTimer >= restartDelay) {
SceneManager.LoadScene(2);
}
}
}
}
The error is triggered on the following line:
if (playerHealthScript.currentHealth <= 0)
Here is the hierarchy - FPSController holds "PlayerHealth" - HUDCanvas holds "GameOverManager:
Here are the inspectors:
Here is the code of "PlayerHealth":
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
public int startingHealth = 100; // The amount of health the player starts the game with.
public int currentHealth; // The current health the player has.
public Slider healthSlider; // Reference to the UI's health bar.
public Image damageImage; // Reference to an image to flash on the screen on being hurt.
public AudioClip deathClip; // The audio clip to play when the player dies.
public float flashSpeed = 5f; // The speed the damageImage will fade at.
public Color flashColour = new Color(1f, 0f, 0f, 0.1f); // The colour the damageImage is set to, to flash.
public float restartDelay = 5f;
//Animator anim; // Reference to the Animator component.
public AudioSource playerAudio; // Reference to the AudioSource component.
// PlayerMovement playerMovement; // Reference to the player's movement.
// PlayerShooting playerShooting; // Reference to the PlayerShooting script.
bool isDead; // Whether the player is dead.
bool damaged; // True when the player gets damaged.
void Awake()
{
// Setting up the references.
// anim = GetComponent<Animator>();
// playerAudio = GetComponent<AudioSource>();
// playerMovement = GetComponent<PlayerMovement>();
// playerShooting = GetComponentInChildren<PlayerShooting>();
// Set the initial health of the player.
currentHealth = startingHealth;
}
void Update()
{
// If the player has just been damaged...
if (damaged)
{
// ... set the colour of the damageImage to the flash colour.
damageImage.color = flashColour;
}
// Otherwise...
else
{
// ... transition the colour back to clear.
damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
}
// Reset the damaged flag.
damaged = false;
}
public void TakeDamage(int amount)
{
// Set the damaged flag so the screen will flash.
damaged = true;
// Reduce the current health by the damage amount.
currentHealth -= amount;
playerAudio.Play();
Debug.Log("PLayer Health: " + currentHealth);
// Set the health bar's value to the current health.
healthSlider.value = currentHealth;
// Play the hurt sound
playerAudio.Play();
// If the player has lost all it's health and the death flag hasn't been set yet...
if (currentHealth <= 0 && !isDead)
{
// ... it should die.
Death();
}
}
void Death()
{
// Set the death flag so this function won't be called again.
isDead = true;
Debug.Log("In der Death Funktion");
First of all, you don't need, or better, you SHOULD NOT, use GetComponent inside Update, it's a very slow method and it impacts a lot the performance.
So, change your code to this:
public class GameOverManager : MonoBehaviour {
public PlayerHealth playerHealthScript;
public float restartDelay = 5f;
private Animator anim;
private float restartTimer;
private void Awake() {
anim = GetComponent<Animator>();
//No need for GetComponent<PlayerHealth>() if you assign it in the Inspector
//playerHealthScript = GetComponent<PlayerHealth>();
}
private void Update() {
if (playerHealthScript.currentHealth <= 0) {
anim.SetTrigger("GamerOver");
restartTimer += Time.deltaTime;
if (restartTimer >= restartDelay) {
SceneManager.LoadScene(2);
}
}
}
}
Moreover, your bug happens most probably because in the Inspector you assigned to the playerHealthScript variable the game object containing the PlayerHealth script. But, since you try in Update to get the script component again but this time from the game object that has the GameOverManager script (and I assume it doesn't have the PlayerHealth script), you get the NullReference since that script can't be found on this game object.
So, as you can see from the two lines commented out in my code, you actually don't need to get that component from script, just assign it via Inspector and you're good to go.