Unity Extension Method GameObject - unity3d

I'm trying to add an extension method to my gameobject, that's works, but my problem is the GameObject share the same result. My goal is to have a different result for each GameObject.
// AddExtension.cs
public static class GameObjectExtensions
{
private static int life;
public static int Life(this GameObject gameObject)
{
return life;
}
public static void ChangeLife(this GameObject gameObject, int numberToAdd)
{
life += numberToAdd;
}
}
And in my main code, I would like to manage GameObject like :
void Start()
{
GameObject.Find("Perso0").ChangeLife(2);
GameObject.Find("Perso1").ChangeLife(4);
GameObject[] rootGOs = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject g in rootGOs)
{
if(g.name == "Perso0")
{
Debug.Log("Perso0 : " + g.Life());
}
if(g.name == "Perso1")
{
Debug.Log("Perso1 : " + g.Life());
}
}
}
But both GameObject have 6 in "Life" ( 2 + 4 )
I whould like to get only 2 for "Perso0" with Life and 4 with "Perso1" with Life
Do you have some clue to helping me ?
Thank you and best Regards

Because your life variable is static, it's going to be the same value you're editing every time you call ChangeLife on a GameObject.
Since extension methods need to belong to static classes, and a static class can only have static members, you cannot achieve the goal you want with extension methods.
Even if you could, it's not the right way to go with the Unity paradigm. With this setup, you're essentially saying "Every GameObject in my scene has a life value," which I don't think you want to do.
Instead, you can create your own components, as below.
public class Enemy : MonoBehaviour
{
[SerializeField] private int _initialHealth = 100;
private int _health = -1;
public int health { get { return _health; } }
private void Awake()
{
_health = _initialHealth;
}
public void IncrementHealth(int health)
{
_health += health;
}
}
This is just an example, but you can make something to suit your needs.

static applied to a member means all instances share a single copy. That is, there's only one life variable, which is modified when you call ChangeLife() on any GameOject.
Since extension methods have to be in a static class, I don't think you can accomplish what you want this way.
However, you should be able to add custom properties to your players and other objects in Unity. I don't remember if they're called "custom properties" exactly, but I know some of the basic tutorials like Roll-a-Ball cover this (or at least used to).

Related

Possible for monobehaviour in one scene to fetch data that will be used in all other scenes? [duplicate]

