Okay so before I begin, Yes I have looked online to find this answer. I've followed advice on multiple other questions, gone through the unity documentation, and done more than a few web searches and nothing I've found so far has solved the error. I'm sure someone will take one look at this and know immediately what's wrong, but as for me, I just can't find it.
Now that that's out of the way Here's the problem. I'm making a Breakout clone and I had everything done, everything working properly. I've got one static class that takes care of the scoring and score related variables, so that other scripts can access them easily. I wanted to practice some basic saving and loading with PlayerPrefs, so I added something for highscores. It's pretty much independent of the other classes, but once I finished that, I started getting a Null Reference Exception in a script that has been done for hours, and was working fine.
I appreciate any help you might have, and any tips for preventing this kind of error the next time around. Sorry It's such a long question.
Here's the full error:
NullReferenceException: Object reference not set to an instance of an object
MenuManager.ActivateLose () (at Assets/Scripts/MenuScripts/MenuManager.cs:31)
Scoring.CheckGameOver () (at Assets/Scripts/Scoring.cs:64)
Scoring.LifeLost () (at Assets/Scripts/Scoring.cs:51)
DeadZone.OnTriggerEnter2D (UnityEngine.Collider2D other) (at Assets/Scripts/DeadZone.cs:22)
And here are the three scripts listed in said error:
using UnityEngine;
using System.Collections;
public class DeadZone : MonoBehaviour
{
public GameObject ballPrefab;
public Transform paddleObj;
GameObject ball;
void Update ()
{
ball = GameObject.FindGameObjectWithTag("Ball");
}
void OnTriggerEnter2D(Collider2D other)
{
//if the object that entered the trigger is the ball
if(other.tag == "Ball")
{
Scoring.LifeLost();
//destroy it, and instantiate a new one above where the paddle currently is
Destroy(ball);
paddleObj.transform.position = new Vector2(0, -2.5f);
(Instantiate(ballPrefab, new Vector2(paddleObj.transform.position.x, paddleObj.transform.position.y + 0.3f), Quaternion.identity) as GameObject).transform.parent = paddleObj;
}
}
}
using UnityEngine;
using System.Collections;
public static class Scoring
{
public static GameObject scoreValue;
public static TextMesh scoreText;
public static int score;
static int multiplier = 0;
static int consecutiveBreaks = 0;
static int lives = 3;
static int totalBricks;
static int remainingBricks;
public static GameObject menuManagerObj;
public static MenuManager menuManager = new MenuManager();
static void Awake()
{
scoreValue = GameObject.FindGameObjectWithTag("Scoring");
scoreText = scoreValue.GetComponent<TextMesh>();
menuManagerObj = GameObject.FindGameObjectWithTag("MenuManager");
//menuManager = menuManagerObj.GetComponent<MenuManager>();
}
public static void BrickDestroyed()
{
if(scoreValue == null && scoreText == null)
{
scoreValue = GameObject.FindGameObjectWithTag("Scoring");
scoreText = scoreValue.GetComponent<TextMesh>();
}
remainingBricks--;
consecutiveBreaks++;
multiplier = 1 + (consecutiveBreaks % 5);
score += 10 * multiplier;
CheckGameOver();
scoreText.text = score + "";
}
public static void LifeLost()
{
consecutiveBreaks = 0;
multiplier = 1;
score -= 100;
lives--;
LivesDisplay.SetLives(lives);
CheckGameOver();
scoreText.text = score + "";
}
public static void SetBrickCount(int brickCount)
{
totalBricks = brickCount;
remainingBricks = totalBricks;
}
public static void CheckGameOver()
{
//lose condition
if(lives < 0) menuManager.ActivateLose();
//win condition
if(remainingBricks == 0) menuManager.ActivateWin();
}
}
using UnityEngine;
using System.Collections;
public class MenuManager : MonoBehaviour
{
public GameObject winMenu;
public GameObject loseMenu;
public GameObject pauseMenu;
public HighScoreManager highScores;
bool isGamePaused = true;
void Awake()
{
winMenu = GameObject.FindGameObjectWithTag("Win");
loseMenu = GameObject.FindGameObjectWithTag("Lose");
pauseMenu = GameObject.FindGameObjectWithTag("Pause");
}
public void ActivateWin()
{
Time.timeScale = 0f;
winMenu.transform.position = new Vector3(0, 0, -1);
highScores.CompareToHighScores(Scoring.score);
}
public void ActivateLose()
{
Time.timeScale = 0f;
loseMenu.transform.position = new Vector3(0, 0, -1);
}
void ActivatePause()
{
if(isGamePaused)
{
Time.timeScale = 0f;
pauseMenu.transform.position = new Vector3(0, 0, -1);
}
else
{
Time.timeScale = 1f;
pauseMenu.transform.position = new Vector3(35, 0, -1);
}
isGamePaused = !isGamePaused;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Escape))
{
ActivatePause();
}
}
}
The problem is that Scoring is not a MonoBehaviour so the Awake method never gets called. You can try to initialize the fields in a static constructor
static Scoring()
{
scoreValue = GameObject.FindGameObjectWithTag("Scoring");
scoreText = scoreValue.GetComponent<TextMesh>();
menuManagerObj = GameObject.FindGameObjectWithTag("MenuManager");
//menuManager = menuManagerObj.GetComponent<MenuManager>();
}
or make the Awake method public and call it from another MonoBehaviour.
Related
I have a ScriptableObject called WeaponInfo that keeps track of different information about weapons that doesn't change (magazine max, rate of fire, degradable bool, etc.) and this class also has a reference to a base weapon Prefab that inherits from Monobehaivior. This prefab is what is instantiated and then given a reference to the WeaponInfo scriptable object. My problem with this setup is as follows:
I want the weapon to have a durability value that decreases with each use. I can't put this in the ScriptableObject since ScriptableObjects data is "static", and changing it is not a good idea. I also can't put it inside the GameObject Prefab itself, because every time the player unequips then reequips the weapon, the durability value is reset to its default state.
I looked around the internet for solutions, but no one has a similar example to my setup where the weapon is unequipped by deleting it from the scene. I also want the ability to store this weapon in a chest. Originally I was gonna do this by storing a reference to the ScriptableObject, but I won't be able to save the durability.
What is the best approach to solving this issue? If anyone wants, I can share code, but I wanted to keep the post simple since my issue is conceptual more than it is syntax.
EDIT: Here is the code for my WeaponInfo class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Weapon Item", menuName = "Inventory/Weapon Item")]
public class WeaponItem : Item
{
[Header("Game Object Info")]
public GameObject WeaponPrefab;
public Vector3 DefaultRotation;
public Vector3 DefaultScale;
[SerializeField]
public BulletItem Bullet;
[Header("Weapon Info")]
public bool Automatic;
[SerializeField]
public int Magazine_Max;
[SerializeField]
public int Ammo_Max;
[SerializeField]
public int Reloading_Time;
[SerializeField]
public float ROF;
public bool degradable;
public override void Use()
{
base.Use();
//oldWeapon is of type WeaponItem
var oldWeapon = WeaponSwapper.Instance.SwapWeapon(this);
Inventory.Instance.AddItem(oldWeapon);
Inventory.Instance.RemoveItem(this);
}
}
And here's the code attached to my weapon prefab:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AmmoWeapon : MonoBehaviour
{
[System.NonSerialized]
public WeaponItem WeaponInfo;
private Inventory inventory;
protected float reloadingTimer;
protected bool reloading;
[SerializeField]
public int Magazine_Current;
[SerializeField]
public int Ammo_Current;
public int durability;
private float elapsedTime = 0;
void Start()
{
inventory = Inventory.Instance;
inventory.OnInventoryChangedCallback += UpdateAmmo;
reloadingTimer = 0;
reloading = false;
LoadAmmo();
}
//this method loads a clip into the magazine
private void LoadAmmo()
{
var totalAmmo = inventory.GetCount(WeaponInfo.Bullet);
if(totalAmmo >= WeaponInfo.Magazine_Max)
{
Magazine_Current = WeaponInfo.Magazine_Max;
Ammo_Current = totalAmmo - WeaponInfo.Magazine_Max;
} else
{
Magazine_Current = totalAmmo;
Ammo_Current = 0;
}
}
//callback for whenever ammo is added to the inventory.. also used to reflect the ammo in the UI
private void UpdateAmmo()
{
var totalAmmo = inventory.GetCount(WeaponInfo.Bullet);
Ammo_Current = totalAmmo;
if (Ammo_Current <= Magazine_Current)
{
Magazine_Current = Ammo_Current;
Ammo_Current = 0;
}
else
Ammo_Current -= Magazine_Current;
}
void Update()
{
//Automatic
if (Input.GetMouseButton(0) && WeaponInfo.Automatic)
{
Fire();
}
//Manual
if (Input.GetMouseButtonDown(0) && !WeaponInfo.Automatic)
{
Fire();
}
//Set reloading flag after a certain amount of time passes
if (reloading)
{
reloadingTimer += Time.deltaTime;
if (reloadingTimer > WeaponInfo.Reloading_Time)
{
reloadingTimer = 0;
reloading = false;
}
}
elapsedTime += Time.deltaTime;
Mathf.Clamp(elapsedTime, 0, WeaponInfo.ROF);
}
protected void Reload()
{
if (reloading)
return;
reloading = true;
reloadingTimer = 0;
LoadAmmo();
}
protected void Fire()
{
if (Magazine_Current == 0)
Reload();
if (!CanFire())
return;
var target = Camera.main.ScreenToWorldPoint(Input.mousePosition);
var bulletPrefab =
Instantiate(WeaponInfo.Bullet.BulletPrefab, transform.position, Quaternion.identity);
var bulletScript = bulletPrefab.GetComponent<Bullet>();
var spriteRenderer = bulletPrefab.GetComponent<SpriteRenderer>();
spriteRenderer.sprite = WeaponInfo.Bullet.Sprite;
bulletScript.BulletInfo = WeaponInfo.Bullet;
bulletScript.SetDirection(target - transform.position);
elapsedTime = 0;
Magazine_Current--;
inventory.RemoveItem(WeaponInfo.Bullet);
//ISSUE IS HERE. durability is reset to initial value whenever this gameobject is initialized again
if (WeaponInfo.degradable)
{
durability--;
if (durability == 0)
{
MessageHandler.Instance.DisplayMessage(WeaponInfo.ItemName + " degraded completely.");
Destroy(gameObject);
}
}
}
protected bool CanFire()
{
if (reloading || elapsedTime < WeaponInfo.ROF || Magazine_Current == 0 || !inventory.Contains(WeaponInfo.Bullet))
return false;
return true;
}
}
And finally, here's the snippet where the weapon is instantiated and attached to my weapon rod:
public WeaponItem SwapWeapon(WeaponItem NewWeapon)
{
DestroyAllChildren();
var previousWeapon = CurrentWeapon;
CurrentWeapon = NewWeapon;
InstantiateAndAttachWeapon(CurrentWeapon);
return previousWeapon;
}
private void InstantiateAndAttachWeapon(WeaponItem weapon)
{
WeaponPrefab = Instantiate(weapon.WeaponPrefab);
var weaponScript = WeaponPrefab.GetComponent<AmmoWeapon>();
weaponScript.WeaponInfo = weapon;
var sprite = WeaponPrefab.GetComponent<SpriteRenderer>();
sprite.sprite = weapon.Sprite;
Debug.Log(CurrentWeapon.DefaultRotation);
WeaponPrefab.transform.parent = transform;
WeaponPrefab.transform.localRotation = Quaternion.Euler(
CurrentWeapon.DefaultRotation);
WeaponPrefab.transform.localScale = weapon.DefaultScale;
WeaponPrefab.transform.localPosition = Vector3.zero;
SetAmmoScript(weaponScript);
}
I'm making a game similar to the 2013 style Cookie Clicker. I'm stuck with making the auto generate over time script. I want to be able to access a script that makes "Muffins" automatically.
I've tried to make the script multiple times but I can't seem to get the muffin count to change.
public bool MakingMuffins = false;
public static int MuffinIncrease = 1;
public int InternalIncrease;
void Update () {
InternalIncrease = MuffinIncrease;
if (MakingMuffins == false)
{
MakingMuffins = true;
StartCoroutine(MakeTheMuffin());
}
}
IEnumerator MakeTheMuffin ()
{
GlobalMuffins.MuffinCount += InternalIncrease;
yield return new WaitForSeconds(1);
MakingMuffins = false;
}
}
and my other main file to start the method.
public void StartAutoMuffin()
{
if (InternalPlayerMuffins >=bakerycost){
playSound.Play();
StartBakery.SetActive(true);
InternalPlayerMuffins -= bakerycost;
bakerycost *= 2;
turnOffButton = true;
themps += 1;
thelevel += 1;
}else{
Debug.Log("Cant do anything");
}
}
StartBakery is a Game Object that should start the auto make after I press the button. It is set inactive as default.
Here is more code from my main script to help understand.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GlobalGameMechanics : MonoBehaviour
{
public static bool turnOffButton = false;
public AudioSource muffinSound;
public AudioSource playSound;
public static int GlobalPlayerDiamonds;
public int InternalPlayerDiamonds = 1200;
public GameObject PlayerDiamondDisplay;
public GameObject TotalMPS;
public GameObject Cost;
public GameObject Level;
public GameObject MPS;
public GameObject FakeCost;
public GameObject FakeLevel;
public GameObject FakeMPS;
public int InternalPlayerMuffins = 15;
public int bakerycost = 25;
public int thelevel = 0;
public int themps = 0;
public int currentMuffins;
public GameObject ShopPanel;
public GameObject fakeButton;
public GameObject realButton;
public GameObject StartBakery;
void Start()
{
}
void Update()
{
GlobalPlayerDiamonds = InternalPlayerDiamonds;
PlayerDiamondDisplay.GetComponent<Text>().text = "" + InternalPlayerDiamonds;
TotalMPS.GetComponent<Text>().text = "/Sec " + themps;
GlobalMuffins.MuffinCount = InternalPlayerMuffins;
Cost.GetComponent<Text>().text = "" + bakerycost;
Level.GetComponent<Text>().text = "Level " + thelevel;
MPS.GetComponent<Text>().text = "MPS " + themps;
FakeCost.GetComponent<Text>().text = "" + bakerycost;
FakeLevel.GetComponent<Text>().text = "Level " + thelevel;
FakeMPS.GetComponent<Text>().text = "MPS " + themps;
currentMuffins = InternalPlayerMuffins;
if (currentMuffins >= bakerycost)
{
fakeButton.SetActive(false);
realButton.SetActive(true);
}
if (turnOffButton == true)
{
realButton.SetActive(false);
fakeButton.SetActive(true);
turnOffButton = false;
}
}
So far I have created 1 button that I want to increase the "InternalPlayerMuffins" By 1 every second. And then I want to have Button 2 to increase by +5 /sec. I'm just having trouble and been stuck for a few days.
I cant make the muffins increase at all on its :/
Your are close, it seems like you forgot to loop your coroutine endlessly like so:
IEnumerator MakeTheMuffin() {
while (true) {
GlobalMuffins.MuffinCount += InternalIncrease;
yield return new WaitForSeconds(1);
MakingMuffins = false;
}
}
Though I may be wrong on that, since I am not so sure if GlobalMuffins.MuffinCount is the integer that you want to increase every second.
But generally the idea is the same, you want would want a coroutine, a target interval, a target integer and the value you want to increase by, like so:
public class YourMuffinMaker : MonoBehaviour {
public float intervals;
public int increment;
private int muffinCount;
void Start() {
muffinCount = 0;
StartCoroutine(CreateMuffinEveryIntervals());
}
private IEnumerator CreateMuffinEveryIntervals() {
while (true) {
muffinCount += increment;
yield return new WaitForSeconds(intervals);
}
}
}
This answer that was posted is an alternative to Coroutine as well.
i can use InvokerRepeating whick calls method every time u need, for example
void Start()
{
InvokeRepeating("CreateNewMuffin", 0f, 1f);
}
I have made a game manager making sure my data gets passed on from the first scene on to the next scene. Within the game manager, I have added certain scripts to ensure it gets passed. As you can see in the picture underneath.
The problem is that the score adds up at the first level, let's say I have 5 points. I go to level 2 and the UI shows my score as 0 (even tho I have nothing put as text within the score text) I kill 1 monster and the UI shows 6. So how can I put the UI to be showing it at all times? (Constant refresh?)
The second problem is that while the score manager does work. The health script cancels everything out when switching levels.
The user starts with 10 health. Takes damage in the first scene, but in the second scene, the user still has 10 health for some reason?
Game Manager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour {
public ActionButton PopupPrefab;
private ActionButton currentlySpawnedPopup;
public static GameManager instance = null;
void Awake () {
if (instance == null) {
instance = this;
} else if (instance != this) {
Destroy (gameObject);
}
Application.targetFrameRate = 60;
}
void Update () {
//if (Input.GetKeyDown(KeyCode.R)) {
// RestartScene ();
//}
}
public void InvokeRestartScene (float time) {
Invoke ("RestartScene", time);
}
public void RestartScene () {
SceneManager.LoadScene (0);
}
public void SpawnPopup (Vector2 position) {
DespawnPopup ();
currentlySpawnedPopup = Instantiate (PopupPrefab, position, Quaternion.identity) as ActionButton;
}
public void DespawnPopup () {
if (currentlySpawnedPopup != null) {
currentlySpawnedPopup.DestroySelf();
currentlySpawnedPopup = null;
}
}
public void FadePopup () {
if (currentlySpawnedPopup != null) {
currentlySpawnedPopup.FadeMe ();
currentlySpawnedPopup = null;
}
}
}
Score Manager
using UnityEngine;
using System;
using System.Collections;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
public int Score { get; private set; }
public int HighScore { get; private set; }
public bool HasNewHighScore { get; private set; }
public static event Action<int> ScoreUpdated = delegate {};
public static event Action<int> HighscoreUpdated = delegate {};
private const string HIGHSCORE = "HIGHSCORE";
// key name to store high score in PlayerPrefs
void Awake()
{
if (Instance)
{
DestroyImmediate(gameObject);
}
else
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
void Start()
{
Reset();
}
public void Reset()
{
// Initialize score
Score = 0;
// Initialize highscore
HighScore = PlayerPrefs.GetInt(HIGHSCORE, 0);
HasNewHighScore = false;
}
public void AddScore(int amount)
{
Score += amount;
// Fire event
ScoreUpdated(Score);
if (Score > HighScore)
{
UpdateHighScore(Score);
HasNewHighScore = true;
}
else
{
HasNewHighScore = false;
}
}
public void UpdateHighScore(int newHighScore)
{
// Update highscore if player has made a new one
if (newHighScore > HighScore)
{
HighScore = newHighScore;
PlayerPrefs.SetInt(HIGHSCORE, HighScore);
HighscoreUpdated(HighScore);
}
}
}
Health Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class Health : MonoBehaviour {
public UnityEvent OnTakeDamageEvent;
public UnityEvent OnTakeHealEvent;
public UnityEvent OnDeathEvent;
[Header ("Max/Starting Health")]
public int maxHealth;
[Header ("Current Health")]
public int health;
[Header ("IsDeathOrNot")]
public bool dead = false;
[Header ("Invincible")]
public bool invincible = false;
public bool becomeInvincibleOnHit = false;
public float invincibleTimeOnHit = 1f;
private float invincibleTimer = 0f;
[Header ("Perform Dead Events after x time")]
public float DieEventsAfterTime = 1f;
void Start () {
health = maxHealth;
}
void Update () {
if (invincibleTimer > 0f) {
invincibleTimer -= Time.deltaTime;
if (invincibleTimer <= 0f) {
if (invincible)
invincible = false;
}
}
}
public bool TakeDamage (int amount) {
if (dead || invincible)
return false;
health = Mathf.Max (0, health - amount);
if (OnTakeDamageEvent != null)
OnTakeDamageEvent.Invoke();
if (health <= 0) {
Die ();
} else {
if (becomeInvincibleOnHit) {
invincible = true;
invincibleTimer = invincibleTimeOnHit;
}
}
return true;
}
public bool TakeHeal (int amount) {
if (dead || health == maxHealth)
return false;
health = Mathf.Min (maxHealth, health + amount);
if (OnTakeHealEvent != null)
OnTakeHealEvent.Invoke();
return true;
}
public void Die () {
dead = true;
if (CameraShaker.instance != null) {
CameraShaker.instance.InitShake(0.2f, 1f);
}
StartCoroutine (DeathEventsRoutine (DieEventsAfterTime));
}
IEnumerator DeathEventsRoutine (float time) {
yield return new WaitForSeconds (time);
if (OnDeathEvent != null)
OnDeathEvent.Invoke();
}
public void SetUIHealthBar () {
if (UIHeartsHealthBar.instance != null) {
UIHeartsHealthBar.instance.SetHearts (health);
}
}
}
I have thought of adding the following script on to my Health Script
But then I get the following error messages:
" Cannot implicitly convert type 'int' to 'bool'"
"The left-hand side of an assignment must be a variable, property or indexer"
void Awake()
{
if (health)
{
DestroyImmediate(gameObject);
}
else
{
(int)health = this;
DontDestroyOnLoad(gameObject);
}
}
The problem is that the score adds up at the first level, let's say I have 5 points. I go to level 2 and the UI shows my score as 0 (even tho I have nothing put as text within the score text) I kill 1 monster and the UI shows 6. So how can I put the UI to be showing it at all times? (Constant refresh?)
You can make one of the scripts set the UI text score when the scene is loaded.
void Start(){
// Loads the scoreText on start
scoreText.text = yourCurrentScore.ToString();
// Will work unless it has a "DontDestroyOnLoad" applied to it
}
The second problem is that while the score manager does work. The
health script cancels everything out when switching levels. The user
starts with 10 health. Takes damage in the first scene, but in the
second scene, the user still has 10 health for some reason?
In your health script, you had this:
void Start () {
health = maxHealth;
}
This resets your health everytime the scene loads and starts (Aka when you load to the next level). This causes the issue.
" Cannot implicitly convert type 'int' to 'bool'"
if (health)
The () for the if statement should be a condition (a question).
For example, doing health < 0 is valid since its saying "Is health less than 0?"
Doing health is not, since its just saying "10" (or some number).
"The left-hand side of an assignment must be a variable, property or
indexer"
(int)health = this;
If you wanted to change the value of health, just do health = 10 or health = some_Variable_That_Is_An_Integer
I am making a game like 2 cars. And I have written code to create a instantiater. It is a car game and there are 4 lanes. Let me show you a picture of my game and yeah this is solely for practise.
Player should avoid square objects and eat circle objects but sometimes 2 square objects get spawned in a same lane making impossible for player to win. I have written this script to control that but I failed. Please help me. At least have a check to my DetectSameLaneFunction(). And tell me what I am doing wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour {
public GameObject[] objects;
public float delaytime = 2f; // this is separate for each prefab with which script is attaches
public float spawnrate = 1f; // this is separate for each prefab with which script is attaches
public static int lastgameobjectindex;
public static GameObject lastgameobject;
public static GameObject SecondLastGameObject;
private float loadingtime;
private GameObject go; // just a temporary variable
public static List<GameObject> spawnobjects = new List<GameObject>();
// Use this for initialization
void Start () {
loadingtime = delaytime;
}
// Update is called once per frame
void Update () {
if (Time.time > loadingtime)
{
float randomness = spawnrate * Time.deltaTime;
if ( randomness < Random.value)
{
Spawners();
}
NextLoadTime();
}
}
private void Spawners()
{
int spawnnumber = Random.Range(0, 2);
GameObject go = Instantiate(objects[spawnnumber]) as GameObject;
go.transform.position = this.transform.position;
spawnobjects.Add(go);
Debug.Log(spawnobjects[spawnobjects.Count-1]);
DetectSameLaneObjects();
/* if (spawnobjects.Count > 4)
{
spawnobjects.RemoveAt(0);
}*/
}
private void DetectSameLaneObjects()
{
if (spawnobjects.Count > 3)
{
lastgameobject = spawnobjects[spawnobjects.Count - 1];
SecondLastGameObject = spawnobjects[spawnobjects.Count - 2];
lastgameobjectindex = spawnobjects.Count - 1;
if (SecondLastGameObject.transform.position.x != lastgameobject.transform.position.x
)
{
if (Mathf.Abs(lastgameobject.transform.position.x- SecondLastGameObject.transform.position.x) < 2.3f)
{
Debug.Log("Destroy function getting called");
Destroy(spawnobjects[lastgameobjectindex]);
spawnobjects.RemoveAt(lastgameobjectindex);
}
}
}
}
void OnDrawGizmos()
{
Gizmos.DrawWireSphere(this.transform.position, 0.6f);
}
void NextLoadTime()
{
loadingtime = Time.time + delaytime;
}
}
Well, it seems I'm re-approaching this problem. What I'm experiencing is, I guess you could say when I "despawn" a monster (the player has killed it), and "respawn" it, it adds all the moves to the list all over again. For example:
Monster A has 2 moves in his list: Scratch & Growl... Monster A dies... Monster A respawns... Monster A now has 4 moves in his list: Scratch & Growl...AND...Scratch & Growl...
Here's my code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MonsterMoves : MonoBehaviour {
public List<BaseMove> moves;
public int level;
public Scratch scratch = new Scratch();
public Growl growl = new Growl();
public FireBall fireball = new FireBall();
public PowerUp powerup = new PowerUp();
void Start(){
moves = gameObject.GetComponent<Monster>().monstersMoves;
level = gameObject.GetComponent<Monster>().level;
}
void Update(){
level = gameObject.GetComponent<Monster>().level;
SetupMoves(level);
}
private void SetupMoves(int level){
if(level >= 1 && !moves.Contains(scratch)){
moves.Add(scratch);
}
if(level >= 1 && !moves.Contains(growl)){
moves.Add(growl);
}
if(level >= 7 && !moves.Contains(fireball)){
moves.Add(fireball);
}
if(level >= 10 && !moves.Contains(powerup)){
moves.Add(powerup);
}
Any ideas on what I'm over-looking?
To answer some comments:
When the monster dies it's script (just called Monster) calls this:
public void SetDead(){
isAlive = false;
timeOfDeath = Time.time;
ReSpawner.deadMonster.Add(this);
this.gameObject.SetActive(false);
}
This handles "despawning" the monster, without actually destroying it. Then there's THIS script which handles the respawning:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ReSpawner : MonoBehaviour {
public float spawnDistance = 50.0f;
public float minSpawnDensity = 6f;
public float minSpawnDistance = 20f;
public int respawnDelay = 10;
public static List<Monster> deadMonster = new List<Monster>();
private Vector3 spawnPoint;
private Vector3 lastSpawnPoint = Vector3.zero;
void Update(){
for(var i = 0; i < deadMonster.Count; i++){
Monster monster = deadMonster [i];
float time = Time.time - monster.timeOfDeath;
if(time > respawnDelay)
{
monster.gameObject.SetActive(true);
monster.isAlive = true;
monster.gameObject.rigidbody.WakeUp();
monster.gameObject.GetComponent<Animator>().enabled = true;
monster.gameObject.GetComponentInChildren<MonsterAI>().enabled = true;
spawnPoint = new Vector3(Random.Range(0, 2000), Random.Range(0, 2000), Random.Range(0, 2000));
spawnPoint.y = TerrainHeight(spawnPoint);
if(!IsInvalidSpawnPoint(spawnPoint, lastSpawnPoint)){
NavMeshHit closestHit;
if(NavMesh.SamplePosition(spawnPoint, out closestHit, 500, 1)){
spawnPoint = closestHit.position;
}else{
Debug.Log("...");
}
monster.gameObject.transform.position = spawnPoint;
monster.SetupMonster();
deadMonster.RemoveAt(i);
i--;
}
}
}
}
private bool IsInvalidSpawnPoint(Vector3 spawnPoint,Vector3 lastSpawnPoint){
if(spawnPoint.y == Mathf.Infinity || (spawnPoint - lastSpawnPoint).magnitude <= minSpawnDensity){
return true;
}else{
return false;
}
}
private float TerrainHeight(Vector3 spawnPoint){
Ray rayUp = new Ray(spawnPoint, Vector3.up);
Ray rayDown = new Ray(spawnPoint, Vector3.down);
RaycastHit hitPoint;
if(Physics.Raycast(rayUp, out hitPoint, Mathf.Infinity)){
return hitPoint.point.y;
}
else if(Physics.Raycast(rayDown, out hitPoint, Mathf.Infinity)){
return hitPoint.point.y;
}else{
return Mathf.Infinity;
}
}
}
I'm hesitant to post the ENTIRE "Monster" script because it's quite extensive.
SO I'VE MADE SOME CHANGES. Now I call the function whenever the monster is first created, and if/when it levels up. Here's the new script for the MonsterMoves...
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class Moves : MonoBehaviour {
public List<Move> moves;
public List<Move> movesToLearn = new List<Move>();
void Start(){
moves = gameObject.GetComponent<Monster>().monstersMoves;
}
public void AddMoves(int level, List<Move> moves){
foreach(Move move in movesToLearn){
if(level >= move.levelLearned){
if(!moves.Contains(move)){
moves.Add(move);
}
}
}
}
}
I call this function in the script that handles all of the properties of the monster. If the list of Moves already has the move in it, I don't want to add it again. However, it's still not working properly. It's STILL adding the move to the list, even if it's already in there.
Remove monster A from the list before you respawn it!