Destroying exact object - c#

I am creating a small 2d game in which you need to survive. Each tree has its own strength = 5. When the player collides and presses the left mouse button then strength is -1 and player wood stat is +1. when the tree strength is equal or less than 0 then the tree is destroyed. Here is my code : (Question is after the code)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Stats: MonoBehaviour
{
//Player Stats
public float hp = 100;
public float wood = 0;
//Tree stats
public float treeLogStrenth = 5;
//Text
public Text woodText;
void Start ()
{
woodText.text = "0";
}
void Update ()
{
woodText.text = wood.ToString();
if (treeLogStrenth <= 0)
{
Destroy(GetComponent<PlayerCollisions>().;
}
}
}
here is another code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCollisions: MonoBehaviour
{
public void OnCollisionStay2D (Collision2D collisionInfo)
{
if (collisionInfo.gameObject.tag == "Tree" && Input.GetMouseButtonDown(0))
{
string treeName = collisionInfo.gameObject.name;
GetComponent<Stats>().wood += 1;
GetComponent<Stats>().treeLogStrenth -= 1;
}
}
}
MY QUESTION : How to make instead of creating all the time another game object for each tree and destroying it, only like one simple code that will do it. please help (UNITY NEWEST VERSION)

So to clear things up: Stats should be attached to the player, right?
You should not do things every frame in Update but rather event driven like
public class Stats : MonoBehaviour
{
// You should never allow your stats to be set via public fields
[SerializeField] private float hp = 100;
[SerializeField] private float wood = 0;
// if you need to read them from somewhere else you can use read-only properties
public float HP => hp;
public float Wood => wood;
[SerializeField] private Text woodText;
private void Start ()
{
woodText.text = "0";
}
public void AddWood(int amount)
{
wood += amount;
woodText.text = wood.ToString();
}
}
Then each tree should rather have its own component instance attached like e.g.
public class Tree : MonoBehaviour
{
[SerializeField] private float treeLogStrenth = 5;
public void HandleClick(Stats playerStats)
{
// if this tree has wood left add it to the player stats
if(treeLogStrength > 0)
{
playerStats.AddWood(1);
treeLogStrenth -= 1;
}
// destroy this tree when no wood left
if (treeLogStrenth <= 0)
{
Destroy(gameObject);
}
}
}
and then finally also attached to the Player
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store the currently collided tree in order to reuse it
private Tree currentlyCollidedTree;
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// Get the Tree component of the tree object you are currently colliding with
// but only once and store the reference in order to reuse it
if(!currentlyCollidedTree) currentlyCollidedTree= collisionInfo.gameObject.GetComponent<Tree>();
// tell the tree to handle a click and pass in your stats reference
currentlyCollidedTree.HandleClick(stats);
}
}
// reset the currentlyCollidedTree field when not colliding anymore
private void OnCollisionExit2D()
{
currentlyCollidedTree = null;
}
}
Well yes there would be an alternative where not every tree needs its own component instance but I would recommend to not use it actually!
You could make your player remember which tree he already clicked in something like
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store all trees we ever clicked on in relation to the according available wood
private Dictionary<GameObject, int> encounteredTrees = new Dictionary<GameObject, int>();
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// did we work on this tree before?
if(encounteredTrees.Contains(collisionInfo.gameObject))
{
// if so gain one wood and remove one from this tree
stats.AddWood(1);
encounteredTrees[collisionInfo.gameObject] -= 1;
// destroy the tree if no wood available and remove it from the dictionary
if(encounteredTrees[collisionInfo.gameObject] <= 0)
{
encounteredTrees.RemoveKey(collisionInfo.gameObject);
Destroy(collisionInfo.gameObject);
}
}
else
{
// the first time we work this tree gain one wood and add
// the tree as new entry to the dictionary with 4 wood left
stats.AddWood(1);
encounteredTrees.Add(collisionInfo.gameObject, 4);
}
}
}
}
this however limits you extremely and it is not possible anymore that you have e.g. different Tree prefabs with different amount of available wood ...

Related

Unity android. Loading Function after killing an app doesn't work properly (Object Reference not set to an instance of an object)