How can I pass score value from one scene to another?
I've tried the following:
Scene one:
void Start () {
score = 0;
updateScoreView ();
StartCoroutine (DelayLoadlevel(20));
}
public void updateScoreView(){
score_text.text = "The Score: "+ score;
}
public void AddNewScore(int NewscoreValue){
score = score + NewscoreValue;
updateScoreView ();
}
IEnumerator DelayLoadlevel(float seconds){
yield return new WaitForSeconds(10);
secondsLeft = seconds;
loadingStart = true;
do {
yield return new WaitForSeconds(1);
} while(--secondsLeft >0);
// here I should store my last score before move to level two
PlayerPrefs.SetInt ("player_score", score);
Application.LoadLevel (2);
}
Scene two:
public Text score_text;
private int old_score;
// Use this for initialization
void Start () {
old_score = PlayerPrefs.GetInt ("player_score");
score_text.text = "new score" + old_score.ToString ();
}
but nothing displayed on screen, and there's no error.
Is this the correct way to pass data ?
I am using Unity 5 free edition, develop game for Gear VR (meaning the game will run in android devices).
Any suggestion?
There are many ways to do this but the solution to this depends on the type of data you want to pass between scenes. Components/Scripts and GameObjects are destroyed when new scene is loaded and even when marked as static.
In this answer you can find
Use the static keyword
Use DontDestroyOnLoad
Store the data local
3a PlayerPrefs
3b serialize to XML/JSON/Binary and use FileIO
1. Use the static keyword.
Use this method if the variable to pass to the next scene is not a component, does not inherit from MonoBehaviour and is not a GameObject then make the variable to be static.
Built-in primitive data types such as int, bool, string, float, double. All those variables can be made a static variable.
Example of built-in primitive data types that can be marked as static:
static int counter = 0;
static bool enableAudio = 0;
static float timer = 100;
These should work without problems.
Example of Objects that can be marked as static:
public class MyTestScriptNoMonoBehaviour
{
}
then
static MyTestScriptNoMonoBehaviour testScriptNoMono;
void Start()
{
testScriptNoMono = new MyTestScriptNoMonoBehaviour();
}
Notice that the class does not inherit from MonoBehaviour. This should work.
Example of Objects that cannot be marked as static:
Anything that inherits from Object, Component or GameObject will not work.
1A.Anything that inherits from MonoBehaviour
public class MyTestScript : MonoBehaviour
{
}
then
static MyTestScript testScript;
void Start()
{
testScript = gameObject.AddComponent<MyTestScript>();
}
This will not work because it inherits from MonoBehaviour.
1B.All GameObject:
static GameObject obj;
void Start()
{
obj = new GameObject("My Object");
}
This will not work either because it is a GameObject and GameObject inherit from an Object.
Unity will always destroy its Object even if they are declared with the static keyword.
See #2 for a workaround.
2.Use the DontDestroyOnLoad function.
You only need to use this if the data to keep or pass to the next scene inherits from Object, Component or is a GameObject. This solves the problem described in 1A and 1B.
You can use it to make this GameObject not to destroy when scene unloads:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
You can even use it with the static keyword solve problem from 1A and 1B:
public class MyTestScript : MonoBehaviour
{
}
then
static MyTestScript testScript;
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
void Start()
{
testScript = gameObject.AddComponent<MyTestScript>();
}
The testScript variable will now be preserved when new scene loads.
3.Save to local storage then load during next scene.
This method should be used when this is a game data that must be preserved when the game is closed and reopened. Example of this is the player high-score, the game settings such as music volume, objects locations, joystick profile data and so on.
Thare are two ways to save this:
3A.Use the PlayerPrefs API.
Use if you have just few variables to save. Let's say player score:
int playerScore = 80;
And we want to save playerScore:
Save the score in the OnDisable function
void OnDisable()
{
PlayerPrefs.SetInt("score", playerScore);
}
Load it in the OnEnable function
void OnEnable()
{
playerScore = PlayerPrefs.GetInt("score");
}
3B.Serialize the data to json, xml or binaray form then save using one of the C# file API such as File.WriteAllBytes and File.ReadAllBytes to save and load files.
Use this method if there are many variables to save.
General, you need to create a class that does not inherit from MonoBehaviour. This class you should use to hold your game data so that in can be easily serialized or de-serialized.
Example of data to save:
[Serializable]
public class PlayerInfo
{
public List<int> ID = new List<int>();
public List<int> Amounts = new List<int>();
public int life = 0;
public float highScore = 0;
}
Grab the DataSaver class which is a wrapper over File.WriteAllBytes and File.ReadAllBytes that makes saving data easier from this post.
Create new instance:
PlayerInfo saveData = new PlayerInfo();
saveData.life = 99;
saveData.highScore = 40;
Save data from PlayerInfo to a file named "players":
DataSaver.saveData(saveData, "players");
Load data from a file named "players":
PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
There is another way:
ScriptableObject
ScriptableObjects are basically data containers but may also implement own logic. They "live" only in the Assets like prefabs. They can not be used to store data permanently, but they store the data during one session so they can be used to share data and references between Scenes ... and - something I also often needed - between Scenes and an AnimatorController!
Script
First you need a script similar to MonoBehaviours. A simple example of a ScriptableObject might look like
// fileName is the default name when creating a new Instance
// menuName is where to find it in the context menu of Create
[CreateAssetMenu(fileName = "Data", menuName = "Examples/ExamoleScriptableObject")]
public class ExampleScriptableObject : ScriptableObject
{
public string someStringValue = "";
public CustomDataClass someCustomData = null;
public Transform someTransformReference = null;
// Could also implement some methods to set/read data,
// do stuff with the data like parsing between types, fileIO etc
// Especially ScriptableObjects also implement OnEnable and Awake
// so you could still fill them with permanent data via FileIO at the beginning of your app and store the data via FileIO in OnDestroy !!
}
// If you want the data to be stored permanently in the editor
// and e.g. set it via the Inspector
// your types need to be Serializable!
//
// I intentionally used a non-serializable class here to show that also
// non Serializable types can be passed between scenes
public class CustomDataClass
{
public int example;
public Vector3 custom;
public Dictionary<int, byte[]> data;
}
Create Instances
You can create instances of ScriptableObject either via script
var scriptableObject = ScriptableObject.CreateInstance<ExampleScriptableObject>();
or to make things easier use the [CreateAssetMenu] as shown in the example above.
As this created ScriptabeObject instance lives in the Assets it is not bound to a scene and can therefore be referenced everywhere!
This when you want to share the data between two Scenes or also e.g. the Scene and an AnimatorController all you need to do is reference this ScriptableObject instance in both.
Fill Data
I often use e.g. one component to fill the data like
public class ExampleWriter : MonoBehaviour
{
// Here you drag in the ScriptableObject instance via the Inspector in Unity
[SerializeField] private ExampleScriptableObject example;
public void StoreData(string someString, int someInt, Vector3 someVector, List<byte[]> someDatas)
{
example.someStringValue = someString;
example.someCustomData = new CustomDataClass
{
example = someInt;
custom = someVector;
data = new Dictionary<int, byte[]>();
};
for(var i = 0; i < someDatas.Count; i++)
{
example.someCustomData.data.Add(i, someDatas[i]);
}
example.someTransformReference = transform;
}
}
Consume Data
So after you have written and stored your required data into this ExampleScriptableObject instance every other class in any Scene or AnimatorController or also other ScriptableObjects can read this data on just the same way:
public class ExmpleConsumer : MonoBehaviour
{
// Here you drag in the same ScriptableObject instance via the Inspector in Unity
[SerializeField] private ExampleScriptableObject example;
public void ExampleLog()
{
Debug.Log($"string: {example.someString}", this);
Debug.Log($"int: {example.someCustomData.example}", this);
Debug.Log($"vector: {example.someCustomData.custom}", this);
Debug.Log($"data: There are {example.someCustomData.data.Count} entries in data.", this);
Debug.Log($"The data writer {example.someTransformReference.name} is at position {example.someTransformReference.position}", this);
}
}
Persistence
As said the changes in a ScriptableObject itself are only in the Unity Editor really persistent.
In a build they are only persistent during the same session.
Therefore if needed I often combine the session persistence with some FileIO (as described in this answer's section 3b) for loading and deserializing the values once at session begin (or whenever needed) from the hard drive and serialize and store them to a file once on session end (OnApplicationQuit) or whenever needed.
(This won't work with references of course.)
Besides playerPrefs another dirty way is to preserve an object during level loading by calling DontDestroyOnLoad on it.
DontDestroyOnLoad (transform.gameObject);
Any script attached to the game object will survive and so will the variables in the script.
The DontDestroyOnLoad function is generally used to preserve an entire GameObject, including the components attached to it, and any child objects it has in the hierarchy.
You could create an empty GameObject, and place only the script containing the variables you want preserved on it.
I use a functional approach I call Stateless Scenes.
using UnityEngine;
public class MySceneBehaviour: MonoBehaviour {
private static MySceneParams loadSceneRegister = null;
public MySceneParams sceneParams;
public static void loadMyScene(MySceneParams sceneParams, System.Action<MySceneOutcome> callback) {
MySceneBehaviour.loadSceneRegister = sceneParams;
sceneParams.callback = callback;
UnityEngine.SceneManagement.SceneManager.LoadScene("MyScene");
}
public void Awake() {
if (loadSceneRegister != null) sceneParams = loadSceneRegister;
loadSceneRegister = null; // the register has served its purpose, clear the state
}
public void endScene (MySceneOutcome outcome) {
if (sceneParams.callback != null) sceneParams.callback(outcome);
sceneParams.callback = null; // Protect against double calling;
}
}
[System.Serializable]
public class MySceneParams {
public System.Action<MySceneOutcome> callback;
// + inputs of the scene
}
public class MySceneOutcome {
// + outputs of the scene
}
You can keep global state in the caller's scope, so scene inputs and outputs states can be minimized (makes testing easy). To use it you can use anonymous functions:-
MyBigGameServices services ...
MyBigGameState bigState ...
Splash.loadScene(bigState.player.name, () => {
FirstLevel.loadScene(bigState.player, (firstLevelResult) => {
// do something else
services.savePlayer(firstLevelResult);
})
)}
More info at https://corepox.net/devlog/unity-pattern:-stateless-scenes
There are various way, but assuming that you have to pass just some basic data, you can create a singelton instance of a GameController and use that class to store the data.
and, of course DontDestroyOnLoad is mandatory!
public class GameControl : MonoBehaviour
{
//Static reference
public static GameControl control;
//Data to persist
public float health;
public float experience;
void Awake()
{
//Let the gameobject persist over the scenes
DontDestroyOnLoad(gameObject);
//Check if the control instance is null
if (control == null)
{
//This instance becomes the single instance available
control = this;
}
//Otherwise check if the control instance is not this one
else if (control != this)
{
//In case there is a different instance destroy this one.
Destroy(gameObject);
}
}
Here is the full tutorial with some other example.
you have several options.
The first one I see is to use static variables, which you will not lose their information or value passing from scenes to scenes (since they are not bound to the object). [you lose the information when closing the game, but not when passing between scenes]
the second option is that the player or the object of which you do not want to lose the information, you pass it through the DontDestroyOnLoad function
Here I give you the documentation and the sample code. [You lose the information when you close the game, but not when you go between scenes]
https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
Third is to use the playerPrefab [https://docs.unity3d.com/ScriptReference/PlayerPrefs.html]
that allow you to save information and retrieve it at any time without hanging it even after closing the game [you must be very careful with the latter if you plan to use it to save data even after closing the game since you can lose the data if you close the game suddenly , since player prefab creates a file and retrieves the information from there, but it saves the file at the end or closes the app correctly]

Is there a way to test the position change of a class with MonoBehaviour?

I would like to learn the basics of testing, how to make a test
I am using the new unity input system (OnMove), I store that input in a vector2, later I use that vector2 in a function that moves the character (ProcessMovementOfShip).
The game works, I can move the player around with WASD, but I would love to have a test that verifies that the function responsible for movement works.
I have tried watching a couple of youtube videos about testing, it feels like the entry into tests are getting to steep, I would love to learn it, I can see the importance of it, I just dont know what I am doing and how to solve the problem at hand and I am starting to feel I should just put the whole thing on a shelf and hopefully return to it later.
How do I test that the player has moved?
PlayMode Test
public class player_movement
{
[UnityTest]
public IEnumerator player_moves_when_processship_is_fed_a_vector()
{
var gameObject = new GameObject();
var playerMovement = gameObject.AddComponent<PlayerMovement>();
Vector2 startPosition = playerMovement.transform.position;
playerMovement.ProcessMovementOfShip(new Vector2(1, 0));
yield return new WaitForFixedUpdate();
Vector2 endPosition = playerMovement.transform.position;
Assert.AreNotEqual(startPosition, endPosition);
}
}
EditMode Test
public class Movement
{
[Test]
public void start_position_of_player_is_0()
{
var gameObject = new GameObject();
var playerMovement = gameObject.AddComponent<PlayerMovement>();
var startPostion = playerMovement.transform.position;
playerMovement.ProcessMovementOfShip(new Vector2(1,0));
var endPosition = playerMovement.transform.position.x;
Assert.AreNotEqual(startPostion, endPosition);
}
}
PlayerMovement.cs
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
[Header("Player Movement")]
[Range(5f, 20f)][SerializeField] float _moveSpeed = 15f;
private Rigidbody2D _rigidBody;
private Vector2 _rawInput;
void Awake()
{
_rigidBody = GetComponent<Rigidbody2D>();
if (_rigidBody == null) Debug.Log("No RigidBody2D detected!");
}
void FixedUpdate()
{
ProcessMovementOfShip(_rawInput);
}
public void ProcessMovementOfShip(Vector2 input)
{
Vector3 delta = input * _moveSpeed * Time.fixedDeltaTime;
delta += transform.position;
_rigidBody.MovePosition(delta);
}
private void OnMove(InputValue value)
{
Vector2 _rawInput = value.Get<Vector2>();
}
}
error
I try to check that the position of the character has changed, I get a "NullReferenceException" System.NullReferenceException : Object reference not set to an instance of an object
You would need to decouple the class with what Unity does and what you do.
I'll go with a simple example to demonstrate.
public class Provider : MonoBehaviour, IProvider
{
[SerializeField] private SomeClass m_someClass;
private Logic m_logic;
void Start()
{
m_logic = new Logic(this);
}
}
public interface IProvider
{}
public class Logic
{
private IProvider m_provider;
public Logic (IProvider provider)
{
m_provider = provider;
}
public int MethodToTest(int someValue)
{
}
}
At this point, we have three parts, the unity part where you will put everything that is Unity related, called Provider. This can be the Unity lifecycle such as Update, Start, any event related that the engine reports and all the connections. In this case, SomeClass is an object with some relevant data.
We have the interface which is the bridge between Provider and Logic. It will have its importance later on in the test.
And then the logic where all the code is stored.
We want to test MethodToTest, in which we are going to take an information from SomeClass and add someValue to it and return that value.
First thing, Logic does not know about Provider, it connects to it via the interface. We want to get some data from SomeClass, we will consider it has a member called Data returning an integer. We can now update the provider and the interface.
public class Provider : MonoBehaviour, IProvider
{
[SerializeField] private SomeClass m_someClass;
private Logic m_logic;
public int SomeClassData => m_someClass.Data;
void Start()
{
m_logic = new Logic(this);
}
}
public interface IProvider
{
int SomeClassData { get; }
}
You may think to pass the SomeClass object from the interface but doing it like this removes the dependency between Logic and SomeClass, they simply don't have any connection.
Now in Logic we can add the code:
public int MethodToTest(int someValue)
{
return m_provider.SomeClassData + someValue;
}
We can now move to the test
[Test]
public void LogicTest_MethodToTest()
{
// this would likely be in the Setup method to reuse provider and logic
IProvider provider = Subsitute.For<IProvider>();
Logic logic = new Logic(provider);
// Use NUnit to have the empty IProvider to return a given value when
// SomeClassData is called (else it returns default 0)
// This means that when MethodToTest will call for SomeClassData, it will return 5
provider.SomeClassData.Returns(5);
int result = logic.MethodToTest(10);
Assert.AreEqual(result, 10);
}
Thanks to the Interface, we no longer need to create all kind of object, the test is limited and NSubstitute takes care of the mocking (creating an empty object of an interface).
Now, this is fine but only one test is not so good, so we can start adding more test. We can copy paste the Test, rename the method and change the value to check but this is redundant. Let's use TestCase instead.
[TestCase(0, 0, 0)]
[TestCase(5, 0, 5)]
[TestCase(5, 5, 10)]
public void LogicTest_MethodToTest(int someClass, int someValue, int expected)
{
// this would likely be in the Setup method to reuse provider and logic
IProvider provider = Subsitute.For<IProvider>();
Logic logic = new Logic(provider);
// Assign the given value for flexible testing
provider.SomeClassData.Returns(someClass);
int result = logic.MethodToTest(someValue);
// compare with expectation
Assert.AreEqual(result, expected);
}
Each value given in the test case will be passed as parameter to the method when NUnit calls it. Instead of static values, you can now run a set of test to make sure the method works in many cases. You should then add corner cases like negative values or max int and so on to fiddle with your method until all green.
In this context, we do not text the Unity part. Simply because we know it works. Unity did the tests already so there is no need to check if the input works or if Start is being called properly.
Logic and the test rely on the fact that SomeClass would return specific values. In this case, we are only testing Logic so we assume SomeClass was implemented and tested properly so we don't need to test it here.
The IProvider can mock values via the return method.
To sum it up: Remove all the logic from Unity class (Provider) and move them to a Logic class. Create a bridge between Unity class and logic via an interface (IProvider). Anything needed in Logic from Provider goes through IProvider so to remove any dependency.
In the test, create the mock IProvider and pass it to the newly created Logic object. You can start testing.
The real benefit is that you now know the method works and if you were to modify it later on, you have your test to confirm it still does it all right.

for some reason, i can only generate up to 3 prefabs with this script. otherwise, I get a MissingReferenceException

I tried to make a dynamic scroll view to display selectable options that can be both generated or destroyed during runtime but for some reason it only lets me to generate up to three, otherwise I get a Missing Reference Exception.
When I generate one then delete it, it will also give a Missing Reference Exception.
But when I generate two, then delete one or even two, everything works just fine no matter what I do.
How can I fix this strange Behaviour?
The code:
private List<JL> JLList;
public List<GameObject> JLID;
public GameObject ButtonPrefab;
public GameObject PrefabParent;
public GameObject JLMenuManager;
public GameObject ScrollViewContent;
private void Update()
{
JLList = JLMenuManager.GetComponent<AddJL>().JLList;
CheckForChange();
}
private void GenerateOptions()
{
foreach (Transform child in ScrollViewContent.transform)
{
Destroy(child.gameObject);
}
for (int i = 0; i < JLList.Count; i++)
{
GameObject newJLOption = Instantiate(ButtonPrefab, PrefabParent.transform);
JLID.Add(newJLOption);
newJLOption = JLID[i];
int JLIndex = i;
newJLOption.GetComponent<Button>().onClick.AddListener(() => LoadJLOptions(JLIndex));
}
}
private void LoadJLOptions(int JLIndex)
{
SendMessage("SetJL", JLIndex);
Debug.Log(JLIndex);
}
private void CheckForChange()
{
int allJLOptions = ScrollViewContent.transform.childCount;
if (allJLOptions != JLList.Count)
{
GenerateOptions();
}
}
It seems like it is only spawning in how many JLList.Count is set to, open your script JLList and there should be a variable called count . Check how many that is set to. If it is 3, then that is your issue. If not, then follow what #BugFinder said and try remove them from the list

Access variable from a different scene in Unity

I am fairly new to Unity and C# and am having some trouble. I am designing a 2d game, which has multiple levels. Each level contains a LevelManager which stores whether the level has been completed or not. They also have the DontDestroyOnLoad command in them. I want to access all the LevelManager gameObjects in my game and then store them in a level select scene. I want to use the win/lose bool to determine if the level has been completed so I can unlock the next level. To be clear, I want a way to access all the LevelManagers in my ENTIRE game and then store them as an array in a GameManager script. How do I do that?
Below is the LevelManager script which declares whether the level has been won or not.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneController : MonoBehaviour
{
private GameObject[] StartHouseCount;
private GameObject[] StartDragonCount;
private GameObject[] LiveDragonCount;
private GameObject[] FinishedHouseCount;
public int NumOfHouses;
public int NumOfFinishedHouse;
public int NumOfDragons;
public int LiveNumOfDragons;
public GameObject[] Players;
public GameObject CurrentPlayer;
[Header("Player")]
public float RefuelRate;
public float RepairRate;
public GameObject canvas;
public bool GameIsPaused = false;
private GameObject MainPlayer;
public bool Win = false;
public int Level;
// Start is called before the first frame update
void Start()
{
CurrentPlayer = Players[0];
StartHouseCount = GameObject.FindGameObjectsWithTag("House");
StartDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
NumOfHouses = StartHouseCount.Length;
NumOfDragons = StartDragonCount.Length;
MainPlayer = Players[0];
}
// Update is called once per frame
void Update()
{
GameIsPaused = canvas.GetComponent<PauseMenu>().GameIsPaused;
LiveDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
LiveNumOfDragons = LiveDragonCount.Length;
FinishedHouseCount = GameObject.FindGameObjectsWithTag("ThankYou");
NumOfFinishedHouse = FinishedHouseCount.Length;
if (Input.GetKeyDown(KeyCode.Alpha1))
{
CurrentPlayer = Players[0];
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
CurrentPlayer = Players[1];
}
if (NumOfFinishedHouse == NumOfHouses)
{
SceneManager.LoadScene("WinScene");
}
if (MainPlayer == null)
{
SceneManager.LoadScene("LoseScene");
}
if (MainPlayer.GetComponent<BasicHelicopterController>().CurrentFuel <= 0 || MainPlayer.GetComponent<BasicHelicopterController>().CurrentHealth <= 0)
{
SceneManager.LoadScene("LoseScene");
}
}
}
You can create a ScoreManager script to store scores of each level using PlayerPrefs and attach this script to a GameObject in the first scene.
public class ScoreManager: MonoBehavior {
public static ScoreManager Instance;
const string LEVEL_KEY = "SCORE_";
const string MAX_LEVEL_KEY = "MAX_LEVEL";
public static int MAX_LEVEL {
get { return PlayerPrefs.GetInt(MAX_LEVEL_KEY, 1); }
set {
if(value > MAX_LEVEL) {
PlayerPrefs.SetInt(MAX_LEVEL_KEY, value);
}
}
}
void Awake() {
if(Instance != null) {
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this);
}
public void SaveScore(int level, float score) {
PlayerPrefs.SetFloat(LEVEL_KEY + level, score);
MAX_LEVEL = level;
}
public float GetScore(int level) {
return PlayerPrefs.GetFloat(LEVEL_KEY + level, 0);
}
public bool IsLevelUnlocked(int level) {
// You can write your own level-unlock logic here.
return level <= MAX_LEVEL;
}
...
}
Then you can just call its functions and properties from other scripts.
...
public void GameOver() {
ScoreManager.Instance.SaveScore(level, score);
}
...
Having all LevelManger scripts with DontDestroyOnLoad will cause a memory leak and sometimes it will affect the game performance.
First, DontDestroyOnLoad is not supposed to do this - main usage for this is to implement things like unity-singleton (easy-to-use template). It means, you`ll have one instance of levelManager, not one per level.
To do it in your way, you need to load all the scenes additively, and get all the LevelManager instances. Or you can load all scenes one-by-one, getting level name and it`s isPassed value from current LevelManager. It can take a while, and its a wrong way.
The right way is to store inter-scene data in ScriptableObject models. It`s like a MonoBehaviour, but not related to scene - it just lays somewhere in project.
So, in your case, you can create ScriptableObject called LevelProgress. It will store the list of all passed levels. It will have public method LevelPassed(string levelName), as a parameter you can use anything - levels name, levels index in build, scene itself, whatever. Every time, when player passes any level, your LevelManager (remove DontDestoryOnLoad from it), which has a reference to LevelProgress scriptable object, will call LevelProgress.LevelPasses(currentLevel). And then, if you need to know, was some level passed, or not, you can just check, is LevelProgress.PassedLevels list contains this level.
Additionally, you need to implement between-sessions persistance for this scriptable object. IMO, the best way for this is to use JSONUtility to convert ScriptableObject into JSON string, and then write it to PlayerPrefs.
I seemed to have figured it out. I created a gameObject containing a GameManagement script, that had the DontDestroyOnLoad line. I had also added a specific tag. I then searched for that object in each level and updated my values to that. The GameManagement script had an array of bools for levels completed and levels unlocked. Each levelmanager decided whether the level was won and updated that. Using that I determined what level was unlocked. I did though need to use Fire King's Awake Command. It ensures that there are no other copies of the script in the game. Solves my problems.

Unity Adding points to a user only once after collision

So I'm getting ready to create my first game, I just finished classes on the C# language so I apologize if I'm using stuff such as interfaces wrong and all that. However, for my question; I'm trying different things and seeing what works. I've created a coin, and a player. The coin works as it should, however sometimes when I collect it, it will give me twice the points it should. The coins value is 15, sometimes when I collect a coin it'll add 15 points, other times it will add 30. How do I prevent this from happening.
Here's my code:
Coin Controller Class:
public class CoinController : MonoBehaviour, IEconomy {
private int MoneyValue;
void Start () {
MoneyValue = 15;
}
void Update () {
}
void OnTriggerEnter(Collider col) {
if (col.CompareTag("Player")) {
Destroy(transform.gameObject);
Value();
}
}
public int Value() {
return EconomyController.Money += MoneyValue;
}
}
Economy Controller:
public class EconomyController : MonoBehaviour{
public static int Money;
void Start() {
Money = 0;
}
}
Economy Interface:
public interface IEconomy {
int Value();
}
I would like to point some things about your code:
A good practice when declaring variables is using lowerCamelCase:
thisIsLowerCamelCase
ThisIsNot
This is a variable name convention that is largely used in programming to differentiate Methods and Classes from variables.
Another thing I've noticed is that your "Money" variable is static and it is still being updated on your CoinController. I'd set this variable to be private int variable and use a setter to update it. With that in mind... Have you tried to use Debug.Log to check if the OnTriggerEnter is triggering twice before the object is destroyed?
Simply write:
Debug.Log ("This should only happen once!");
And play the game. If your console shows this message two times, this trigger is being called twice. Another thing that you might notice is that you are calling the Value () method after you called the Destroy (transform.gameObject).
I would do something like:
public class CoinController : MonoBehaviour{
private int moneyValue = 15;
private EconomyController economyController;
void Start (){
economyController = FindObjectOfType (typeof (EconomyController)) as EconomyController;
}
void OnTriggerEnter (Collider col) {
if (col.CompareTag("Player")) {
AddValue();
}
}
public int AddValue() {
EconomyController.money += moneyValue; //Option one.
EconomyController.AddMoney (moneyValue) ; //Option two.
DestroyGameObject ();
}
private void DestroyGameObject (){
Destroy(transform.gameObject);
}
}
Using the clean code principles, option 2 is using a public void function created inside the EconomyController class changing a private variable.
My intuition tells me that you are probably collecting two coins at the time. I'm not sure how you are setting out the coins but I've had a similar problem before.
Imagine a game of snake. Lets say you've programmed it so once you eat a square you create a new one to a random location. There is a probability that the new square would appear inside the snake so it would instantly be eaten. This could be why it happens only some of the time.
Try disabling the collider before you destroy it.
Destroying a gameobject isn't instant and it's (annoyingly) quite easy to set off triggers multiple times.
void OnTriggerEnter(Collider col) {
if (col.CompareTag("Player")) {
// Pseudo Code: GetComponent<TheColliderItIs>().Enabled = false;
Value();
Destroy(transform.gameObject);
}
}