Unity - C# : how can I use a value in different scenes? - c#

I have made my first Unity3D game. It's a racing game with the infinite road. I made some pickup coins too.
the problem is when I pick up the coins and then I go to shop menu to see whether it's added to the total coins or not, it is not added till I play another round and then the coins i collected the round before will be added.
It's like a delay or something.
Is it because I don't know how can I use a value in different scenes? or it,s something else.
someone told me to use PlayerPrefs, I used it in my scripts but for this, I mean the count of coins, I don't know how to use it.
The script bellow is my player script in which I count the pickup coins and some other things related to the player. I only bring the Ontrigger and count related codes.
void Start () {
CameraAnimation = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Animator>();
_GameManager = GameObject.FindGameObjectWithTag("GameManager").GetComponent<GameManager>();
_AudioSource = GetComponent<AudioSource>();
count = 0;
SetCountText ();
}
void OnTriggerEnter(Collider other){
if(other.gameObject.tag == "Coins") {
count = count + 1;
SetCountText ();
}
}
public void SetCountText(){
CountText.text = count.ToString ();
}
The code below is my calculateCoin in which I add a count of collected coins to the previous value and show the total in shop scene textbox:
public class CalculateCoin : MonoBehaviour{
// Use this for initialization
public static int Coin = 10000;
public Text ShowCoin;
void Start () {
ShowCoin.text = PlayerPrefs.GetInt("Coin").ToString();
Coin =PlayerPrefs.GetInt("Coin")+Player.count;
PlayerPrefs.SetInt ("Coin", Coin);
}
}

First Option,
You can add 'DontDestroyOnLoad' for specific GameObject that include data as container. so It could be used as shared data between scene.
or Make 'Singleton' class and use it as data container it might be option.

Related

GameObject.FindGameObjectsWithTag("Enemy").Length off by one for some reason?

so I'm wanting to pause the game once the amount of enemies hits 0. So I'm using GameObject.FindGameObjectsWithTag("Enemy").Length to find the number of enemies. I put this in a function that's called right when the enemies are instantiated so I can see the length go to 4, as there's 4 enemies spawning. When an enemy is killed the function is called again where the length is printed to console again. For some reason, on the first enemy killed the count repeats with a 4 again despite there only being 3 enemies. Once another enemy is killed it reports 3 when there's actually 2 and so on until I get to 1 when there's 0 enemies.
Here's the first snippet of code:
public class EnemyList : MonoBehaviour
{
public List<GameObject> weakMobs = new List<GameObject>();
public List<GameObject> mediumMobs = new List<GameObject>();
public List<GameObject> bossMobs = new List<GameObject>();
public List<Transform> spawningChildren = new List<Transform>();
public static int mobCount;
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0], spawningChildren[Random.Range(0, 4)]) as GameObject;
}
CheckMobCount();
}
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Length;
print(mobCount);
}
The next piece of code is where the enemy is killed and the CheckMobCount() is called again.
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
enemyList.CheckMobCount();
//needs death animations
}
}
Here's the console messages:
Console of printed lengths
I'm self taught so I apologize if this is elementary. I've tried doing this several different ways and this is the closest I've been but I'm open to new ideas as well.
Thank you!!
As noted in this answer, the object is not actually destroyed in the current frame.
From the documentation:
The object obj is destroyed immediately after the current Update loop… Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
I also agree that using DestroyImmediate() is a bad idea.
Ultimately, your question seems to really be about pausing the game when the enemy count reaches 0, which unfortunately hasn't actually been answered yet.
In fact, you don't really need to do anything different except move the check for the enemy count to the beginning of the Update() method, and pause the game there if it's 0. Then you'll find that the component for the enemy has been destroyed at that point.
Presumably enemies are spawned before the update loop starts (i.e. before the first frame), but if not then you can use whatever logic you're already using to decide that new enemies need to be spawned, to detect the fact that you haven't spawned any yet and avoid pausing before the enemies have spawned.
Here you have attached your script to your enemy instances. And they are still alive when you are querying for the number of enemies left.
You should do the following:
public class Enemy: MonoBehaviour
{
public static int EnemyCount = 0;
private void Start()
{
EnemyCount++;
}
private void OnDestroy()
{
EnemyCount--;
}
}
And then you can query the enemy count from anywhere but just excessing the EnemyCount by Enemy.EnemyCount.
If you want to get a more difficult example then you can check out this Game Dev tutorial: https://www.youtube.com/watch?v=LPBRLg4c5F8&t=134s
Destroy is actually executed at the end of the frame. There is DestroyImmediate that is executed immidiatelly but it's not recommended to be used. What I would do is to add a field or a property to identify whether the enemy is still alive and then to check against it. Something like:
class Enemy : MonoBehaviour
{
public bool IsAlive { get; set; } = true;
}
public class EnemyList : MonoBehaviour
{
//...
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Select(x => x.GetComponent<Enemy>()).Count(x => x.IsAlive);
print(mobCount);
}
}
And then:
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
this.GetComponent<Enemy>().IsAlive = false;
enemyList.CheckMobCount();
//needs death animations
}
}
This can be further optimized to store the Enemy somewhere and not use GetComponent every time but you get the idea.
As already mentioned by others the issue is that Destroy is executed delayed.
Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
You could simply count only the GameObjects that are still alive, those for which the bool operator is true.
Does the object exist?
It will be false for objects destroyed in that same frame.
E.g. using Linq Count
using System.Linq;
....
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Count(e => e);
which basically equals doing
mobCount = 0;
foreach(e in GameObject.FindGameObjectsWithTag("Enemy"))
{
if(e) mobCount++;
}
There is no need for an additional property or Component.
I am suggesting you to use “DestroyImmediate” instead of “Destroy”,Then look at the result.
I have a better idea, why not just use static variables when spawning enemies?
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0],
spawningChildren[Random.Range(0, 4)]) as GameObject;
mobCount++;
}
}
Do not use Linq
Do not use DestroyImmediate (it will freeze and bug your game, probably)
Avoid FindGameObjectsWithTag in loops, only in initialization.
Track your enemies in an array or list
When you destroy an enemy, remove it's reference from the list
Use the list count/length to get the real actual number.

