Singleton – extra script not destroyed? - c#

I want to have a singleton which will store, update and show player score through all levels (scenes), but something works wrong.
This my singletone script GameStatus.cs:
using UnityEngine;
using TMPro;
public class GameStatus : MonoBehaviour
{
public static GameStatus instance;
[SerializeField] int pointsPerBlock = 50;
[SerializeField] TextMeshProUGUI scoreText;
[SerializeField] public static int currentScore = 0;
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(gameObject);
}
}
public void AddToScore()
{
currentScore += pointsPerBlock;
scoreText.text = currentScore.ToString();
}
}
I also have another script for objects player need to destroy, called Block.cs. Here it is:
using UnityEngine;
public class Block : MonoBehaviour
{
Level level;
GameStatus gameStatus;
private void Start()
{
level = FindObjectOfType<Level>();
gameStatus = FindObjectOfType<GameStatus>();
level.CountBreakableBlocks();
}
private void OnCollisionEnter2D(Collision2D collision)
{
DestroyBlock();
}
private void DestroyBlock()
{
level.BlockDestroyed();
gameStatus.AddToScore();
Destroy(gameObject);
}
}
And on level 1 everything works fine, but when the game goes to the next level this happens:
Player score stops updating.
If I use Debug.Log(currentScore); in GameStatus.cs I can see that this variable doesn't change when player breaks blocks, but if use Debug.Log(gameStatus.currentScore); in Block.cs then I can see that this variable is getting updated.
Debug.Log(FindObjectsOfType().Length); shows that there is one GameStatus object in the first level and two objects in the next levels, although I can't see the second one GameStatus in hierarchy.
So my question is - what's wrong and how to fix it?

If you use singleton, there is no point to make
currentScore
static variable, just make it
public int currentScore;
Also in your block cs you can just call
GameStatus.instance.AddToScore();
No need to make reference at start

Related

How to Instantiate an object onto a path in Unity

