I have been following the Shooter Survival tutorial offered by Unity. I have been using it to understand how certain bits of code interact with and call upon each other in different scripts in c#. Recently I have run into a problem where I take no damage from the enemy attacks; I can still harm them.
My code for the Player Health
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
namespace CompleteProject
{
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.
Animator anim; // Reference to the Animator component.
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;
// Set the health bar's value to the current health.
healthSlider.value = currentHealth;
// Play the hurt sound effect.
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;
// Turn off any remaining shooting effects.
playerShooting.DisableEffects ();
// Tell the animator that the player is dead.
anim.SetTrigger ("Die");
// Set the audiosource to play the death clip and play it (this will stop the hurt sound from playing).
playerAudio.clip = deathClip;
playerAudio.Play ();
// Turn off the movement and shooting scripts.
playerMovement.enabled = false;
playerShooting.enabled = false;
}
public void RestartLevel ()
{
// Reload the level that is currently loaded.
SceneManager.LoadScene (0);
}
}
}
My code for the Enemy Attack
using UnityEngine;
using System.Collections;
namespace CompleteProject
{
public class EnemyAttack : MonoBehaviour
{
public float timeBetweenAttacks = 0.5f; // The time in seconds between each attack.
public int attackDamage = 10; // The amount of health taken away per attack.
Animator anim; // Reference to the animator component.
GameObject player; // Reference to the player GameObject.
PlayerHealth playerHealth; // Reference to the player's health.
EnemyHealth enemyHealth; // Reference to this enemy's health.
bool playerInRange; // Whether player is within the trigger collider and can be attacked.
float timer; // Timer for counting up to the next attack.
void Awake ()
{
// Setting up the references.
player = GameObject.FindGameObjectWithTag ("Player");
playerHealth = player.GetComponent <PlayerHealth> ();
enemyHealth = GetComponent<EnemyHealth>();
anim = GetComponent <Animator> ();
}
void OnTriggerEnter (Collider other)
{
// If the entering collider is the player...
if(other.gameObject == player)
{
// ... the player is in range.
playerInRange = true;
}
}
void OnTriggerExit (Collider other)
{
// If the exiting collider is the player...
if(other.gameObject == player)
{
// ... the player is no longer in range.
playerInRange = false;
}
}
void Update ()
{
// Add the time since Update was last called to the timer.
timer += Time.deltaTime;
// If the timer exceeds the time between attacks, the player is in range and this enemy is alive...
if(timer >= timeBetweenAttacks && playerInRange && enemyHealth.currentHealth > 0)
{
// ... attack.
Attack ();
}
// If the player has zero or less health...
if(playerHealth.currentHealth <= 0)
{
// ... tell the animator the player is dead.
anim.SetTrigger ("PlayerDead");
}
}
void Attack ()
{
// Reset the timer.
timer = 0f;
// If the player has health to lose...
if(playerHealth.currentHealth > 0)
{
// ... damage the player.
playerHealth.TakeDamage (attackDamage);
}
}
}
}
I have been told from a friend of mine that uses C++ that he finds it odd that in the Player hierarchy the Player Health is an asset and might have to be directly called upon somehow.
Screen shot of Player hierarchy
I can confirm that the code works as you have provided it, but requires a specific set of circumstances for EnemyAttack to successfully call PlayerHealth.TakeDamage():
The GameObject your EnemyAttack is attached to needs a Rigidbody, you may want to set IsKinematic = true
Any type of Collider component - flagged with IsTrigger = true
The GameObject your PlayerHealth is attached to needs both a Rigidbody and Collider with the Gameobject's Tag set to 'Player', as you've proven in the screencap you provided
I believe your EnemyAttack is failing to damage the player because the flag playerInRange is always false. This flag is set to true when passing through OnTriggerEnter() but not before OnTriggerExit(). Since the components aren't set up properly in the scene, OnTriggerEnter() will never be called.
The problem is likely here:
if(timer >= timeBetweenAttacks && playerInRange && enemyHealth.currentHealth > 0)
{
// ... attack.
Attack ();
}
For the player to be attacked by the Enemy three things have to be true at th-same time.
timer has to more than or equals to 0.5f. playerInRange must also be true. Finally enemyHealth.currentHealth must be more than 0.
Once your code is over 30 lines with too many C# scripts, you must know how to debug your own code.
My best guess is that playerInRange is never true and that's why attack is not being called. Add Debug.Log("Attacked Player"); before Attack (); and check if "Attacked Player" is shown when Enemy attacks player. If it is not then one of these is false.
Also add Debug.Log("Player is in Range"); to see if player is being detected at-all in the
void OnTriggerEnter (Collider other); function like below:
void OnTriggerEnter (Collider other)
{
// If the entering collider is the player...
if(other.gameObject == player)
{
// ... the player is in range.
playerInRange = true;
Debug.Log("Player is in Range");
}
}
These will tell you what is going on in your scene.
Related
I'm making a top-down game where you drive a car and shoot targets at the same time. I have a script that makes a sprite of a crosshair follows the mouse cursor and I want to have it set up so that when the player presses the mouse button (the mouse button isn't in the code now) and the crosshair sprite is overlapping an enemy sprite, the enemy dies. I was following this documentation on Bounds.Intersects. Here's my code:
public class shootingScript : MonoBehaviour
{
public GameObject target, enemy;
CircleCollider2D targetCollider;
CapsuleCollider2D enemyCollider;
// Start is called before the first frame update
void Start()
{
//Check that the first GameObject exists in the Inspector and fetch the Collider
if (target != null)
{
print("targ not null");
targetCollider = target.GetComponent<CircleCollider2D>();
}
//Check that the second GameObject exists in the Inspector and fetch the Collider
if (enemy != null)
{
print("enemy not null");
enemyCollider = enemy.GetComponent<CapsuleCollider2D>();
}
}
// Update is called once per frame
void Update()
{
if (targetCollider.bounds.Intersects(enemyCollider.bounds))
{
print("hit");
Destroy(enemy);
}
}
}
In-game "targ not null" and "enemy not null" prints but when I move my cursor and the crosshair goes over the enemy "hit" is not printed and the enemy is not destroyed. I have a CircleCollider2D on the crosshair and a CapsuleCollider2D on the enemy. The script is on an empty game object. I also tried sprite.bounds but that resulted in the enemy getting killed as soon as I run the game.
EDIT:
Here's the code that keeps the crosshair sprite on the cursor. I copied it from somewhere. I set moveSpeed to 99999 since I want the crosshair sprite to be exactly where the mouse is.
public class mouseReticle : MonoBehaviour
{
private Vector3 mousePosition;
public float moveSpeed = 0.1f;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
mousePosition = Input.mousePosition;
mousePosition = Camera.main.ScreenToWorldPoint(mousePosition);
transform.position = Vector2.Lerp(transform.position, mousePosition, moveSpeed);
}
}
The target sprite and enemy sprite were at different depths (z).
I'm working on a RTS game. I did a normal move script and I increase the agent's stopping distance so they don't look at each other and pump that much. But they still hit each other.
I can't find the way to make agents avoid each other and not push each other. Or someway to ignore the physics while still trying to avoid each other. Here is the code for the click to move
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class moveTest : MonoBehaviour {
NavMeshAgent navAgent;
public bool Moving;
// Use this for initialization
void Start () {
navAgent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update () {
move();
}
void move()
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Input.GetMouseButtonDown(1))
{
Moving = true;
if (Moving)
{
if (Physics.Raycast(ray, out hit, 1000))
{
navAgent.SetDestination(hit.point);
navAgent.Resume();
}
}
}
}
}
From the following link (which sadly don´t have images anymore)
Modify the avoidancePriority. You can set the enemie´s value of
30 or 40 for example.
Add a NavMeshObstacle component to the "enemy" prefab
Use this script for the Enemy Movement
Script
using UnityEngine;
using System.Collections;
public class enemyMovement : MonoBehaviour {
public Transform player;
NavMeshAgent agent;
NavMeshObstacle obstacle;
void Start () {
agent = GetComponent< NavMeshAgent >();
obstacle = GetComponent< NavMeshObstacle >();
}
void Update () {
agent.destination = player.position;
// Test if the distance between the agent and the player
// is less than the attack range (or the stoppingDistance parameter)
if ((player.position - transform.position).sqrMagnitude < Mathf.Pow(agent.stoppingDistance, 2)) {
// If the agent is in attack range, become an obstacle and
// disable the NavMeshAgent component
obstacle.enabled = true;
agent.enabled = false;
} else {
// If we are not in range, become an agent again
obstacle.enabled = false;
agent.enabled = true;
}
}
}
Basically the issue that this approach tries to solve is when the player is surrounded by enemies, those which are in range to attack (almost touching the player) stop moving to attack, but the enemies in the second or third row are still trying to reach the player to kill him. Since they continue moving, they push the other enemies and the result is not really cool.
So with this script, when an enemy is in range to attack the character, it becomes an obstacle, so other enemies try to avoid them and instead of keep pushing, go around looking for another path to reach the player.
Hope it can help you some how
I am pretty fluent in using Unity, but regarding Mechanim and Animations I am not the too great at currently, so please don't give me too much of a hard time lol. So I have this boolean that is in my GameManager script:
public bool countDownDone = false;
This boolean gets set to true once my "3,2,1, GO!" Countdown Timer ends at the beginning of my game. Everything in my game starts after that boolean is set to true. Example:
using UnityEngine;
using System.Collections;
public class PlaneMover : MonoBehaviour {
private GameManagerScript GMS; // Reference to my GameManager Script
public float scrollSpeed;
public float tileSizeZAxis;
public float acceleration; //This has to be a negative number in the inspector. Since our plane is coming down.
private Vector3 startPosition;
void Start () {
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> (); //Finds the Script at the first frame
// Transform position of the quad
startPosition = transform.position;
}
void Update () {
if (GMS.countDownDone == true) //Everything starts once the countdown ends.
{
/* Every frame - time in the game times the assigned scroll speed
and never exceed the length of the tile that we assign */
float newPosition = Mathf.Repeat (Time.time * scrollSpeed, tileSizeZAxis);
// New position equal to the start position plus vector3forward times new position
transform.position = startPosition + Vector3.forward * newPosition; // was vector3.forward
scrollSpeed += Time.deltaTime * acceleration; // This makes the plane increase in speed over time with
// whatever our acceleration is set to.
}
}
}
I have this Crawling animation that plays at the very beginning of the game (Even before the Timer ends) and loops forever. My question is , how do I make that crawling animation also start once that boolean is set to "true"? Or do I just apply it to a CoRoutine and make it play after 3 seconds? I have done extensive research on whether to reference Animation or Animator and I got confused on that too. Any advice? If you need more pictures or details on my question, just let me know. Thank you! :)
NEW CODE BELOW
using UnityEngine;
using System.Collections;
public class Crawling : MonoBehaviour {
Animator animator;
private GameManagerScript GMS;
void Start ()
{
animator = GetComponent<Animator> ();
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> ();
}
void Update ()
{
if (GMS.countDownDone == true)
{
animator.Play("Main Character Crawling", 1);
}
}
}
I have this Crawling animation that plays at the very beginning of the
game (Even before the Timer ends) and loops forever. My question is ,
how do I make that crawling animation also start once that boolean is
set to "true"?
The solution is to play the animation from script.
Remove the currentAnimation.
Select the GameObject your PlaneMover script is attached to, attach Animation and Animator components to it. Make sure that the Animation's Play Automatically is unchecked.
public class PlaneMover : MonoBehaviour {
private GameManagerScript GMS; // Reference to my GameManager Script
public float scrollSpeed;
public float tileSizeZAxis;
public float acceleration; //This has to be a negative number in the inspector. Since our plane is coming down.
private Vector3 startPosition;
Animation animation;
public AnimationClip animationClip; //Assign from Editor
void Start () {
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> (); //Finds the Script at the first frame
// Transform position of the quad
startPosition = transform.position;
animation = GetComponent<Animation>();
//Add crawing Animation
animation.AddClip(animationClip, "Crawling");
//Add other animation clips here too if there are otheres
}
void Update ()
{
if (GMS.countDownDone) //Everything starts once the countdown ends.
{
/* Every frame - time in the game times the assigned scroll speed
and never exceed the length of the tile that we assign */
float newPosition = Mathf.Repeat (Time.time * scrollSpeed, tileSizeZAxis);
// New position equal to the start position plus vector3forward times new position
transform.position = startPosition + Vector3.forward * newPosition; // was vector3.forward
scrollSpeed += Time.deltaTime * acceleration; // This makes the plane increase in speed over time with
// whatever our acceleration is set to.
//Play Animation
animation.PlayQueued("Crawling", QueueMode.CompleteOthers);
}
}
} }
I solved the problem!!
This is my script:
using UnityEngine;
using System.Collections;
public class Crawling : MonoBehaviour {
public Animator animator;
private GameManagerScript GMS;
void Start ()
{
animator = GetComponent<Animator> ();
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> ();
}
void Update ()
{
if (GMS.countDownDone == true) {
animator.enabled = true;
}
else
{
animator.enabled = false;
}
}
}
All I do is just enable the Animator that I attach in the Inspector whenever "countDownDone" becomes "true", and for added safety, I added the "else" for it to be disabled. If someone notices a way for me to improve this script, please let me know. Thank you :)
Shorter version:
if (GMS.countDownDone)
animator.enabled = true;
else
animator.enabled = false;
as the title says, I want to make it so that when the player game object gets destroyed, the game will then pause and bring up a screen (I've made one called GameOverScreen), however, I cannot seem to get my head around it. The screen displays however the game doesn't pause. Any idea what could be causing this? Here is my code:
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
public float m_StartingHealth = 100f; // The amount of health each tank starts with.
public Slider m_Slider; // The slider to represent how much health the tank currently has.
public Image m_FillImage; // The image component of the slider.
public Color m_FullHealthColor = Color.green; // The color the health bar will be when on full health.
public Color m_ZeroHealthColor = Color.red; // The color the health bar will be when on no health.
public GameObject m_ExplosionPrefab; // A prefab that will be instantiated in Awake, then used whenever the tank dies.
public Camera mainCamera;
public Camera gameOverCamera;
public GameObject GameOverMenu;
private bool gameover = false;
private AudioSource m_ExplosionAudio; // The audio source to play when the tank explodes.
private ParticleSystem m_ExplosionParticles; // The particle system the will play when the tank is destroyed.
private float m_CurrentHealth; // How much health the tank currently has.
private bool m_Dead; // Has the tank been reduced beyond zero health yet?
void Start()
{
GameOverMenu.SetActive(false);
}
private void Awake()
{
// Instantiate the explosion prefab and get a reference to the particle system on it.
m_ExplosionParticles = Instantiate(m_ExplosionPrefab).GetComponent<ParticleSystem>();
// Get a reference to the audio source on the instantiated prefab.
m_ExplosionAudio = m_ExplosionParticles.GetComponent<AudioSource>();
// Disable the prefab so it can be activated when it's required.
m_ExplosionParticles.gameObject.SetActive(false);
}
private void OnEnable()
{
// When the tank is enabled, reset the tank's health and whether or not it's dead.
m_CurrentHealth = m_StartingHealth;
m_Dead = false;
// Update the health slider's value and color.
SetHealthUI();
}
public void TakeDamage(float amount)
{
// Reduce current health by the amount of damage done.
m_CurrentHealth -= amount;
// Change the UI elements appropriately.
SetHealthUI();
// If the current health is at or below zero and it has not yet been registered, call OnDeath.
if (m_CurrentHealth <= 0f && !m_Dead)
{
{
gameover = !gameover;
}
if (gameover)
{
GameOverMenu.SetActive(true);
Time.timeScale = 0;
}
if (!gameover)
{
GameOverMenu.SetActive(false);
Time.timeScale = 1;
}
OnDeath();
mainCamera.transform.parent = null;
mainCamera.enabled = true;
gameOverCamera.enabled = false;
}
}
private void SetHealthUI()
{
// Set the slider's value appropriately.
m_Slider.value = m_CurrentHealth;
// Interpolate the color of the bar between the choosen colours based on the current percentage of the starting health.
m_FillImage.color = Color.Lerp(m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
}
private void OnDeath()
{
// Set the flag so that this function is only called once.
m_Dead = true;
// Move the instantiated explosion prefab to the tank's position and turn it on.
m_ExplosionParticles.transform.position = transform.position;
m_ExplosionParticles.gameObject.SetActive(true);
// Play the particle system of the tank exploding.
m_ExplosionParticles.Play();
// Play the tank explosion sound effect.
m_ExplosionAudio.Play();
// Turn the tank off.
gameObject.SetActive(false);
}
}
set a precise float value like this ,
Time.timeScale = 0.0f;
Firstly, you don't need two if's, just do:
if (gameover) {
Time.timeScale = 0;
}else{
Time.timeScale = 1;
}
Next, add this:
void OnPauseGame ()
{
GameOverMenu.SetActive(true);
}
If still doesn't work, then add:
void Update(){
if(Time.timeScale == 0)return;
}
Easiest way to my opinion to pause the game upon an event is go use the 'Error Pause' button on the Console.
Of course you will need to generate an error but this is easy.
Just use the
Debug.LogError("Game Paused");
Good luck
This code is for an elevator-type platform, where once the player stands on it, it 'takes' the player up by adding force onto it.
The thing is, while the force is created, the rigidbody (the player) does not move when the elevator moves. The code was written in C#, using Unity 5. In the code, the player is assigned the public 'rb', and contains a rigidbody.
The animation is a simple animation clip that moves the elevator up. Any ideas? Thank you for your time and answers in advance.
The elevator is Kinematic, the Player is not.
using UnityEngine;
using System.Collections;
/*This script activates when the player steps on the elevator, as it takes them up a floor.*/
public class ElevatorMovementScript : MonoBehaviour
{
private bool elevatorUp = false;
public Animation anim;
public int elevatorDelay = 5;
public int force = 800;
public Rigidbody rb;
// Use this for initialization
void Start ()
{
anim = GetComponent<Animation>();
}
// Update is called once per frame
void Update ()
{
}
/*Checks if the player has stepped onto the elevator. If the player has, it waits five seconds, and then pushes the player up.*/
void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player" && !elevatorUp)
{
Invoke("AnimationPlay",elevatorDelay);
elevatorUp = true;
}
}
/*Plays the animation of the player going up. Used for the 'Invoke' method.*/
void AnimationPlay()
{
rb.AddForce(transform.up * force);
Debug.Log (transform.up * force);
anim.Play ("Up");
}
}
It looks like this script is on your elevator's game object, in which case this line:
rb.AddForce(transform.up * force);
Will try to apply a force to the elevator, not the player. You've got to keep track of the player's rigidbody or somehow get it on demand in AnimationPlay.
You said that
the player is assigned the public 'rb'
But rb = GetComponent<Rigidbody>(); will ignore this and use the rigidbody attached to the gameobject that ElevatorMovementScript is attached to.