Arcade Coin Script for Unity

Hello!
For a school project, I have to create a 2d Arcade Game. And one of the things that I have to include is a coin system. Let me explain:
In the main menu you start out with 3 coins, and whenever you click play, one coin will be spent so you will have 2 coins. At the end of some levels, you can pick a coin up so it will refill in the main menu. Once you got no coins left you cant enter the game anymore.
I hope I explained it well enough. I do have the coin pickup part but I just can't wrap my head around the rest of what I have to do. Can someone maybe link me a tutorial that implements this system or maybe help me?
You should use a variable for the amount of coins. Call it coins. Also, make sure you aren’t loading new scenes, or it will erase the amount. So once the player hits play, take a coin away and start the game. Then, they can pick the coins up again. Write this in the script coin:
public int coins = 3;
bool pressedTheButton;
bool pickedUpCoin;
void Update(){
if (pressedTheButton){
coins -= 1;
pressedTheButton = false;
}
if (pickedUpCoin){
coins += 1;
pickedUpCoin = false;
}
}
You should change the variable called pressedTheButton when the player starts the game. Same with the pickedUpCoin. If you are changing scenes, then add a function to it called DontDestroyOnLoad(gameObject);
Add it to a Start(){} function of the script.
public int coins = 3;
bool pressedTheButton;
bool pickedUpCoin;
void Start(){
DontDestroyOnLoad(gameObject);
}
void Update(){
if (pressedTheButton){
coins -= 1;
pressedTheButton = false;
}
if (pickedUpCoin){
coins += 1;
pickedUpCoin = false;
}
}
What this does, is doesn’t destroy the game object when you load the scene for the game. Attach the script to any game object you don’t want to be destroyed when loading the new scene. For example you can add it to an empty game object, or something that won’t change.

(Unity3D) in Block Breaker game I'm trying to load levels when all bricks destroyed