I am recently new to c# and I need some help.
Essentially I have two scripts, one for spawning objects and one for moving an object along a path. I need a way to combine these two mechanics so that when a new object is instantiated it automatically joins the path and follows it.
The path is made using iTween.
![The objects the scripts are attached to] (https://i.stack.imgur.com/QPQn2.png)
I've tried changing the variable m_PlayerObj to the cube prefab and I've tried adding the Path script to the instantiation script but nothing seems to work.
The scripts attached do nkt include these attempts I made as I wanted to make the code very clear.
Spawner script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnerScript : MonoBehaviour
{
public GameObject cubeprefab;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(cubeprefab, transform.position, Quaternion.identity);
}
}
}
Path script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Path : MonoBehaviour
{
public GameObject m_PlayerObj;
public Transform[] positionPoint;
[Range(0, 1)]
public float value;
// Start is called before the first frame update
void Start()
{
Debug.Log(iTween.PathLength(positionPoint));
}
float tempTime;
// Update is called once per frame
void Update()
{
if (value < 1)
{
value += Time.deltaTime / 10;
}
iTween.PutOnPath(m_PlayerObj, positionPoint, value);
}
private void OnDrawGizmos()
{
iTween.DrawPath(positionPoint,Color.green);
}
}
As stated above, any help would be greatly appreciated as I am really stuck on this conceot and since I am new to Unity I really can’t see a way around it // how to fix it.
Instead of only storing the player object in the Path script, store a collection of objects. That way, the path can keep track of more than one object.
//public GameObject m_PlayerObj; // Get rid of this
public List<GameObject> followers; // Add this
Then, in your Update loop, you can loop through all of them.
void Update()
{
for (var i = 0; i < followers.Length; ++i)
{
if (value < 1)
{
value += Time.deltaTime / 10;
}
iTween.PutOnPath(m_PlayerObj, positionPoint, value);
}
}
Of course, now, you need to make sure you pass your cube instance to the Path GameObject when you spawn it, so the path knows about the cube follower. That means your spawner also needs to know about the path.
public class SpawnerScript : MonoBehaviour
{
public GameObject cubeprefab;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var cubeInst = Instantiate(cubeprefab, transform.position, Quaternion.identity);
path.followers.Add(cubeInst);
}
}
}
Now a new problem is going to be that each object is going to be at the same position on the path, because the path only stores one value - a better term might be progress. So if they're all the same, like the cube, you won't be able to tell because they'd overlap.
So you have to decide what you want to do instead. Evenly space them? You could do that with some math. Or have them all start from the beginning and keep track of their progress separately? Then you'd need to store progress for each of them. A better place to do that is probably on the cube object, which means you need to add a new script to your cube prefab:
public class PathFollower : MonoBehaviour
{
[Range(0, 1)]
public float pathProgress;
}
And, you need to start referring to the prefab by this script, instead of just GameObject:
public class SpawnerScript : MonoBehaviour
{
public PathFollower pathFollower;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var followerInst = Instantiate(pathFollower, transform.position, Quaternion.identity);
path.followers.Add(followerInst);
}
}
}
public class Path : MonoBehaviour
{
//public GameObject m_PlayerObj; // Get rid of this
public List<PathFollower> followers; // Add this
//...
Finally, you need to make sure to use the individual progress for each path follower, rather than a single progress value like your old Path script did:
for (var i = 0; i < followers.Count; ++i)
{
if (followers[i].pathProgress < 1)
{
followers[i].pathProgress += Time.deltaTime / 10;
}
iTween.PutOnPath(followers[i].gameObject, positionPoint, followers[i].pathProgress);
}
Putting it all together (separate files of course, with their own includes!):
public class SpawnerScript : MonoBehaviour
{
public PathFollower pathFollower;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var followerInst = Instantiate(pathFollower, transform.position, Quaternion.identity);
path.followers.Add(followerInst);
}
}
}
public class Path : MonoBehaviour
{
//public GameObject m_PlayerObj; // Get rid of this
public List<PathFollower> followers; // Add this
public Transform[] positionPoint;
//[Range(0, 1)]
//public float value; // Don't need this anymore either
// Start is called before the first frame update
void Start()
{
Debug.Log(iTween.PathLength(positionPoint));
}
// Update is called once per frame
void Update()
{
for (var i = 0; i < followers.Count; ++i)
{
if (followers[i].pathProgress < 1)
{
followers[i].pathProgress += Time.deltaTime / 10;
}
iTween.PutOnPath(followers[i].gameObject, positionPoint, followers[i].pathProgress);
}
}
private void OnDrawGizmos()
{
iTween.DrawPath(positionPoint,Color.green);
}
}
public class PathFollower : MonoBehaviour
{
[Range(0, 1)]
public float pathProgress;
}

Troubles with FindObjectOfType

I'm trying to make a simple damage boost I have 2 scripts, one attached to the projectile and one to the booster
Projectile:
public class DamageDealer : MonoBehaviour
{
[SerializeField] public int damage = 25;
public int GetDamage()
{
return damage;
}
public void Hit()
{
Destroy(gameObject);
}
public void IncreaseDamage(int value)
{
damage += value;
Mathf.Clamp(damage, 0, int.MaxValue);
}
}
Booster:
public class DamageUp : MonoBehaviour
{
int damageBoost = 50;
DamageDealer damageDealer;
void Awake()
{
damageDealer = FindObjectOfType<DamageDealer>();
Debug.Log(damageDealer); // this returns as null
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
damageDealer.IncreaseDamage(damageBoost);
}
}
}
As you can see in the Booster I've already identified what's returning as null, but I'm quite new to unity and C# so I don't know what I'm doing wrong
Some more info the Projectile is a prefab while the Booster is not (yet)
scripts are attached and enabled, objects are enabled too.
Hit() and GetDamage() are for another script I have no issue with.
Thanks in advance.
You're probably trying to find the object before it's instantiated, change the Awake to Start or try to change Script Execution Order settings
Check Also; Order of execution for event functions