I am at begginner level with unity.
I have Load() function that goes off in OnApplicationPause(false). It works fine if I block the screen or minimalise app, and come back to it. However, when I kill it, I get error and the data doesnt get loaded.
Below is the script attached to the GameObject "SaveManager"
using System.Collections.Generic;
using UnityEngine;
using System;
public class SaveManager : MonoBehaviour
{
public GameObject ZwierzetaGroup;
public GameObject JedzeniaGroup;
public GameObject PrzedmiotyGroup;
public List<GameObject> zwierzeta_sprites;
public List<GameObject> jedzenia_sprites;
public List<GameObject> przedmioty_sprites;
public static DateTime oldDate;
Camera mainCamera;
public SaveState saveState;
void Start()
{
mainCamera = Camera.main;
FillArrays();
}
public void Save()
{
Debug.Log("Saving.");
SaveSpriteArray("zwierze", zwierzeta_sprites);
SaveSpriteArray("przedmiot", przedmioty_sprites);
SaveSpriteArray("jedzenie", jedzenia_sprites);
PlayerPrefs.SetInt("pieniazki", saveState.GetPieniazki());
PlayerPrefs.SetInt("HayAmount", saveState.GetHayAmount());
PlayerPrefs.SetInt("HayMax", saveState.GetHayMax());
PlayerPrefs.SetInt("FruitAmount", saveState.GetFruitAmount());
PlayerPrefs.SetInt("FruitMax", saveState.GetFruitMax());
//time:
PlayerPrefs.SetString("sysString", System.DateTime.Now.ToBinary().ToString());
PlayerPrefs.SetInt("First", 1);
}
public void SaveSpriteArray(string saveName, List<GameObject> sprites)
{
for (int i = 0; i < sprites.Count; i++)
{
if (sprites[i].activeSelf)
{
PlayerPrefs.SetInt(saveName + i, 1);
}
else
{
PlayerPrefs.SetInt(saveName + i, 0);
}
}
}
public void Load()
{
Debug.Log("Loading.");
//wczytanie czasu:
long temp = Convert.ToInt64(PlayerPrefs.GetString("sysString"));
oldDate = DateTime.FromBinary(temp);
Debug.Log("oldDate: " + oldDate);
//wczytywanie aktywnych sprite'ow
LoadSpriteArray("zwierze", zwierzeta_sprites);
LoadSpriteArray("przedmiot", przedmioty_sprites);
LoadSpriteArray("jedzenie", jedzenia_sprites);
saveState.SetPieniazki(PlayerPrefs.GetInt("pieniazki"));
saveState.SetHayAmount(PlayerPrefs.GetInt("HayAmount"));
saveState.SetHayMax(PlayerPrefs.GetInt("HayMax"));
saveState.SetFruitAmount(PlayerPrefs.GetInt("FruitAmount"));
saveState.SetFruitMax(PlayerPrefs.GetInt("FruitMax"));
mainCamera.GetComponent<UpdateMoney>().MoneyUpdate();
}
public void LoadSpriteArray(string saveName, List<GameObject> sprites)
{
for (int i = 0; i < sprites.Count; i++)
{
if (PlayerPrefs.GetInt(saveName + i) == 1)
{
sprites[i].SetActive(true);
}
else
{
sprites[i].SetActive(false);
}
}
}
private void FillArrays()
{
//find children
foreach (Transform child in ZwierzetaGroup.transform)
{
zwierzeta_sprites.Add(child.gameObject);
}
foreach (Transform child in PrzedmiotyGroup.transform)
{
przedmioty_sprites.Add(child.gameObject);
}
foreach (Transform child in JedzeniaGroup.transform)
{
jedzenia_sprites.Add(child.gameObject);
}
}
}
Below is a chunk of script attached to the main camera (probably a mistake). SaveManager GameObject with Script is attached to this one in inspector. This script is pretty big, so I'll skip the parts that I don't find relevant.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ManageEncouters: MonoBehaviour
{
DateTime currentDate;
public int First;
public SaveState saveState;
public SaveManager saveManager;
public HayBar hayBar;
public FruitBar fruitBar;
public GameObject[] jedzenia_sprites;
void Start()
{
}
void OnApplicationPause(bool pauseStatus)
{
if (!pauseStatus)
{
currentDate = System.DateTime.Now;
//Sprawdzanie czy jest to piersze uruchomienie gry (brak zapisu)
First = PlayerPrefs.GetInt("First");
if (First == 0)
{
Debug.Log("First time in app.");
RandomiseAnimals();
SaveManager.oldDate = currentDate;
hayBar.SetHayMax(1);
hayBar.SetHay(0);
fruitBar.SetFruitMax(1);
fruitBar.SetFruit(0);
saveState.SetPieniazki(100);
this.GetComponent<UpdateMoney>().MoneyUpdate();
}
else
{
Debug.Log("Not the first time in app.");
saveManager.Load();
}
if (TimeInSeconds(currentDate, SaveManager.oldDate) > 12)
{
Debug.Log("It's been more than 12 seconds sience last time.");
EatFood(currentDate, SaveManager.oldDate);
RandomiseAnimals();
}
else
{
Debug.Log("It's been less than 12 seconds sience last time.");
}
}
if (pauseStatus)
{
saveManager.Save();
}
}
private int TimeInSeconds(DateTime newD, DateTime oldD)
{
TimeSpan difference = newD.Subtract(oldD);
int seconds = (int)difference.TotalSeconds;
return seconds;
}
}
Below is the error I get, I don't know how to copy the text, so it's an Image.
I'm pretty sure that what you have here is a timing issue.
OnApplicationPause
Note: MonoBehaviour.OnApplicationPause is called as a GameObject starts. The call is made after Awake. Each GameObject will cause this call to be made.
So to me this sounds like it might be called when your SaveManager is not yet initialized, in particular the mainCamera.
I think you could already solve the issue by moving the initialization into Awake instead
private void Awake()
{
mainCamera = Camera.main;
FillArrays();
}
In general my little thumb rule is
use Awake wherever possible. In particular initialize everything where you don't depend on other scripts (initialize fields, use GetComponent, etc)
use Start when you need other scripts to be initialized already (call methods on other components, collect and pass on instances of some prefabs spawned in Awake, etc)
This covers most of cases. Where this isn't enough you would need to bother with the execution order or use events.

