I am using PlayerPerfabs to store player data like which level the player has unlocked and the point has got during the game, and I want the PlayerPerfabs data updated regularly(like per 5 seconds) during playing, so I made a class called ApplicationModel to hold the global data like points player get the level info etc..
public ApplicationModel: MonoBehaviour{
public static int point {get;set;}
//try to load the PlayerPerfabs when start game.
static ApplicationModel(){
if(PlayerPerfabs.HasKey("point")){
point = PlayerPerfabs.GetInt("point");
}else{
point = 0;
}
}
//coroutine to set the data to PlayerPerfabs
IEnumerator Save()
{
while (true)
{
Debug.Log("setting playerperfab ....");
yield return new WaitForSeconds(5f);
ApplicationModel.RestorePerfab();
}
}
public static void RestorePerfab(){
PlayerPerfabs.SetInt("point", point);
}
private void Start(){
StartCoroutine(Save());
}
}
But the coroutine was never executed according to the console log (cause the log was never printed).
Maybe because I didn't attach this script to a gameobject but once I attach this to a game object, then when the scene changed or that gameobject is destroyed, the regularly saving will ended, right? So how to do this?
You need to create a GameObject and then attach this script to it if you want it to work. If you're changing scenes and want it to continue working you could call DontDestroyOnLoad on that GameObject and it won't get destroyed when you switch scenes.
Related
I am trying to do when i destroy all boxes something happen.
My code is;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class destroy : MonoBehaviour
{
private string BALL_TAG = "ball";
public AudioClip coin;
public AudioSource src;
public float numBox = 120f;
public bool isDestroyed;
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag(BALL_TAG))
{
src.clip = coin;
src.Play();
Destroy(gameObject);
isDestroyed = true;
}
}
private void Update()
{
boxes();
}
public void boxes()
{
if(isDestroyed == true)
numBox -= 1f;
if(numBox == 119)
SceneManager.LoadScene("mainManu");
}
private IEnumerator Two()
{
yield return new WaitForSeconds(1f);
Destroy(gameObject);
}
}
But it doesn't work.
It is suppose to do when I broke 1 box it sends me to menu.
I think its problem in "numBox -= 1f;" because I don't know hot to make this.
I don't understand your code completely. So, I need to make some assumptions.
I think the Script is attached to the box and every box has this Script. I also think, that your player Shoots Ball. Those Balls have a collider with an ball tag.
There are multiple problems with your code.
The first one is, that your count variable, numBox, is saved in your destroy Script, which is placed on each box.
this means, that every Box is counting for itself.
You have to centralize this. There are multiple ways for doing this.
One way is to declare this variable as static(https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/static)
This is not best practice, but works.
A Better way is to have a Script on Your Player, which holds this number and every Box searches for this Script and change this number if it is destroyed.
The second big Problem is, that your are doing some really weird thing in your Update and the collision handling
First of all, you are setting isDestroyed to true. Then in your boxes method, which is called in every Frame, you are decrementing your numBox variable by one, if this is Destroyed is true.
So if your Box gets hit, you are decrementing every frame.
After that you are checking every frame if your numBox is 119
If so, you change the Scene.
This is the reason, why you are getting to your MainMenu after only one boy
This behaviour is very weird, because it is totally unnecessary. You can reduce your variable directly in in your OnCollisionEnter2D Method.
There are some little things, which can be improved.
When you are trying to play a Sound, you don't have to specify the AudioClip in code. You can assign this directly in Unity on the AudioSource Component via drag and drop. This makes your code simpler.
You are not calling the Two Coroutine. You've specified this Coroutine but don't call it.
//Script on Player
public class PlayerBoxDestroyManager:MonoBehaviour
{
public int StartBoxes = 120;
private int Boxes;
private void Start()
{
Boxes = StartBoxes;
}
public void DestroyBox()
{
//Reduce our Boxes count
//This is equal to Boxes -= 1
// Boxes = Boxes -1
Boxes--;
// If we have less or zero Boxes left, we End call our EndGame methode
if(Boxes <= 0)
{
EndGame();
}
}
private void EndGame()
{
// We change the Scene to the mainMenu
SceneManager.LoadScene("mainManu");
}
}
```
//Script on all Boxes
public class Box : MonoBehaviour
{
public string Balltag = "ball";
//Audio Source the Audio Clip has to be assigned in the Unity editor
public AudioSource Coin;
private void OnCollisionEnter2D(Collision2D collision)
{
//Check it colliding Object has the right Tag
if(collision.transform.tag == Balltag)
{
//Get the reference to the Player Script
PlayerBoxDestroyManager PBDM = FindObjectOfType<PlayerBoxDestroyManager>();
//We can now access the Destroy Box Methode
PBDM.DestroyBox();
//Play the sound
Coin.Play();
//If we destroy our Object now, the Sound would also be deletet.
//We want to hear the sound, so we have to wait, till the sound is finished.
StartCoroutine(WaitTillAudioIsFinished());
}
}
IEnumerator WaitTillAudioIsFinished()
{
//we wait till the sound is finished
while (Coin.isPlaying)
{
yield return null;
}
//if finished, we destroy the Gameobject
Destroy(gameObject);
}
}
I hope I helped you. If you have questions, feel free to ask.
And sorry for my English:)
I have a game wehre you are a cube and you dodge obstacles, I just implemented the ability to change the color of the cube like changing skin. I did that by assigning a different material to the player when he presses "2".
Here is the script:
void Update()
{
if (Input.GetKeyDown("2"))
{
Object.GetComponent<MeshRenderer>().material = Material1;
}
}
When you die the scene resets and when you win a new scene is loaded, I would like the game to remember the material change even after the scene is reset or a new scene is loaded. I have done some research and found something called "PlayerPrefs" and I have been playing around with it but nothing even got close to working and I didn't really understand what i was doing.
I really want to understand how this works becuase I know i will be using it alot when making games. Can someone help me understand?
Thanks.
Create one gameobject and apply this script
public class SavingMaterial : MonoBehaviour
{
public static SavingMaterial instance;
public Material mat;
void Awake()
{
if(instance == null)
{
instance = this;
DontDestroyOnLoad(base.gameObject);
}
else
{
Destroy(base.gameObject);
}
}
public void StoreMaterial(MeshRenderer mesh)
{
mat = mesh.material;
}
}
When you want to store material call this function like this
SavingMaterial.instance.StoreMaterial(you meshrenderer component);
and when you need this material just get from this class like this
material = SavingMaterial.instance.mat;
and note that if you quit the game you loose saved material as it is store in variable otherwise scene change and reset won't affect it.
you only save int,string and bool in playerprefs. you have to store values of materials into string and then save this string into playerprefs.
alternatively you can assign this material to local variable and set that script on Dontdestroyonload so it will not reset as scene is destroy or reset.
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.
I've been working on a simple 2D game in unity and it just has three scenes, the start scene, the game scene, and the game over scene. I want to display the score from the game in the game over screen. I created a score manager game object in the game scene that uses the DontDestroyOnLoad() function to carry it over into the game over screen and I gave it access to the score which is managed by the game manager. I've been debugging my code and the score is translated over into the score manager and is maintained when the game over screen loads, but for some reason it won't let me update the score text object. Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour {
public static ScoreManager Instance;
private GameController gameController;
private int scoreInstance;
private Text scoreText;
// When scene is first loaded
void Awake() {
this.InstantiateController();
}
// Use this for initialization
void Start () {
GameObject gameControllerObject = GameObject.FindWithTag("GameController");
if (gameControllerObject != null)
{
gameController = gameControllerObject.GetComponent<GameController>();
}
GameObject scoreTextObject = GameObject.FindWithTag("ScoreText");
if (scoreTextObject != null)
{
scoreText = scoreTextObject.GetComponent<Text>();
}
scoreInstance = 0;
scoreText.text = "";
}
// Update is called once per frame
void Update () {
scoreInstance = gameController.score;
Debug.Log("Score: " + scoreInstance.ToString());
scoreText.text = scoreInstance.ToString();
}
private void InstantiateController ()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(this);
}
else if (this != Instance)
{
Destroy(this.gameObject);
}
}
}
So I tried to programmatically gather the "score text" ui component in the start function because I figured I can't just make it public and drag in the text component because the score manager is actually in a different scene than the score text object. I also tried adding this whole bit of code to gather the text component into the update function so that it can do that when the score manager is actually a part of game over screen. Nothing seems to work and I have no idea why. Can anybody please help me with this? Also I keep getting a "NullReferenceException: Object reference not set to an instance of an object" error. Thanks in advance for any help.
Unity Start function is only called the first time the script is enabled, i.e. not every time the scene changes for a DontDestroyOnLoad object.
So if you need to wire up some changes after a scene change, you need to either detect the scene change, or have an object that starts in that scene trigger the code you want to run.
Having another object on the new scene trigger things is easy and pretty fool-proof, but there's a builtin function you can add to your other objects:
void OnLevelWasLoaded(int currentLevel)
{
}
This will be called on level changes, and give you the level's number (not name sadly). However, the above is deprecated and they want you to use Unity's SceneManager, so the proper way to set this up is now:
Unity 5 OnLevelWasLoaded?
Start()
{
SceneManager.sceneLoaded += this.OnLoadCallback;
}
void OnLoadCallback(Scene scene, LoadSceneMode sceneMode)
{
// you can query the name of the loaded scene here
}
I'm trying to write a spawn controller script. Fundamentally I'm also asking to see if there's a better way to do this?
What:
Every x seconds, SpawnController.cs selects a random unit, start, PitStop and final positions from a series of arrays.
It calls 'SpawnSingle.cs' with these variables.
'SpawnSingle.cs' instantiates the GameObject, sends the destination to 'navMove' script attached to the GameObject once when created, waits for x seconds when it arrives and changes destination.
Question:
How do I make each instance of my called script (SpawnSingle)
'wait' for x seconds midway, as it's controlling the gameObject. I
can't use a coroutine to stop it.
How do I pass in the second set of coordinates after? It doesn't
seem to work when using the SpawnController.
//In SpawnController.cs
...
...
private Transform currentDestination;
private NavMove moveScript;
private GameObject newEnemy;
public static SpawnSingle newSpawn = new SpawnSingle();
void Start()
{
// To build a function to randomise the route and spawn at intervals
spawnCounter = 0;
Spawn();
spawnCounter++;
Spawn();
void Spawn()
{
newSpawn.SpawnEnemy(Enemies[spawnCounter], SpawnPoint[spawnCounter], PitStop[spawnCounter], Destination[spawnCounter]);
}
SpawnSingle.cs then assigns the first stop (pitStop) and tells it to move there using navAgents.
Problem:
When it arrives at PitStop location, I want it to wait for a few seconds, then continue on to the final destination.
This all worked OK for single instances without the Controller.
// in SpawnSingle.cs
private Transform currentDestination;
private NavMove moveScript; // This script moves the navAgent
private GameObject newEnemy;
public void SpawnEnemy(GameObject Enemies, Transform SpawnPoint, Transform PitStop, Transform Destination)
{
newEnemy = GameObject.Instantiate(Enemies, SpawnPoint.position, Quaternion.identity) as GameObject;
moveScript = newEnemy.GetComponent<NavMove>();
currentDestination = PitStop;
moveScript.destination = currentDestination;
if (arrivedAtP())
{
// Stop and wait x seconds
moveScript.nav.enabled = false;
// ***HELP HERE*** How do I make this script wait? Coroutines don't work when this script is called from an extenral source it seems?
// Wait for x seconds--
//Continue moving to final destination
//*** HELP HERE*** When instantiated from an external script, this doesn't continue to pass in the new location?***
moveScript.nav.enabled = true;
currentDestination = Destination;
moveScript.destination = currentDestination;
}
}
I think the best way to go about solving this is to make separate components for each behaviour.
Right now your spawner is responsible for 3 things:
1) making enemies, 2) setting initial nav points, 3) updating nav points later.
I would advise making your spawner only responsible for spawning objects.
Then make a navigator component that creates the nav points and pit stops etc.
so something like this:
public class Spawner : MonoBehaviour {
//only spawns, attached to some gameobject
public GameObject prefabToSpawn;
public GameObject Spawn() {
//instantiate etc..
GameObject newObject = Instantiate(prefabToSpawn);
return newObject
}
}
public class EnemyManager : MonoBehaviour {
//attached to an empty gameObject
public Spawner spawner;
public Enemy CreateNewEnemy () {
GameObject newEnemy = spawner.Spawn ();
// add it to list of enemies or something
//other stuff to do with managing enemies
}
}
public class Navigator : MonoBehaviour {
//Attached to Enemy prefab
Destination currentDestination;
public float changeDestinationTime;
void Start() {
currentDestination = //first place you want to go
InvokeRepeating ("NewDestination", changeDestinationTime, changeDestinationTime);
}
void NewDestination() {
currentDestination = // next place you want to go
}
}
Obviously that's not a complete solution but it should help get you pointed in the right direction (and change directions every 8 secs :D ). Let me know if I misunderstood what you're trying to do!