UnityException: GetActiveScene is not allowed to be called from a MonoBehaviour constructor

I'm trying to make a save level system and I keep on getting this error.
UnityException: GetActiveScene is not allowed to be called from a MonoBehaviour constructor
I've tried searching this up, but there were no results. Here is the code I used:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class EndLevel : MonoBehaviour
{
public PlayerMovement pm;
public GameObject completeLevelUI;
// Start is called before the first frame update
void Start() {
}
// Update is called once per frame
void Update() {
}
void OnCollisionEnter (Collision collisionInfo) {
if(collisionInfo.collider.tag == "Finish") {
Debug.Log("You beat the level!");
pm.enabled = false;
completeLevelUI.SetActive(true);
Level = Level + 1;
PlayerPrefs.SetFloat("Level", Level);
Debug.Log("Saved");
Invoke("NextLevel", 3);
}
}
public void NextLevel() {
SceneManager.LoadScene (SceneManager
.GetActiveScene().buildIndex + 1);
}
}
Any Ideas about the error?
You have to get the current active Scene in the Start() or Awake() methods.
Example with Start():
private int sceneNumber;
private void Start() {
sceneNumber = SceneManager.GetActiveScene().buildIndex;
}
As an alternative solution you could also use a Lazzy Getter. Which means that the value won't get stale between when the scene loads and when you use it, which might matter with other pieces of data.
Example with Lazzy Getter:
private int sceneNumber {
get {
return SceneManager.GetActiveScene().buildIndex;
}
}
When you use either of those solutions, you can now simply call scenenumber + 1 in your SceneManager.Load() function call instead.
Additionally you need to ensure that you are calling an IEnumerator, instead of Invoking the Function call. If you want to delay the scene loading.
Function Call:
private void OnCollisionEnter (Collision collisionInfo) {
...
StartCoroutine(NextLevel(3f));
...
}
private IEnumerator NextLevel(float seconds) {
yield return new WaitForSeconds(seconds);
SceneManager.LoadScene(sceneNumber + 1);
}

How can I make a public "enemyDamage" integer from 1 script, communicate with another script?

I am new to coding and am not quite sure how to add context from 1 script to another. For my game so far, I have been Frankensteining code from multiple sources and have head into a dead end. I want my player to take the same amount of damage as stated by enemyDamage in the 'Enemy' script. I'm not too sure what other context to give you, but if you know how you could help me out, that would be awesome!
Enemy Script
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public int health = 100;
public int enemyDamage = 10;
public GameObject deathEffect;
public void TakeDamage (int damage)
{
health -= damage;
if (health <= 0)
{
Die();
}
}
void Die ()
{
Instantiate(deathEffect, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}
PlayerHealth Script
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
public int maxHealth = 10;
public int currentHealth;
public int damage = 1;
public HealthBar healthBar;
// Start is called before the first frame update
void Start()
{
currentHealth = maxHealth;
healthBar.SetMaxHealth(maxHealth);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
TakenDamage(1);
}
if (currentHealth <= 0)
{
PlayerDeath();
}
}
public void TakenDamage(int damage)
{
currentHealth -= damage;
healthBar.SetHealth(currentHealth);
}
void PlayerDeath()
{
UnityEngine.Debug.Log("bean guy");
}
public void OnTriggerEnter2D(Collider2D hitInfo)
{
UnityEngine.Debug.Log("We did it boys");
PlayerHealth player = hitInfo.GetComponent<PlayerHealth>();
{
UnityEngine.Debug.Log("beans");
TakenDamage(enemyDamage); // I need this to update with enemyDamage 's value
}
}
}
There are multiple ways:
Make EnemyDamage a Static Variable
public static int enemyDamage = 10;
Then you can call it in other scripts with Enemy.enemyDamage
Be aware that you can't set static variables in the Inspector.
Use GetComponent
Enemy enemy = gameObject.GetComponent(typeof(Enemy )) as Enemy;
enemy.enemyDamage
Create a GameManager
GameManager.CS:
#region Singelton
public static GameManager instance;
void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one Instance of Inventory found");
return;
}
instance = this;
}
#endregion
public int enemyDamage = 10;
Referencing the GameManager Script:
GameManager gm;
void Start()
{
gm = GameManager.instance;
}
//Calling Function in GameManager
gm.EndGame();
// Getting Value from GameManager
gm.enemyDamage();
When to use what?
If you want a more short term solution I would use a static variable, not advised with multiple enemies (different enemyDamage Values are now immosible)
If you have more variables or even functions that need to be accessible from multiple scripts I would advise you to use a Game Manager instead
You need to get a reference of the Enemy to use GetComponent, but makes it possible to add multiple different enemyDamage Values