How can I tell when a number has finished increasing?

I feel like I'm missing some easy solution here, but I'm stuck on this. I'm calculating a score based on how far the player travels until they hit a building at the end of the course. The destruction score is separate from the distance score, and it increments until all of the building pieces have come to a rest.
I have an animation I want to play to add the distance score to the total destruction score and give the player's overall score, but I need the animation to trigger once the destruction score has stopped increasing. Right now, each piece of the building has code that checks if its moving and increments the score while true.
public class SkiLodgeScoreTracker : MonoBehaviour
{
Rigidbody rb;
private GameObject[] score;
private void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
score = GameObject.FindGameObjectsWithTag("Score");
}
private void Update()
{
//check if lodgePiece is moving, while it is, add to Score object
if(rb.velocity.magnitude >= 2f && !rb.isKinematic)
{
score[0].GetComponent<SkiScore>().addToSkiScore(2f);
}
}
}
Here's where I want to have the animation trigger once that score has stopped (this was another attempt I made, the logic doesn't work)
public Animator moveScore;
...
if(skiScore > 0)
{
previousScore2 = previousScore1;
previousScore1 = skiScore;
if(previousScore1 == previousScore2 && !scoreMoved)
{
moveScore.SetTrigger("EndCourse");
addScoreToSkiScore();
}
}
public void addScoreToSkiScore()
{
scoreMoved = true;
for(float i = score; i>0; i--)
{
skiScore += 1;
}
}
I wanted to grab the score on one frame and see if it equals the score on the next frame and, if so, then trigger the animation, but I feel like that's not a valid option.
Any ideas?
In SkiScore, keep track of how many pieces are moving, and when a piece stops and the count becomes 0, do your thing:
public class SkiScore : MonoBehaviour
{
int movingCount;
void Start()
{
ResetMovingCount();
/* ... */
}
public void ResetMovingCount() {movingCount = 0;} // call as needed
public void OnStartedMoving() {++movingCount;}
public void OnStoppedMoving()
{
if (--movingCount == 0) OnNoneMoving();
}
void OnNoneMoving() {/* do the thing */}
/* ... */
}
For each piece, use a flag to remember if it has moved recently and if it has, and it's no longer moving, let your score manager know:
public class SkiLodgeScoreTracker : MonoBehaviour
{
Rigidbody rb;
private GameObject[] score;
// flag used to recognize newly stopped movement
bool recentlyMoving;
// cache for GetComponent
SkiScore mySkiScore
private void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
score = GameObject.FindGameObjectsWithTag("Score");
// GetComponent is expensive, try not to call it in Update unless necessary
mySkiScore = score[0].GetComponent<SkiScore>();
}
private void Update()
{
//check if lodgePiece is moving, while it is, add to Score object
if(rb.velocity.magnitude >= 2f && !rb.isKinematic)
{
mySkiScore.addToSkiScore(2f);
recentlyMoving = true;
mySkiScore.OnStartedMoving();
}
else if (recentlyMoving)
{
recentlyMoving = false;
mySkiScore.OnStoppedMoving();
}
}
}

