I am facing a problem in Unity3D. I have the same health script attached to both the player and the enemy. I want to show the game-over message when the player dies but the issue is that the game over message appears for both the player and enemy when they die.
My code looks like is that:
public class CharacterStats : MonoBehaviour
{
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
health = Mathf.Clamp (health, 0, 100);
}
public void damage(float damage)
{
health -= damage;
if(health<=0)
{
Die();
Application.LoadLevel(gameover);
}
}
void Die()
{
characterController.enabled = false;
if (scriptstodisable.Length == 0)
return;
foreach (MonoBehaviour scripts in scriptstodisable)
scripts.enabled = false;
if (ragdollmanger != null)
ragdollmanger.Raggdoll();
}
}
As you are using 1 script for both player and enemy. You should have different classes for both and implement an interface or derive from a base class to implement health:
public class Character : MonoBehaviour
{
public float Health;
public virtual void Damage(float damageValue)
{
Health -= damageValue;
}
public virtual void Die()
{
}
}
public Enemy : Character
{
public override void Die()
{
// do enemy related stuff
}
}
public Player : Character
{
public override void Die()
{
// do player related stuff.
// like game over screen
}
}
Hope this helps :)
You could use a bool to check whether CharacterStats is attached to the player, for example by adding a tag called ”Player” to the player game object and checking if gameObject.tag == “Player”, or you could equivalently name the game object ”Player” and check gameObject.name if you so wish.
You could then run the function for the game over message only if the game object is a player (isPlayer is true).
public class CharacterStats : MonoBehaviour
{
bool isPlayer = false;
// Use this for initialization
void Start ()
{
if(gameObject.tag == “Player”)
{
isPlayer = true;
}
}
// Update is called once per frame
void Update ()
{
health = Mathf.Clamp (health, 0, 100);
}
public void damage(float damage)
{
health -= damage;
if(health<=0)
{
if(isPlayer)
{
// Do Player-only stuff
}
// Do Stuff for both players and enemies
}
}
}
Related
I'm currently developing developing a 2d top-down RPG game in Unity.
I am trying to implement a healing system where you heal some points when you buy a coffee. This is my healthManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HealthManager : MonoBehaviour
{
public int maxHealth = 10;
public int currentHealth;
public HealthBarScript healthBar;
void Start()
{
currentHealth = maxHealth;
healthBar.SetMaxHealth(maxHealth);
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
TakeDamage(1);
}
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
healthBar.SetHealth(currentHealth);
if(currentHealth <= 0)
{
currentHealth = 0;
}
}
public void heal(int heal)
{
currentHealth += heal;
healthBar.SetHealth(currentHealth);
if(currentHealth >= maxHealth)
{
currentHealth = maxHealth;
}
}
}
And this is the script to buy coffee from a game object (sth. like a healing fountain):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class KaffeeautomatDialog : MonoBehaviour
{
public GameObject dialogBox;
public Text dialogText;
public string dialog;
public bool playerInRange;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.E) && playerInRange)
{
if(dialogBox.activeInHierarchy)
{
dialogBox.SetActive(false);
}
else
{
dialogBox.SetActive(true);
dialogText.text = dialog;
}
}
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
Debug.Log("Player entered");
playerInRange = true;
var healthManager = other.GetComponent<HealthManager>();
if(Input.GetKeyDown(KeyCode.J) && playerInRange)
{
if(healthManager != null)
{
healthManager.heal(5);
Debug.Log("You healed 5 Points!");
}
}
}
}
public void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
Debug.Log("Player left");
playerInRange = false;
dialogBox.SetActive(false);
}
}
}
The Dialog Box shows up just fine and the Text is displayed. When i am staning in front of the healing fountain, i can activate and deactivate the dialog box by pressing "e".
But when i press "j", i don't heal and the console.log won't appear in Unity.
Did i mess up the Component import?
Checking for an Input it should always happen inside the Update() method and not inside the OnTriggerEnter2D() because the OnTriggerEnter2D() method it will only executed everytime something gets inside the trigger which might happen only onces. On the other hand Update() it will be executed every single frame and check for an Input.
What you can do is the following, inside your OnTriggerEnter2D() method you need to have a global boolean variable that indicates that the player has entered the trigger and inside your Update() method when you check for the Input key "J" you need to check if the above boolean flag is true, if it is true then continue with the heal process. Also you need to make the flag false when the player exit the trigger.
From what I see you already have such flag playerInRange, you can write the following code:
public HealthManager healthManager;
void Update()
{
if(playerInRange)
{
if(Input.GetKeyDown(KeyCode.E))
{
if(dialogBox.activeInHierarchy)
{
dialogBox.SetActive(false);
}
else
{
dialogBox.SetActive(true);
dialogText.text = dialog;
}
}
if(Input.GetKeyDown(KeyCode.E))
{
if(healthManager != null)
{
healthManager.heal(5);
Debug.Log("You healed 5 Points!");
}
}
}
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
Debug.Log("Player entered");
playerInRange = true;
healthManager = other.GetComponent<HealthManager>();
}
}
Remove the Input check from your OnTriggerEnter2D() method.
Im currently trying to code a health system for my game and I want my Player GameObject to ignore collision with the Health Potion GameObject if the Player has max health. My problem is that I cannot simply turn off the collision between the Player Layer and Health Potion Layer because I only want to ignore collision if the Player has Max Health. I tried doing it myself but it didn't work. Here's my code:
public class ExampleCodeUA : MonoBehaviour{
public int PlayerMaxHealth = 100, PlayerCurrentHealth;
public HealthBar healthBar;
private void Start()
{
PlayerCurrentHealth = PlayerMaxHealth;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("HealthPotion"))
{
if (PlayerCurrentHealth == PlayerMaxHealth)
{
Physics2D.IgnoreLayerCollision(6, 7);
}
else if (PlayerCurrentHealth > 50)
{
GetHealth(PlayerMaxHealth - PlayerCurrentHealth);
}
else if (PlayerCurrentHealth <= 50)
{
GetHealth(50);
}
}
}
void GetHealth(int healing)
{
PlayerCurrentHealth += healing;
healthBar.SetHealth(PlayerCurrentHealth);
}
}
You don't want to modify your physics configuration at runtime. What you could do to avoid the collision... is to make it impossible, by having nothing to collide with. Both of your objects have a collider. What you could do is to disable all existing HealthPotion colliders by modifying their code. An implementation could be the following:
using System.Collections.Generics;
using UnityEngine;
public class HealthPotion : MonoBehaviour
{
private static List<HealthPotion> _existingPotions = new List<HealthPotion>();
public static void EnablePotionsCollider(bool value)
{
foreach (var potion in _existingPotions)
{
potion._collider.enabled = value;
}
}
private Collider _collider;
void Awake()
{
_collider = GetComponent<Collider>();
}
void OnEnable()
{
_existingPotions.Add(this);
}
void OnDisable()
{
_existingPotions.Remove(this);
}
}
Then you just have to call the method when you want to enable/disable by doing
HealthPotion.EnablePotionsCollider(value you want);
The playerLife variable doesn't update visibly in the Inspector or the on-screen Health Text, but the Player still dies because the playerLife drops below zero.
I've determined that the Player prefab attached to the Zombie GameObject is solely the Player prefab rather than the in-scene active Player. How do I make it so that the zombies always reference the in-scene active Player rather than the basic Player prefab, by script? (Also, it won't allow me to manually drag the active Player into the Zombie)
Call hierarchy for playerLife
public class Player : MonoBehaviour
{
public RaycastHit hit;
public int gunDamage = 1;
public Zombie zombie;
private float hitForce = 100f;
public float playerLife;
private Vector3 flareLower = new Vector3(0, -0.5f, 0);
void Start()
{
spawnPoints = playerSpawnPoint.GetComponentsInChildren<Transform>();
playerLife = 200;
}
void Update() //T-toggle
{
if (Input.GetButton("Fire1"))
{
LazerBeam();
}
if (reSpawn != lastToggle)
{
ReSpawn();
reSpawn = false;
}
else
lastToggle = reSpawn;
}
public void Life (float damage)
{
playerLife -= damage;
if (playerLife <=0)
{
playerLife = 100;
SceneManager.LoadScene(2);
}
}
}
public class Zombie : MonoBehaviour
{
public int currentHealth;
public Player player;
public PlayerLifeCollider playerCollider;
private int damage;
public void Damage(int damageAmount)
{
currentHealth -= damageAmount;
if (currentHealth <= 0)
{
PlayerLifeCollider.instance.ObjectsInRange.Remove(gameObject);
DestroyZombie();
}
}
public void DestroyZombie()
{
Destroy(gameObject);
// gameObject.SetActive(false);
}
public void DamagePlayer(float damage)
{
player.Life(damage);
}
}
As you said, the problem is that you are not referencing the Player object on your scene, but a prefab one. To avoid that, you can add a Start function to the Zombie script and ask to look for what should be the only Player instance in the scene. For this, you can use the FindObjectOfType function:
void Start()
{
player = FindObjectOfType<Player>();
}
Considering you will only have one Player script in your entire scene, what you can also do is to save in your Player class a static reference to your Player instance.
public class Player : MonoBehaviour
{
private static Player _instance;
public static Player Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<Player>();
}
return _instance;
}
}
// Reset of your class
}
You can then get this reference in your Zombie script:
public class Zombie : MonoBehaviour
{
static Player player;
void Start()
{
if(player == null)
{
player = Player.Instance;
}
}
// Rest of your class content
}
This way, you will only have one call to the FindObjectOfType function instead of once per object using the Zombie script.
public void Damage(int damageAmount)
{
currentHealth -= damageAmount;
print(currentHealth);// will show in terminal if thats what you are asking
if (currentHealth <= 0)
{
PlayerLifeCollider.instance.ObjectsInRange.Remove(gameObject);
DestroyZombie();
}
}
In Unity3D my enemy is not taking damage upon colliding with my projectile explosion.
Although this is not the case as it the health variable is unaffected upon colliding with my projectile explosion.
My Enemy and Barrel classes inherit from Entity which handles the taking of damage (subtracting the damage variable from the health variable). Although only the barrel class is working as intended.
The tags are 100% correct and I would prefer to continue using inheritance so please no suggestions to change the method in which my classes take damage.
the class that Enemy and Barrel inherit from
using UnityEngine;
using System.Collections;
public class Entity : MonoBehaviour {
public float health = 25;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public virtual void takeDamage(float dmg){
health -= dmg;
if (health <= 0){
Destroy(this.gameObject);
}
}
}
Enemy class
using UnityEngine;
using System.Collections;
public class Enemy : Entity {
private NavMeshAgent agent;
public GameObject target;
// Use this for initialization
void Start () {
agent = GetComponent<NavMeshAgent> ();
}
// Update is called once per frame
void Update () {
agent.SetDestination (target.transform.position);
}
}
Barrel class
using UnityEngine;
using System.Collections;
public class Barrel : Entity {
private Transform myTransform;
//Effects
public GameObject barrelExplosion;
public GameObject explosionDamage;
public GameObject explosionSound;
// Use this for initialization
void Start () {
myTransform = this.transform;
}
// Update is called once per frame
void Update () {
}
public override void takeDamage(float dmg){
health -= dmg;
if (health <= 0){
Instantiate(barrelExplosion, myTransform.position, myTransform.rotation);
Instantiate(explosionSound, myTransform.position, myTransform.rotation);
Instantiate(explosionDamage, myTransform.position, myTransform.rotation);
Destroy(this.gameObject);
}
}
}
ExplosionAOE the class that sends the damage
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ExplosionAOE : MonoBehaviour {
public float damage = 100.0f;
public float lifeTime = 0.05f;
private float lifeTimeDuration;
public List<GameObject> damageTargets = new List<GameObject>();
public float radius = 15.0f;
GameManager gameManager;
void Start() {
gameManager = GameObject.FindGameObjectWithTag("GameManager").GetComponent<GameManager>();
//Destroy (this.gameObject, lifeTime);
lifeTimeDuration = Time.time + lifeTime;
transform.GetComponent<SphereCollider>().radius = radius;
}
void Update() {
//Explosion finishes, damage targets and remove AOE field
if (Time.time > lifeTimeDuration) {
foreach (GameObject target in damageTargets) {
if (target != null) {
//Calculate damage based on proximity to centre of explosion
float thisDamage = ((radius - Vector3.Distance(target.transform.position, transform.position)) / radius) * damage;
print(thisDamage);
target.GetComponent<Entity>().takeDamage(thisDamage);
//target.SendMessage("takeDamage", damage); //<< This is not good code. Let's fix this!
}
}
Destroy(this.gameObject);
}
}
void OnTriggerEnter(Collider otherObject) {
if (otherObject.gameObject.tag == "Enemy") {
damageTargets.Add(otherObject.gameObject);
}
if (otherObject.gameObject.tag == "Player") {
Vector3 jumpVector = (otherObject.transform.position - transform.position).normalized;
jumpVector *= 25;
otherObject.GetComponent<CharacterMotor>().SetVelocity(jumpVector);
}
}
}
Sorry this is a bit of a lengthy one and EVERYTHING is tagged correctly so that is not the issue, thanks.
Problem 1.
Use "Debug.Log" everywhere
void OnTriggerEnter(Collider otherObject) {
Debug.Log("in trig");
Debug.Log("otherObject.gameObject.tag is " + otherObject.gameObject.tag);
if (otherObject.gameObject.tag == "Enemy") {
Debug.Log("a");
damageTargets.Add(otherObject.gameObject);
}
if (otherObject.gameObject.tag == "Player") {
Debug.Log("b");
Vector3 jumpVector = (otherObject.transform.position -
transform.position).normalized;
jumpVector *= 25;
otherObject.GetComponent<CharacterMotor>().SetVelocity(jumpVector);
}
}
In particular, in Entity and Enemy.
Questions such as this one are instantly answered by tracking with Debug.Log.
Problem 2.
It's a PITA getting the relationships between triggers, rigidbody, etc.
It's very likely that's a problem here.
http://docs.unity3d.com/Manual/CollidersOverview.html
Go down to the annoying "trigger action matrix" and work from there.
Problem 3.
As a rule, never use the "tags" feature in Unity. (They only added tags to help "hello world" tutorials.)
In practice you use layers everywhere and always:
(Layers are particularly essential in shooting games: every single category needs a layer.)
Problem 4.
The code shown is definitely looking good. Here's some example code not unlike yours for tips.
Trivial example, note the breakaway code (the returns) inside the OnTrigger, you should do that).
Also,
use extentions
everywhere and always in Unity. Quick tutorial
it's the #1 tip if you actually want to work professionally.
public class Enemy:BaseFrite
{
public tk2dSpriteAnimator animMain;
public string usualAnimName;
[System.NonSerialized] public Enemies boss;
[Header("For this particular enemy class...")]
public float typeSpeedFactor;
public int typeStrength;
public int value;
// could be changed at any time during existence of an item!
[System.NonSerialized] public FourLimits offscreen; // must be set by our boss
[System.NonSerialized] public int hitCount; // that's ATOMIC through all integers
[System.NonSerialized] public int strength; // just as atomic!
[System.NonSerialized] public float beginsOnRight;
private bool inPlay; // ie, not still in runup
void Awake()
{
boss = Gp.enemies;
}
..........
protected virtual void Prepare() // write it for this type of sprite
{
ChangeClipTo(bn);
// so, for the most basic enemy, you just do that.
// for other enemy, that will be custom (example, swap damage sprites, etc)
}
void OnTriggerEnter2D(Collider2D c)
{
// we can ONLY touch either Biff or a projectile. to wit: layerBiff, layerPeeps
GameObject cgo = c.gameObject;
if ( gameObject.layer != Grid.layerEnemies ) // if we are not enemy layer....
{
Debug.Log("SOME BIZARRE PROBLEM!!!");
return;
}
if (cgo.layer == Grid.layerBiff) // we ran in to Biff
{
Gp.billy.BiffBashed();
// if I am an enemy, I DO NOT get hurt by biff smashing in to me.
return;
}
if (cgo.layer == Grid.layerPeeps) // we ran in to a Peep
{
Projectile p = c.GetComponent<Projectile>();
if (p == null)
{
Debug.Log("WOE!!! " +cgo.name);
return;
}
int damageNow = p.damage;
Hit(damageNow);
return;
}
Debug.Log("Weirded");
}
public void _stepHit()
{
if ( transform.position.x > beginsOnRight ) return;
++hitCount;
--strength;
ChangeAnimationsBasedOnHitCountIncrease();
// derived classes write that one.
if (strength==0) // enemy done for!
{
Gp.coins.CreateCoinBunch(value, transform.position);
FinalEffect();
if ( Gp.superTest.on )
{
Gp.superTest.EnemyGottedInSuperTest(gameObject);
boss.Done(this);
return;
}
Grid.pops.GotEnemy(Gp.run.RunDistance); // basically re meters/achvmts
EnemyDestroyedTypeSpecificStatsEtc(); // basically re achvments
Gp.run.runLevel.EnemyGotted(); // basically run/level stats
boss.Done(this); // basically removes it
}
}
protected virtual void EnemyDestroyedTypeSpecificStatsEtc()
{
// you would use this in derives, to mark/etc class specifics
// most typically to alert achievements system if the enemy type needs to.
}
private void _bashSound()
{
if (Gp.biff.ExplodishWeapon)
Grid.sfx.Play("Hit_Enemy_Explosive_A", "Hit_Enemy_Explosive_B");
else
Grid.sfx.Play("Hit_Enemy_Non_Explosive_A", "Hit_Enemy_Non_Explosive_B");
}
public void Hit(int n) // note that hitCount is atomic - hence strength, too
{
for (int i=1; i<=n; ++i) _stepHit();
if (strength > 0) // biff hit the enemy, but enemy is still going.
_bashSound();
}
protected virtual void ChangeAnimationsBasedOnHitCountIncrease()
{
// you may prefer to look at either "strength" or "hitCount"
}
protected virtual void FinalEffect()
{
// so, for most derived it is this standard explosion...
Gp.explosions.MakeExplosion("explosionC", transform.position);
}
public void Update()
{
if (!holdMovement) Movement();
if (offscreen.Outside(transform))
{
if (inPlay)
{
boss.Done(this);
return;
}
}
else
{
inPlay = true;
}
}
protected virtual void Movement()
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
}
......
/*
(frite - flying sprite)
The very base for enemies, projectiles etc.
*/
using UnityEngine;
using System.Collections;
public class BaseFrite:MonoBehaviour
{
[System.NonSerialized] public float mpsNow;
// must be set by the boss (of the derive) at creation of the derive instance!
private bool _paused;
public bool Paused
{
set {
if (_paused == value) return;
_paused = value;
holdMovement = _paused==true;
if (_paused) OnGamePause();
else OnGameUnpause();
}
get { return _paused; }
}
protected bool holdMovement;
protected virtual void OnGamePause()
{
}
protected virtual void OnGameUnpause()
{
}
protected string bn;
public void SetClipName(string clipBaseName)
{
bn = clipBaseName;
}
}
Is more easy if in ExplosionAOE/OnTriggerEnter function you call the takeDamage function:
scriptCall = otherObject.GetComponent(EnemyScript);
scriptCall.takeDamage(damage);
There is issue that i facing with two objects and one button. One is cube second is ground when we click on button cube is collide with ground destroy and instantiate again. On Cube collision score is decrement.Also in hierarchy there is Empty game object which name is controller which has method of text score.Score is working fine but i want that when score is 0 then button click does not work and cube is not instantiate.
Cube :
Ground :
Controller :
CubeScript:
public class Cube : MonoBehaviour {
Rigidbody2D body;
void Start () {
body = GetComponent<Rigidbody2D>();
body.isKinematic = true;
}
}
Ground Script:
public class Ground : MonoBehaviour {
private Button button;
private BoxCollider2D collide;
public GameObject object1Clone;
void Start () {
collide = GetComponent<BoxCollider2D>();
collide.isTrigger = true;
button = GameObject.FindGameObjectWithTag ("Button").GetComponent<Button> ();
button.onClick.AddListener (() => Magnetic ());
}
void OnTriggerEnter2D(Collider2D target) {
Destroy (target.gameObject);
Instantiate (object1Clone, new Vector3 (0f, 4.12f, 0f), Quaternion.identity);
}
public void Magnetic(){
GameObject.FindGameObjectWithTag ("Player").GetComponent<Rigidbody2D> ().isKinematic = false;
}
}
ScoreScript:
public class ScoreScript : MonoBehaviour {
public static int Score=1;
void OnTriggerEnter2D(Collider2D target) {
if (Score <=0) {
} else {
Score--;
Controller.instance.SetScore(Score);
}
}
}
Controller:
public class Controller : MonoBehaviour {
public static Controller instance;
public Text scoreText;
void Start () {
scoreText.text = ""+1;
if(instance==null){
instance=this;
}
}
public void SetScore(int score){
scoreText.text =""+score;
}
}
First change the listener registration to this:
button.onClick.AddListener (Magnetic);
this will make it easier to remove the listener.
I will show you two ways of doing it, an easy one and a proper one a bit harder to grasp. So if you don't quite get it, use the first and learn about the second.
Every time you decrease the score, check for it and call for the appropriate action:
public class ScoreScript : MonoBehaviour {
public static int Score=1;
void OnTriggerEnter2D(Collider2D target)
{
Score--;
Controller.instance.SetScore(Score);
if(Score <= 0){
GameObject.Find("ground").GetComponent<Ground>().ClearButtonListener();
}
}
}
And in the Ground component:
public void ClearButtonListener()
{
button.onClick.RemoveListener (Magnetic);
}
Now the second more appropriate way would be to use event and listener
public class ScoreScript : MonoBehaviour, IScoreHandler {
public static int Score=1;
public event Action OnScoreZero = () => {};
void OnTriggerEnter2D(Collider2D target)
{
Score--;
Controller.instance.SetScore(Score);
if(Score <= 0){
OnScoreZero();
}
}
}
public interface IScoreHandler{ event Action OnScoreZero; }
And your listeners listens.
public class Ground : MonoBehaviour {
private Button button;
private BoxCollider2D collide;
public GameObject object1Clone;
private IScoreHandler scoreHandler = null;
void Start () {
scoreHandler = GameObject.Find("Score").GetComponent<IScoreHandler>();
if(scoreHandler != null){
scoreHandler.OnScoreZero += ClearButtonListener;
}
collide = GetComponent<BoxCollider2D>();
collide.isTrigger = true;
button = GameObject.FindGameObjectWithTag ("Button").GetComponent<Button> ();
button.onClick.AddListener (Magnetic);
}
void OnDestroy(){
if(scoreHandler != null){
scoreHandler.OnScoreZero -= ClearButtonListener;
}
}
}
Thanks to interface and event, your class is no more relying on another class but on an interface which makes it more flexible and scalable.
You need to set the field interactable of the UnityEngine.UI.Button object to false, see http://docs.unity3d.com/ScriptReference/UI.Button.html, i.e. use
void OnTriggerEnter2D(Collider2D target) {
if (Score <=0) {
/* disable the button */
GameObject.FindGameObjectWithTag ("Button").GetComponent<Button>().interactable = false;
}
in your ScoreScript.cs.