My GameObject always stays inactive, SetActive(true) doesn't work and the coroutine won't start

I have a number of Prefabs stored in an array and instantiate one at a time by clicking a button. The object appears but is always inactive.
I've searched for hours for an answer and tried a lot but the problem stays the same. I hope it's ok to ask, even though there are several similar questions but none of the answers solves my problem.
Like I've already described, I want to instantiate a GameObject from an array per button click and the object indeed appears but when I try to start a coroutine on it(if that's helpful, it's an IEnumerator that shall let the Object fade out by handling it's colours alpha channel), I always get an error that says the GameObject is inactive. I'm not able to set it active by writing myPrefab.gameObject.SetActive(true) and I've tried to call it at several places in my code. It always stays inactive. Has anybody any idea what might be the problem?
public class Class1 : MonoBehaviour
{
[SerializeField]
private MyClass[] myPrefabs;
private MyClass myCurrentPrefab;
private int myIndex;
public void ButtonEffect()
{
InstantiatePrefab();
myCurrentPrefab.OnButtonEffect();
}
private void InstantiatePrefab()
{
myIndex = Random.Range(0, myPrefabs.Length);
myCurrentPrefab = myPrefabs[myIndex];
Instantiate(myCurrentPrefab);
}
}
public class MyClass : MonoBehaviour
{
private SpriteRenderer mySprite;
private void Start()
{
mySprite = GetComponent<SpriteRenderer>();
}
private void Awake()
{
this.gameObject.SetActive(true)
}
private void OnButtonEffect()
{
StartCoroutine(FadeOut(mySprite, 3));
}
public IEnumerator FadeOut(SpriteRenderer spriteToFade, float duration)
{
//DoFadeOutStuff
}
}
Here I've tried to set the object active in the Awake function of its own code but I've also tried it in the both functions of Class1 and its Start function. I've also tried to handle it for the whole array in an foreach loop. Always the same result. All I want to do is to set it active, right now I don't even care if my fade out stuff works correctly. First I thought it was because my instantiated object is a clone but I can't find anything about it, so even if the solution is pretty obvious, please help!
The problem is that you're referencing the actual prefab itself, which isn't actually active in the scene. You need to create a gameobject and set its value to the instantiated prefab. The following should work
using UnityEngine;
public class Class1 : MonoBehaviour
{
[SerializeField]
private GameObject[] myPrefabs;
public void ButtonEffect()
{
var go = InstantiatedPrefab();
go.GetComponent<MyClass>().OnButtonEffect();
}
private GameObject InstantiatedPrefab()
{
var index = Random.Range(0, myPrefabs.Length);
return Instantiate(myPrefabs[index]);
}
}
using System.Collections;
using UnityEngine;
public class MyClass : MonoBehaviour
{
private SpriteRenderer mySprite;
private void Start()
{
mySprite = GetComponent<SpriteRenderer>();
}
public void OnButtonEffect()
{
StartCoroutine(FadeOut(mySprite, 3));
}
private IEnumerator FadeOut(SpriteRenderer spriteToFade, float duration)
{
print("FadeOut");
yield return 0;
}
}

Categories

Resources