Unity3D playing sound when Player collides with an object with a specific tag

I using Unity 2019.2.14f1 to create a simple 3D game.
In that game, I want to play a sound anytime my Player collides with a gameObject with a specific tag.
The MainCamera has an Audio Listener and I am using Cinemachine Free Look, that is following my avatar, inside the ThridPersonController (I am using the one that comes on Standard Assets - but I have hidden Ethan and added my own character/avatar).
The gameObject with the tag that I want to destroy has an Audio Source:
In order to make the sound playing on the collision, I started by creating an empty gameObject to serve as the AudioManager, and added a new component (C# script) to it:
using UnityEngine.Audio;
using System;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public Sound[] sounds;
// Start is called before the first frame update
void Awake()
{
foreach (Sound s in sounds)
{
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
}
}
// Update is called once per frame
public void Play (string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.source.Play();
}
}
And created the script Sound.cs:
using UnityEngine.Audio;
using UnityEngine;
[System.Serializable]
public class Sound
{
public string name;
public AudioClip clip;
[Range(0f, 1f)]
public float volume;
[Range(.1f, 3f)]
public float pitch;
[HideInInspector]
public AudioSource source;
}
After that, in the Unity UI, I went to the Inspector in the gameObject AudioManager, and added a new element in the script that I named: CatchingPresent.
On the Third Person Character script, in order to destroy a gameObject (with a specific tag) when colliding with it, I have added the following:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
}
}
It is working properly as that specific object is disappearing on collision. Now, in order to play the sound "CatchingPresent" anytime the Player collides with the object with the tag, in this case, Present, I have tried adding the following to the if in the OnCollisionEnter:
FindObjectOfType<AudioManager>().Play("CatchingPresent");
But I get the error:
The type or namespace name 'AudioManager' could not be found (are you
missing a using directive or an assembly reference?)
AudioManager.instance.Play("CatchingPresent");
But I get the error:
The name 'AudioManager' does not exist in the current context
As all the compiler errors need to be fixed before entering the Playmode, any guidance on how to make the sound playing after a collision between the player and the gameObject with the tag Present is appreciated.
Edit 1: Assuming that it is helpful, here it goes the full ThirdPersonUserControl.cs:
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.CrossPlatformInput;
namespace UnityStandardAssets.Characters.ThirdPerson
{
[RequireComponent(typeof (ThirdPersonCharacter))]
public class ThirdPersonUserControl : MonoBehaviour
{
public Text countText;
public Text winText;
private int count;
private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
private Transform m_Cam; // A reference to the main camera in the scenes transform
private Vector3 m_CamForward; // The current forward direction of the camera
private Vector3 m_Move;
private bool m_Jump; // the world-relative desired move direction, calculated from the camForward and user input.
private void Start()
{
count = 20;
SetCountText();
winText.text = "";
// get the transform of the main camera
if (Camera.main != null)
{
m_Cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
// we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
}
// get the third person character ( this should never be null due to require component )
m_Character = GetComponent<ThirdPersonCharacter>();
}
private void Update()
{
if (!m_Jump)
{
m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
}
}
// Fixed update is called in sync with physics
private void FixedUpdate()
{
// read inputs
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
bool crouch = Input.GetKey(KeyCode.C);
// calculate move direction to pass to character
if (m_Cam != null)
{
// calculate camera relative direction to move:
m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
m_Move = v*m_CamForward + h*m_Cam.right;
}
else
{
// we use world-relative directions in the case of no main camera
m_Move = v*Vector3.forward + h*Vector3.right;
}
#if !MOBILE_INPUT
// walk speed multiplier
if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif
// pass all parameters to the character control script
m_Character.Move(m_Move, crouch, m_Jump);
m_Jump = false;
}
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
//FindObjectOfType<AudioManager>().Play("CatchingPresent");
AudioManager.instance.Play("CatchingPresent");
}
}
void SetCountText()
{
countText.text = "Missing: " + count.ToString();
if (count == 0)
{
winText.text = "You saved Christmas!";
}
}
}
}
Edit 2: Hierarchy in Unity:
Reformulated the approach that I was following and solved the problem by simply adding an Audio Source to the ThirdPersonController (with the AudioClip that I wanted to call) and added GetComponent<AudioSource>().Play(); to the if statement as it follows:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
GetComponent<AudioSource>().Play();
}
}
Importing your scripts myself works without any issues when using FindObjectOfType<AudioManager>().Play("CatchingPresent");. Try reimporting your scripts from the editor (right click in the project folder > reimport all. this might take a while depending on the size of your project)
to use AudioManager.instance.Play("CatchingPresent"); you would first need to create a static variable that holds instance like this (this only works as a singleton, and will break if multiple AudioManager's are in the scene):
public class AudioManager : MonoBehaviour
{
//Create a static AudioManager that will hold the reference to this instance of AudioManager
public static AudioManager Instance;
public Sound[] sounds;
//Assign Instance to the instance of this AudioManager in the constructor
AudioManager()
{
Instance = this;
}
// Rest of the AudioManager code
}
Doing it like this, and using the rest of your code also works for me.

Unity 3D Attaching Score Display Script to prefab

I was following Unity 3d tutorial on the Learn Unity website, but here is the thing I wanted to do things a bit differently. It worked out well at start but in the end this turned out to be a bad decision and now I manually need to attach the script to every pickable object.
Here is my code:
Note: What it does is rotate the Pickups and display the score when the pickups collide with player ball.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PickUps : MonoBehaviour {
public Vector3 Rotate;
private int Score;
public Text ScoreGUI;
private void Start()
{
Rotate = new Vector3(0, 25, 0);
Score = 0;
DisplayScore();
}
void Update () {
transform.Rotate(Rotate*Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Ball"))
{
Destroy(this.gameObject);
Score = Score + 1;
DisplayScore();
}
}
void DisplayScore()
{
ScoreGUI.text = "SCORE " + Score.ToString();
}
}
Problem:
It works yes but I need to manually attach the text (under canvas) to every pickup object which is exhausting and not a good thing to do.
What I want To achieve:
Like in the tutorials mostly they use prefabs in this kind of work (I think), problem is I can attach the text to the pickups (objects/biscuits) in the current scene but I cannot drag and attach the text To the prefab of biscuits I made the text just wont attach in its blank for "Text".
You shouldn't change the score Text directly. Use a Controller to make the bridge instead. I would do something like this:
Put this script somewhere in your scene:
public class ScoreManager : Singleton<ScoreManager>
{
private int score = 0;
// Event that will be called everytime the score's changed
public static Action<int> OnScoreChanged;
public void SetScore(int score)
{
this.score = score;
InvokeOnScoreChanged();
}
public void AddScore(int score)
{
this.score += score;
InvokeOnScoreChanged();
}
// Tells to the listeners that the score's changed
private void InvokeOnScoreChanged()
{
if(OnScoreChanged != null)
{
OnScoreChanged(score);
}
}
}
This script attached in the Text game object:
[RequireComponent(typeof(Text))]
public class ScoreText : MonoBehaviour
{
private Text scoreText;
private void Awake()
{
scoreText = GetComponent<Text>();
RegisterEvents();
}
private void OnDestroy()
{
UnregisterEvents();
}
private void RegisterEvents()
{
// Register the listener to the manager's event
ScoreManager.OnScoreChanged += HandleOnScoreChanged;
}
private void UnregisterEvents()
{
// Unregister the listener
ScoreManager.OnScoreChanged -= HandleOnScoreChanged;
}
private void HandleOnScoreChanged(int newScore)
{
scoreText.text = newScore.ToString();
}
}
And in your PickUps class:
void DisplayScore()
{
ScoreManager.Instance.SetScore(Score); // Maybe what you need is AddScore to not
// reset the value everytime
}
A simple singleton you can use (you can find more complete ones on the internet):
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null) Debug.LogError("Singleton of type " + typeof(T).ToString() + " not found in the scene.");
}
return instance;
}
}
}
But be careful, the singleton pattern can be a shot in the foot if not used correctly. You should only it them moderately for managers.