Good Morning Developers!
so here is what i'm trying to do: i created a block breaker game, and i wrote some code so that when all bricks in the scene are destroyed the next level is loaded.
It works fine, but there is a bug! when i lose before destroying all the bricks, and then i press "play again", the static variable who is responsible of counting bricks on scene does not reset to 0! it keeps the number of brick before i lost and add to it the number of bricks in the new scene!, so instead of returning 24 for ex (which is the correct number of bricks in scene) it returns 35 (11 + 24)
how can i fix that please?
here is the code i'm using: first the brick script :
public int maxHits;
public int timesHit;
public Sprite[] hitSprites;
public static int breakableCount = 0;
private bool isBreakable;
private LevelManager levelManager;
// Use this for initialization
void Start () {
isBreakable = (this.tag == "Breakable");
if(isBreakable){
breakableCount++;
}
print (breakableCount);
timesHit = 0;
levelManager = GameObject.FindObjectOfType<LevelManager> ();
}
void OnCollisionEnter2D(Collision2D collision) {
if (isBreakable) {
HandleHits ();
}
}
void HandleHits(){
//TODO remove the print!!
print ("collison");
timesHit++;
if (timesHit >= maxHits) {
breakableCount--;
print (breakableCount);
levelManager.BrickDestroyed ();
Destroy (gameObject);
} else {
LoadSprite ();
}
}
// Update is called once per frame
void Update () {
}
//TODO Remove this when player can WIN
void NextLevel(){
levelManager.LoadNextLevel ();
}
void LoadSprite(){
int spriteIndex = timesHit - 1;
this.GetComponent<SpriteRenderer> ().sprite = hitSprites [spriteIndex];
}
and here is the LevelManager script I'm using to manage levels :
public void LoadLevel (string name) {
Debug.Log ("level change requested for : " + name);
Application.LoadLevel (name);
}
public void ExitRequest() {
Debug.Log ("Exit game requested");
Application.Quit ();
}
public void LoadNextLevel () {
Application.LoadLevel (Application.loadedLevel + 1);
}
public void BrickDestroyed () {
if(Brick.breakableCount <= 0) {
LoadNextLevel ();
}
}
hope i explained correctly, and sorry if i made some English errors i'm not native speaker lol, Thank you have a nice day ^^
-Edited due to misunderstanding-
I didn't realize that was your BRICK script. The reset should be inside our LevelManager.
Your first line in your function to load a new level in LevelManager should be:
breakableCount = 0;
This will make it so that when the level is initialized that the counter is reset.
Also, you could reset the same way as soon as you've decided that a person has beat the current level.
Also, I recognize this from Ben Tristram's Unity Dev Course. You should try using the tools built into his class for questions, there is a lot of support there for these specific exercises!
Stack Overflow is great though, and it's a great source for when that stuff falls through. Another place to check is https://gamedev.stackexchange.com/
private static int breakableCount = 0;
public static int BreakableCount
{
get{ return breakableCount; }
set{
breakableCount = value;
if(breakableCount <= 0){ EndOfLevel() }
}
}
Turning your variable into property (or you can use a method if you prefer), you can now add some logic when it gets modified.
EndOfLevel is just a method you call to load the next level, save some data and reset some static values before leaving.
I Want to update this post because i find a solution and i have another question in the same subject!
First i'll tell you how i fixed it:
as #JorgeSantos suggested i created a ResetGame fonction in my loadlevel script :
void ResetGame(){
Brick.breakableCount = 0;
print ("breakableCount set to 0 ");
}
then i called that fontion in my LoadLevel fonction :
public void LoadLevel (string name) {
ResetGame ();
Debug.Log ("level change requested for : " + name);
Application.LoadLevel (name);
now the variable is resetting just fine
the only problem (it's not really a problem because the game runs fine, it's just that i want to know why it's happening) is that for ex let's say that i run the game, destroy 4 bricks, and then i lose, (keep in mind that there are 24 bricks in the scene) so i left 20 bricks non destroyed!
when i press play again, in the console, i notice that when i destroy a brick the breakableCount variable is not taking new values, then when i destroy 4 brick, (which means i'm in the same number of bricks left as i were before losing), then the breakableCount variable takes the value 20 (which is the right value) and continue decreasing when i destroy bricks normally!, You can see now the the game continue to work fine, but i dont understand why that variable is not reset to the right number of brick after i click on play again, and only takes effect when i reach the same number of destroyed brick as in my fist try?!
hope i made my point clear looking forward for your answers, and thank you all ^^

Incrementing integer with multiple sources of input C#

In my game there is a score. When an object collides, a set increment is added to the score. There is one scoreboard. What is happening in my game is that when an object collides it hijacks the scoreboard to show only its own person history of scores. When the next object collides, it takes over the scoreboard and shows its own personal history. I am trying to show the amalgamate score of all of the objects put together, each contributing their part to a grand total.
void OnCollisionEnter (Collision col)
{
if(col.gameObject.name == "Plane")
{
score += 50;
textMeshComponent = GameObject.Find ("Score").GetComponent<TextMesh> ();
textMeshComponent.text = score.ToString ();
}
}
There are 10 of (col.gameObject.name) and there is one "Plane" that they all interact with.
How can I make the score a conglomerate like I described? I am really at a loss for how to manipulate the (col.gameObject.name) side of that equation.
Thanks for your advice.
Make score a member of Plane, instead of this, and make it public so all the other gameObjects can get it.
You can also create score as its own gameObject, or create a gameObject that contains all the globals.
Let me suggest another approach although not recommended textMeshComponent.text = (score+(int.Parse(textMeshComponent.text))).toString();
now this will add points as a whole regardless each gameobject adding its own to the main value
Consider having an object in your scene whose sole concern is counting (and maybe displaying) the score. Lets name it ScoreController.
Whenever an event occurs for which you want to award score to the player, the responsible code just calls your ScoreController and tells them how much score to award.
Your ScoreController GameObject might have a script like this:
public class ScoreController : MonoBehaviour
{
public TextMesh scoreDisplay;
public int Score { get; private set; }
void Start()
{
UpdateView();
}
public void AwardScore(int amount)
{
Score += amount;
UpdateView();
}
private void UpdateView()
{
scoreDisplay.text = Score.ToString();
}
}
Now, other code can award score by calling
GameObject.FindObjectOfType<ScoreController>().AwardScore(123);
If you want to read up on related topics, consider reading about the Model-View-Control design pattern, and about SoC (seperation of concerns).

C# frames running twice?

This has me utterly confused. I am using unity3d and c# for the scripts and it seems as if the code is running twice per frame. However on button down I have a sprite change position and it only changes once at least I think it does.
I added the Debug in and I am getting results like this:
score 1 at 3.569991 at frame 168
score 2 at 3.57414 at frame 168
score 3 at 3.818392 at frame 183
score 4 at 3.820178 at frame 183
and so forth carrying on. I am not updating the score in any other scripts. There is more to this script but it is just printing the score out on screen.
Is there any reason why a script may run like this?
full script:
using UnityEngine;
using System.Collections;
public class Score : MonoBehaviour {
public static int highScore;
public static int myScore;
public static bool allowScore;
public GUIText myText;
public static bool WhichScene;
//only allows score to start when first object has passed player object
void OnTriggerEnter2D(Collider2D collisionObject) {
allowScore = true;
Debug.Log ("allowScore is true");
}
void Start () {
highScore = PlayerPrefs.GetInt("highScore");
int scale = Screen.height / 20;
myText.fontSize = scale;
}
//add 1 to score every switch
void Update () {
// need to stop score counting
if (DeathOnImpact.dead == true) {
allowScore = false;
} if (Input.GetMouseButtonDown (0) && allowScore == true && WhichScene == true) { // added SpawnerObjectMovement.WhichScene == true
//Input.GetMouseButtonDown (0)
//Input.GetKeyDown("space")
myScore = myScore + 1;
Debug.Log ("My score is " + myScore + " point(s)" + " at time:" + Time.realtimeSinceStartup + " at frame:" + Time.frameCount);
} if (myScore > highScore) {
highScore = myScore;
PlayerPrefs.SetInt("highScore", highScore);
}
myText.text = myScore.ToString ();
//myText.text = "Score: " + myScore.ToString ();
if (Score.WhichScene == false) {
int scale = Screen.height / 40;
myText.fontSize = scale;
myText.text = "practice mode";
}
}
}
The script is attached to a TriggerObject, a sprite, and a Gui Text
WhichScene referes to which button I pressed, 'play' for normal play or 'practice mode' for an easier version. Score is disabled for 'practice mode'.
UPDATE: I have just edited out everything that I have added since the problem arose and it has not been fixed. Im going to check all unity setting to see if anything has changed. It seems in build setting an old Scene which I deleted around when the problem arose is not selcted but just 'shadowed' so I cant select it. The Scene was an exact copy of the PlayScene. Is this a sign of the problem?
SOLUTION: It appears that separating the script into two smaller scripts has solved the issue. I am still unsure why it has only arisen now since it was working before as one, but oh well I guess. Thank you.
Based on your comments, where you have said that your script is attached to 3 GameObjects, that means that the Update() method is getting called 3 times per frame.
public class Score : MonoBehaviour {
public static int myScore;
You have declared myScore as a static int. This functionally means it will be shared by all instances of the Score script that run.
The Update() method of MonoBehaviour is called once per frame for every GameObject that has this script attached. You have 3 GameObjects with this script attached. Therefore, each will call Update() on their individual instance of the Score script.
I'm not sure what you exactly intend to happen, so it's hard for me to give any advice beyond pointing out the problem.
I think that you need to split this script into multiple scripts. This script is doing too much. It's violating the Single Responsibility Principal (SRP). This is one of the most important principles to follow in programming. I'd suggest splitting this into at least 3 scripts. I'd probably make the following scripts:
PlayerStatistics - Attach this script to your player object (I'm assuming that is the sprite you mentioned). It will simply hold the statistics, including score, for your player. There should only be one attached to each player.
ScoreBoard - Attach this script to your GUI component. It will take a reference to the PlayerStatistics. Notice this is a reference to the single instance that is on your Player. Your ScoreBoard script will only read the value of the score from the PlayerStatistics script.
ScoringTrigger - Attach this to your TriggerObject. It would also have a reference to the PlayerStatistics script. It would have the code that checks to see if scoring should be done, and updates the value of the PlayerStatistics script.
To avoid some events to run twice a frame use Events to get imput actions:
if(Event.current.type == EventType.MouseDown){
if(Event.current.button == 0 && allowScore && WhichScene) {
// do it once!
}
}

Categories

Resources