How to achieve awareness of "kill" events in a scene

I have been doing a RPG game in Unity with C # and when doing a system of quests, specifically those of killing a certain number of enemies, I found the problem of having 3 enemies in the scene and being the target of the quest: Kill 3 enemies. If I kill them before activating the quest and later active the quest does not give me the reward (in this case experience). How can I tell the enemies and make that if the quest detects that I have already killed the necessary enemies to get the quest give me the reward equally?
Here the two needed scripts i think:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestObject : MonoBehaviour {
public int questNumber;
public QuestManager qManager;
public string startText;
public string endText;
public bool isItemQuest;
public string targetItem;
public bool isEnemyQuest;
public string targetEnemy;
public int enemiesToKill;
private int enemyKillCount;
private PlayerStats playerStats;
public int EXPToGive;
void Start () {
playerStats = FindObjectOfType <PlayerStats> ();
}
void Update () {
if (isItemQuest) {
if (qManager.itemCollected == targetItem) {
qManager.itemCollected = null;
EndQuest ();
}
}
if (isEnemyQuest) {
if (qManager.enemyKilled == targetEnemy) {
qManager.enemyKilled = null;
enemyKillCount++;
}
if (enemyKillCount >= enemiesToKill) {
EndQuest ();
}
}
}
public void StartQuest (){
qManager.ShowQuestText (startText);
}
public void EndQuest (){
qManager.ShowQuestText (endText);
playerStats.AddEXP (EXPToGive);
qManager.questCompleted [questNumber] = true;
gameObject.SetActive (false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public int startingHealth;
public int currentHealth;
public GameObject damageBurst;
private PlayerStats playerStats;
public int EXPToGive;
public string enemyQuestName;
private QuestManager qManager;
void Start ()
{
// Setting up the references.
//anim = GetComponent <Animator> ();
//enemyAudio = GetComponent <AudioSource> ();
//enemyMovement = GetComponent <EnemyMovement> ();
//enemyAttacking = GetComponentInChildren <EnemyAttack> ();
// Set the initial health of the player.
currentHealth = startingHealth;
playerStats = FindObjectOfType <PlayerStats> ();
qManager = FindObjectOfType <QuestManager> ();
}
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
public void TakeDamage (int amountDamage)
{
// Reduce the current health by the damage amount.
currentHealth -= amountDamage;
Instantiate (damageBurst, transform.position, transform.rotation);
}
public void SetMaxHelth () {
currentHealth = startingHealth;
}
}
One Aproach would be to create some type of "WorldManager" which counts every Enemy which has been slain. And when Starting a quest this quest could check the WorldManagers kill count and add it to it's own count.
public void StartQuest (){
qManager.ShowQuestText (startText);
this.enemyKillCount += worldManager.GetKillCount();
}
In your enemy class you have to add a kill to your worldManager.
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
this.worldManager.AddKill(this)
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
Alternative:
Make your QManager be aware of every kill in a Scene.
You can achieve this through many ways.
One of them is passing your EnemyObject an reference of your Qmanager and do the same as with the "WorldManager" provided above, or you use Messaging and fire a Message targeting the QManager when an enemy is slain.
Alternative 2:
Throw an Event when an enemy has been slain and subscribe to it on your QManager/WorldManager. This way u can reuse your enemy class in every game. From my point of view static dependencies are evil, but there are many discussions and SO and everywhere on the internet about that.
You can several approach. The most straight-forward is to use static.
The purpose of static is for the variable/method to belong to the class instead of an instance of the class.
In your case, you want each enemy to have its own health, this cannot be static.
And you want to count how many instances there are in the scene from the class. So static is fine.
public class Enemy:MonoBehaviour
{
private static int enemyCount = 0;
public static int EnemyCount {get{ return enemyCount;} }
public event Action<int> RaiseEnemyDeath;
public static void ResetEnemyCount(){
enemyCount = 0;
}
private int health;
public void Damage(int damage)
{
CheckForDamage(); // here you check that damage is not neg or too big...
this.health -= damage;
if(this.health <= 0)
{
OnDeath();
}
}
void OnActivate()
{
enemyCount++;
this.health = 20;
}
void OnDeath()
{
enemyCount--;
RaiseEnemyDeath(enemyCount); // Should check for nullity...
}
}
This one is fairly simple. The first part is all static and is relevant to the class. The second part is relevant to the instance. If you use a pool of enemy and then reuse the same instance multiple times, the OnActivate method is called when you make the enemy alive in the scene (it may have been there for a while as inactive). Then when the health is down, kill the enemy (there are not all the required actions there...) and trigger the event.
Using the public static property, you can know what is the enemy count from a GameManager (Enemy should not affect the gameplay, only takes care of the enemy).
public class GameManager:MonoBehaviour
{
void Start()
{
Enemy.RaiseEnemyDeath += Enemy_RaiseEnemyDeath;
}
void Enemy_RaiseEnemyDeath(int count)
{
if(count < 0){ // End of level }
// You can also access enemyCount
int count = Enemy.EnemyCount;
}
}
The good point of using this principle is that Enemy has no clue about GameManager and can be reused in another game without any modification. The GameManager is a higher level entity and knows about it.

Categories