Is there a better way to write my item/weapon unlock system? - c#

I'm really struggling with creating a weapon/item unlock system via an in-game shop. The player should only be able to select weapons at indexes that have been unlocked or bought.
I've wasted two days on the best way to achieve this and I'm still not anywhere near a solution.
Currently this is what I have:
I have a WeaponUnlock script that handles a dictionary of weapons and another WeaponSwitcher script that access vales from that script to check if it is unlocked or not.
public class WeaponUnlockSystem : MonoBehaviour
{
private Dictionary<string, bool> weaponUnlockState;
void Start()
{
// Seeding with some data for example purposes
weaponUnlockState = new Dictionary<string, bool>();
weaponUnlockState.Add("Shotgun", true);
weaponUnlockState.Add("Rifle", true);
weaponUnlockState.Add("FireballGun", false);
weaponUnlockState.Add("LaserGun", false);
weaponUnlockState.Add("FlamethrowerGun", false);
weaponUnlockState.Add("SuperGun", false);
}
public bool IsWeaponUnlocked(string weapon_)
{
if (weaponUnlockState.ContainsKey(weapon_))
return weaponUnlockState[weapon_]; // this will return either true or false if the weapon is unlocked or not
else
return false;
}
public void UnlockWeapon(string weapon_) //this will be called by the button..
{
if (weaponUnlockState.ContainsKey(weapon_))
weaponUnlockState[weapon_] = true;
}
}
The WeaponSwitchergets the name of the weapon from the nested children every time the nextWeapon button is pressed:
weaponName = this.gameObject.transform.GetChild(weaponIdx).name.ToString(); //get the name of the child at current index
if (weaponUnlock.IsWeaponUnlocked(weaponName)) //ask weaponUnlockSystem if this name weapon is unlocked or not, only enable the transform if true is returned
selectedWeapon = weaponIdx;
This...... works..... but I know is not practical. As the script sits on a game object and at every time it is called the Start() is called that will reset all the unlocked values. Also will I have to make it persistent via DontDestroyOnLOad() through scenes?
I can get use PlayerPrefs and set a value for every weapon, but that is also not ideal and also to avoid it being reset every time someone opens my game I would have to perform a checks of PlayerPrefs.HasKey() for every weapon. Also not practical.
There must be a better way of doing this. Its funny that I cannot find much help online for this and dont know how everyone is getting around this.
Thanks

You create a class that is not a GameObject, just a normal class. There you can initialize the dictionary and have methods to unlock or check if a weapon is unlocked.
As I don't know what your architecture of the rest of the code looks like, I will go ahead and assume that you have some kind of a persistent player model.
You could just add the WeaponUnlockSystem to your player GameObject and manage the unlocks from there.
To me this would make the most sense as the player is probably the one unlocking the weapons so the WeaponUnlockSystem should be attached to him.
Here is a really basic code example:
public class WeaponUnlockSystem {
private Dictionary<string, bool> weaponUnlockState;
public WeaponUnlockSystem()
{
weaponUnlockState = new Dictionary<string, bool>();
weaponUnlockState.Add("Shotgun", true);
weaponUnlockState.Add("Rifle", true);
weaponUnlockState.Add("FireballGun", false);
weaponUnlockState.Add("LaserGun", false);
weaponUnlockState.Add("FlamethrowerGun", false);
weaponUnlockState.Add("SuperGun", false);
}
public bool IsWeaponUnlocked(string weapon)
{
if (weaponUnlockState.ContainsKey(weapon))
return weaponUnlockState[weapon];
else
return false;
}
public void UnlockWeapon(string weapon)
{
if (weaponUnlockState.ContainsKey(weapon))
weaponUnlockState[weapon] = true;
}
}
public class Player : MonoBehaviour {
private WeaponUnlockSystem weaponUnlockSystem;
void Start()
{
weaponUnlockSystem = new WeaponUnlockSystem();
...
}
public void NextWeapon(string weapon)
{
...
}
}
I would also advice against using a static class for convenience here. Static should only be used, if you don't want to change the state of an object or don't need to create an instance of an object.

I had a Unity project recently where we needed to hold and manipulate some simple data between scenes. We just made a static class that doesn't extend monobehaviour.
Also, if you're looking for efficiency, why not just maintain an array of unlocked weapons?
All in all, why not try something like this:
public static class WeaponUnlockSystem
{
private static string[] unlockedWeapons = InitialWeapons();
private static string[] InitialWeapons(){
string w = new string[]{
"Shotgun",
"Rifle",
}
}
public static bool IsWeaponUnlocked(string name)
{
int i = 0;
bool found = false;
while(i < unlockedWeapons.Length && !found){
if (string.Equals(unlockedWeapons[i],name)){
found = true;
}
i++;
}
return found;
}
public static void UnlockWeapon(string name)
{
string[] weapons = new string[unlockedWeapons.Length+1];
int i = 0;
bool found = false;
while(i < unlockedWeapons.Length && !found){
if (string.Equals(unlockedWeapons[i],name)){
found = true;
} else {
weapons[i] = unlockedWeapons[i];
}
}
if(!found){
weapons[unlockedWeapons.Length] = name;
unlockedWeapons = weapons;
}
}
I'm not running my c# IDE, so I apologise if there's any syntax errors. Should be efficient, simple and - as it's static - you shouldn't need to put it on an empty GameObject to have it work.

Related

How can I store a gameobject in a variable, destroy it in the scene, but not in the variable?

I have an inventory that can store up to 5 items and every item in the scene is different, there are no duplicates. There's gonna be like one letter, one book, one gun, etc. in the scene.
The inventory is displayed on screen with 5 slots that I can iterate through with the mouse wheel.
So, instead of using a list or a linked list, I have an array of InventoryObjects and an InventoryObject contains a slot displayed on screen and the item displayed in the slot. It's basically like the equipped item bar in Minecraft. You have some slots with an item per slot and you can use whatever item you have currently selected. Only difference being I have 5 slots and Minecraft has 10.
using UnityEngine;
public class InventoryObject
{
public GameObject ItemObject;
public GameObject SlotObject;
// Only used for initialization
public InventoryObject(GameObject AddSlot)
{
SlotObject = AddSlot;
ItemObject = null;
}
}
The problem is that I want to pick up an item, add it to the inventory and destroy it, then instantiate it when selected. However, if I do that, the item will be added to the inventory and when I use Destroy() it also destroys the item in the inventory.
Here's the player script:
private void Update()
{
// _input.grab detects when I press the key to grab an item
if (_input.grab)
{
Grab();
_input.grab = false;
}
}
void Grab()
{
if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed
Inventory.instance.Add(objectToGrab);
Destroy(objectToGrab);
}
And here's the inventory script:
public class Inventory : MonoBehaviour
{
public static Inventory instance;
public int maxItems;
// Slots are added in editor
public GameObject[] InventorySlots;
public InventoryObject[] SlotList = new InventoryObject[5];
void Awake()
{
instance = this;
// Init list of InventoryObjects
for (int i = 0; i < 5; i++)
{
SlotList[i] = new InventoryObject(InventorySlots[i]);
}
}
public void Add(GameObject item)
{
if (item == null) return; // Guard clause
// Adding the item in the first available slot
for (int i = 0; i < maxItems; i++)
{
// Detecting an available slot
if (SlotList[i].ItemObject == null)
{
// Adding the item and getting out of the loop
SlotList[i].ItemObject = item;
i = maxItems;
}
}
}
}
I have found someone with a similar issue and the comments suggested object pooling. But I don't generate a bunch of objects quickly, I only generate one and it's a specific one, it's not like a bullet taken from a pool of a thousand bullets. So I don't think object pooling is suitable here and I'm wondering how I can solve my problem.
If I can't just copy an item, destroy the original and instantiate the copy, then how about disabling the item and enabling it when necessary? I'm just not sure how that would work because if the item is disabled, it's still in the scene, so could it conflict with other things or is it like if it wasn't there at all from the player's POV?
Disabling a GameObject will also disable any script attached to it, this should be the only drawback of using this solution.
If you want scripts and components attached to it to still be working while the item is hidden, you could simply disable the renderer instead.
Here's a small code snippet to illustrate my example, although I wouldn't recommend using the GetComponent function outside of Unity's Awake and Start event functions.
Disabling the item's renderer :
private void Grab()
{
if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed
Inventory.instance.Add(objectToGrab);
objectToGrab.GetComponent<Renderer>().enabled = false;
}
But as I think that disabling the Item's renderer shouldn't be delegated to a player function, here's a better implementation of this solution using a proper component for items that can be grabbed by the player.
IGrabItem interface :
public interface IGrabItem
{
public bool Enabled { get; set; }
}
Script to be attached to items that can be grabbed :
public class Item: MonoBehaviour, IGrabItem
{
[SerializeField] private Renderer itemRenderer;
private void Awake()
{
if (itemRenderer == null)
{
itemRenderer = GetComponent<Renderer>();
}
}
public bool Enabled
{
get => itemRenderer.enabled;
set => itemRenderer.enabled = value;
}
}
We add a new line in the Add function of Inventory :
public class Inventory : MonoBehaviour
{
public void Add(IGrabItem item)
{
item.Enabled = false;
// Do your thingy with the item here
}
}
Disabling the item is not the responsability of the Grab function anymore :
private void Grab()
{
if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed
Inventory.instance.Add(objectToGrab);
}

PUN2: Specify an instanced player gameObject through RPC call

I'm aware you can't pass gameObject references through RPC calls since every client uses it's own unique references for every instanciation. Is there a way to refer to a specific gameObject on ALL clients via an RPC call?
Here's a sample of what i'm trying to accomplish
if(Input.GetKeyDown("r")){
PhotonView.RPC("ChangeReady", RPCTarget.AllBuffered, gameObject)
}
[PunRPC]
void ChangeReady(GameObject PLAYER) {
PlayerScript PSCRIPT = PLAYER.GetComponent<PlayerScript>();
if (PSCRIPT.READY)
{
PSCRIPT.GetComponent<PlayerScript>().READY = false;
}
else {
PSCRIPT.READY = true;
}
}
I am trying to make a ready check system for players so they can toggle ready on and off and the information is transmitted to the other clients. Not sure how to refer to the specific gameObject since as previously mentioned it's impossible to do in RPC.
How would I go about doing this?
There are a few ways of doing this, if all you want to do is set a ready state:
a) Use CustomProperties, which allows you to store player specific data in a key-value pairs. and then, you can locally check for these values on your player(s) GameObjects and change their behaviours with your RPC. here is an Example:
bool ready = (bool)PhotonNetwork.player.customProperties["Ready"];
Hashtable hash = new Hashtable();
hash.Add("Ready", true);
PhotonNetwork.player.SetCustomProperties(hash);
Follow this link for info on CustomProperties
b) Use OnPhotonInstantiate by implementing IPunMagicInstantiateCallback On your Monobehaviour
void OnPhotonInstantiate(PhotonMessageInfo info)
{
// e.g. store this gameobject as this player's charater in Player.TagObject
info.sender.TagObject = this.GameObject;
}
the TagObject helps you associate a GameObject with a particular player. So once they are instantiated, This is called automatically, and Your GameObject can be tied to the player and retrieved using Player.TagObject and Execute your ready state logic (Via RPC or any other way)
Follow this link for info about OnPhotonInstantiate
See Photon RPC and RaiseEvent => "You can add multiple parameters provided PUN can serialize them" -> See Photon - Supported Types for Serialization
=> No it is not directly possible.
Solution
You will always need a way to tell other devices which object you are referring to since they don't have the same refernences as you.
If you are referring to objetcs of your Photon Players, though you can instead send over the
PhotonNetwork.LocalPlayer.ActorNumber
and on the other side get its according GameObject via the ActorNumber.
There are multiple ways to do so.
I once did this by having a central class like
public class PhotonUserManager : IInRoomCallbacks, IMatchmakingCallbacks
{
private static PhotonUserManager _instance;
private readonly Dictionary<int, GameObject> _playerObjects = new Dictionary<int, GameObject>();
public static bool TryGetPlayerObject(int actorNumber, out GameObject gameObject)
{
return _instance._playerObjects.TryGetValue(actorNumber, out gameObject);
}
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
_instance = new PhotonUserManager();
PhotonNetwork.AddCallbackTarget(_instance);
}
public void OnCreateRoomFailed(short returnCode, string message){ }
public void OnJoinRoomFailed(short returnCode, string message){ }
public void OnCreatedRoom() { }
public void OnJoinedRoom()
{
Refresh();
}
public void OnLeftRoom()
{
_playerObjects.Clear();
}
public void OnJoinRandomFailed(short returnCode, string message){ }
public void OnFriendListUpdate(List<FriendInfo> friendList) { }
public void OnPlayerEnteredRoom(Player newPlayer)
{
Refresh();
}
public void OnPlayerLeftRoom(Player otherPlayer)
{
if(_playerObjects.ContainsKey(otherPlayer.ActorNumber))
{
_playerObjects.Remove(otherPlayer.ActorNumber);
}
}
public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged){ }
public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps){ }
public void OnMasterClientSwitched(Player newMasterClient){ }
private void Refresh()
{
var photonViews = UnityEngine.Object.FindObjectsOfType<PhotonView>();
foreach (var view in photonViews)
{
var player = view.owner;
//Objects in the scene don't have an owner, its means view.owner will be null => skip
if(player == null) continue;
// we already know this player's object -> nothing to do
if(_playerObjects.ContainsKey(player.ActorNumber)) continue;
var playerObject = view.gameObject;
_playerObjects[player.ActorNumber] = playerObject;
}
}
}
This automatically collects the GameObject owned by the joined players. There is of course one limitation: If the players spawn anything themselves and own it .. it might fail since then there are multiple PhotonViews per player => in that case use the one with the lowest NetworkIdendity. In Photon the viewIDs are composed of first digit = ActorNumber + 3 digits ids ;)
and later do
if(!PhotonUserManager.TryGetPlayerObject(out var obj))
{
Debug.LogError($"Couldn't get object for actor number {receivedActorNumber}");
}
As another option you can use OnPhotonInstantiate
public class SomeComponentOnYourPlayerPrefab : PunBehaviour
{
public override void OnPhotonInstantiate(PhotonMessageInfo info)
{
// only set if not already set in order to not
// overwrite the tag for a player that spawns something later
if(info.sender.TagObject == null)
{
info.sender.TagObject = gameObject;
}
}
}
and then later do
var obj = PhotonNetwork.LocalPlayer.Get(receivedActorNumber).TagObject;

How to prevent the reload of already loaded gameobjects in Unity?

I'm currently developing a game in Unity and I ran into a small problem. I'm working on a restart function that gets called automatically when the player dies and loads the first scene again. However for some reason when reloading the scene games objects are duplicated with the version of the gameobject that was active at the time of death being inactive and the version that gets loaded as should be loaded getting set to active and so on every time the player dies adding a new duplicate of the same gameobjects to the hierarchy. I tried to solve this problem in multiple ways. First by trying to check each the gameobjects that get duplicated already have an instance of themselves running by attaching a script that checks every time a change in scene occurs wether or not their already is an instance of the gameobjects present:
public static GameObject Instance;
void Awake()
{
if(Instance){
DestroyImmediate(gameObject);
}else
{
DontDestroyOnLoad(gameObject);
Instance = this;
}
}
This seemed to solve the problem at first but it became to teadious towards the end because the scripts made all of my other scene objects behave badly or not at all so I chose to look for another solution.
Secondly I tried to Destroy each individual gameobject before I start loading the first scene. This also seemed to work at first but now my object pooler just recreates new intances of the gameobjects that it adds too the hierarchy esentially displacing the same problem to other gameobjects.
Finally in order to solve this problem I tried to make my objectpooler run only once when the scene that requires it to be loaded gets called but this didn't seem to work either. Does anyone have any idea how I could solve this problem. This is part of the script responsible for loading the original scene upon player death:
void Restart()
{
GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject gos in allObjects)
{
if (gos.activeInHierarchy)
{
if (gos != GameObject.Find("GameManager") && gos != GameObject.Find("ScreenBound"))
{
gos.SetActive(false);
}
}
}
MySceneManager.LoadScene(0, this);
}
How could I change this in order to be able to reload the original scene without having any previously loaded GameObject get duplicated and behave according to how it should in the scene in which it got loaded originally?
The class responsible for loading and deloading scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class MySceneManager
{
private static int lastLoadedScene = 0;
public static void LoadScene(int index, MonoBehaviour caller)
{
ObjectPooler objP = new ObjectPooler();
objP.ReleaseAll();
caller.StartCoroutine(loadNextScene(index));
}
private static IEnumerator loadNextScene(int index)
{
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
yield return null;
}
_async.allowSceneActivation = true;
while (!_async.isDone)
{
yield return null;
}
var newScene = SceneManager.GetSceneByBuildIndex(index);
if (!newScene.IsValid()) yield break;
SceneManager.SetActiveScene(newScene);
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
lastLoadedScene = index;
}
}
This is my ObjectPooler:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
#region Singleton
public static ObjectPooler Instance;
private void Awake()
{
if (Instance)
{
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
#endregion
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private Dictionary<string, Pool> prefabPools;
void Start()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
DontDestroyOnLoad(obj);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
private List<GameObject> currentlySpawnedObjects = new List<GameObject>();
public void Release(GameObject obj)
{
currentlySpawnedObjects.Remove(obj);
obj.SetActive(false);
obj.transform.SetParent(transform);
poolDictionary[obj.tag].Enqueue(obj);
DontDestroyOnLoad(obj);
}
public void ReleaseAll()
{
foreach (var child in currentlySpawnedObjects)
{
Release(child);
}
}
public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
return null;
}
GameObject objectToSpawn = poolDictionary[tag].Dequeue();
objectToSpawn.SetActive(true);
objectToSpawn.transform.position = position;
objectToSpawn.transform.rotation = rotation;
IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();
if (pooledObj != null)
{
pooledObj.OnObjectSpawn();
}
poolDictionary[tag].Enqueue(objectToSpawn);
return objectToSpawn;
currentlySpawnedObjects.Add(objectToSpawn);
return objectToSpawn;
}
}
Depends of your needs you can try next ways:
Use singleton pattern if you need save single instance of objects. This case relevant for saving managers (GameplayManager, SceneController, AssetBundleManager, etc.) in other cases will be better to use other ways. To read more about implementation you can see this article.
Destroy all old objects when loaded new scene. To do this you can use SceneManager.LoadScene method with LoadSceneMode.Single as parameter. It will keep DontDestoryOnLoad objects but will remove all others.
I'm not sure but the first possible issue to me already seems to be that it is running in Coroutine on an object in the scene you are going to unload.
It is cool that this is doable but have in mind that the Coroutine will stop working as soon as the caller object/component is destroyed or disabled.
To avoid that I would move your script to an object in the DontDestroyOnLoadScene using a Singleton pattern.
The next issue might be you going by SceneIndex ... both scenes, the one you want to unload and the one you want to load have index 0!
So maybe you get a conflict between the scene additively loading and the one you want to unload.
This also might happen again when you called
var newScene = SceneManager.GetSceneByIndex(lastLoadedScene);
To avoid this I would rather go by scene reference for the unloading
public class MySceneManager : MonoBehaviour
{
private static MySceneManager instance;
// Lazy initialization
// With this you wouldn't even need this object in the scene
public static MySceneManager Instance
{
if(instance) return instance;
instance = new GameObject ("MySceneManager").AddComponent<MySceneManager>();
DontDestroyOnLoad(instance);
}
// Usual instant initialization having this object in the scene
private void Awake ()
{
if(instance && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}
public void LoadScene(int index)
{
StartCoroutine(loadNextScene(index));
}
private IEnumerator loadNextScene(int index)
{
// I didn't completely go through your ObjectPooler but I guess you need to do this
ObjectPooler.Instance.ReleaseAll();
// Instead of the index get the actual current scene instance
var currentScene = SceneManager.GetActiveScene();
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
yield return new WaitWhile(() => _async.progress < 0.9f);
_async.allowSceneActivation = true;
yield return new WaitUntil(() => _async.isDone);
// You have to do this before otherwise you might again
// get by index the previous scene
var unloadAsync = SceneManager.UnloadSceneAsync(currentScene);
yield return new WaitUntil(()=>unloadAsync.isDone);
var newScene = SceneManager.GetSceneByBuildIndex(index);
SceneManager.SetActiveScene(newScene);
}
}
Alternatively since anyway you do nothing special while loading/unloading the scenes:
why using Additive scene loading at all if you could also simply call
ObjectPooler.Instance.ReleaseAll();
SceneManager.LoadSceneAsync(index);
without making it additive so the current scene is simply removed automatically as soon as the new scene is fully loaded.
Note: Types on Smartphone so no warranty but I hope the idea gets clear

Access Serialized List Object for Custom Unity Editor Window

I am trying to create a Custom EditorWindow (TransporterToolKit.cs) with buttons of my game's fast-travel points.
I have a GameObject (TransporterSystem.cs, a singleton, the manager):
that has a LIST of child GameObjects that are Nodes (The GameObjects fast travel points). Each node has a Serializable TransporterLocation that holds details about the actual location.
I get null object error:
NullReferenceException: Object reference not set to an instance of an
object
whether I am running the game or not using my current TransporterToolKit.cs file
How do I access the list of nodes so I can get their Serializable TransporterLocation?
EDITOW WINDOW:
What my problem question is about.
TransporterToolKit.cs
public class TransporterToolKit : EditorWindow {
[MenuItem("Project ToolKits/Transporter ToolKit")]
public static void ShowWindow() {
GetWindow<TransporterToolKit>("Transporter ToolKit");
}
public List<TransporterNode> nodes;
private void OnEnable() {
//ERROR COMES FROM THIS LINE
nodes = TransporterSystem.s_Instance.GetAllTransporterNodes();
}
private void OnGUI() {
GUILayout.Label("Transporter System", EditorStyles.boldLabel);
//Create a list of buttons with the location name
foreach (TransporterNode node in nodes) {
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.BeginHorizontal();
if (GUILayout.Button(node.ThisLocation.locationName)) {
//Do something
}
GUILayout.EndHorizontal();
EditorGUILayout.EndVertical(); //end section outline
}
}
}
The other classes.
TransporterSystem.cs
public class TransporterSystem : Singleton<TransporterSystem> {
[Header("Transporter Nodes")]
[SerializeField]
private List<TransporterNode> nodeList;
public List<TransporterNode> GetAllTransporterNodes() {
return nodeList;
}
}
TransporterNode.cs
public class TransporterNode : MonoBehaviour {
[SerializeField]
private TransporterLocation thisLocation;
public TransporterLocation ThisLocation {
get {
return thisLocation;
}
set {
thisLocation = value;
}
void Awake() {
ThisLocation.locationName = gameObject.name;
ThisLocation.transporterLocation = transform.position;
}
}
TransporterNode.cs
public enum TRANSPORTER_NODE_STATE {
OFFLINE,
ONLINE
}
[Serializable]
public class TransporterLocation {
public Vector3 transporterLocation;
public TRANSPORTER_NODE_STATE locationState;
public string locationName;
public TransporterLocation() {
transporterLocation = Vector3.zero;
locationName = "NOT SET";
locationState = TRANSPORTER_NODE_STATE.OFFLINE;
}
}
It looks like s_Instance is null. The problem is, that you are asking for the TransporterSystem static instance in OnEnable of the EditorWindow, however, the static instance will only be set in Awake during play mode. (At least this is what I assume after testing your code in my project).
I would fix this by actively searching for the TransporterSystem from within the editor window:
TransporterToolKit.cs
private void OnEnable()
{
TransporterSystem system = FindObjectOfType<TransporterSystem>();
if (system == null)
{
Debug.LogError("No TransporterSystem in scene.");
}
else
{
nodes = system.GetAllTransporterNodes();
}
}
Alternatively, you could also make your singleton implementation lazy, similar to this:
public class MonoSingleton
{
public static TransporterSystem instance
{
get
{
if (m_Instance == null)
m_Instance = Object.FindObjectOfType<TransporterSystem>();
if (m_Instance == null)
Debug.LogError("Unable to find TransporterSystem instance in scene.");
return m_Instance;
}
}
private static TransporterSystem m_Instance;
}
To fix the problem, that the Node is only updated in play mode:
// Reset is called when the component is added to a GameObject or when Reset is selected from the inspector.
void Reset() {
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(gameObject, "Update LocationNode");
#endif
ThisLocation.locationName = gameObject.name;
ThisLocation.transporterLocation = transform.position;
}
Instead of assigning the values in Awake, you need to do it at edit time. The simplest example would be via the Reset callback (but you could trigger it from your editor window or a special button as well). The important part is, that you want to not only set the values, but actually serialize the data to disk. This means, marking the scene as dirty. Unity recommends, to use the Undo class, to not only record an undoable action, but also set the scene as dirty. Alternatively, you can just do:
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene);
Beware, that the editor code needs to be either within a file that is placed in the Editor folder or surrounded by the compilation symbols UNITY_EDITOR.

Static instance does not work like I expect

I'm doing some tests for the states of a game built using Unity, using something like the following:
script 1
public class ControladorEstados : MonoBehaviour {
private static ControladorEstados controladorEstados = null;
//public bool pause { get; set;} = false; //¿ usa c# 4?
private bool pause;
public bool Pause {
get { return pause; }
set { pause = value; }
}
private bool gameOver;
public bool GameOver {
get { return gameOver; }
set { gameOver = value; }
}
//protected ControladorEstados () {}
private ControladorEstados () {}
void Start () {
pause = false;
gameOver = false;
}
void Update () {
Debug.Log("Update");
if (gameOver == true) {
Debug.Log("GameOver Game");
}
if (pause) {
Debug.Log("Pause Game");
}
}
public static ControladorEstados getInstancia () {
if (controladorEstados == null) {
controladorEstados = new ControladorEstados ();
}
return controladorEstados;
}
}
script 2
..//
private ControladorEstados controladorEstados = null;
void Awake() {
rb = GetComponent<Rigidbody>();
controladorEstados = ControladorEstados.getInstancia ();
}
void Start() {
}
void Update () {
// test
gameOver ();
}
private void gameOver (){
controladorEstados.GameOver = true;
Debug.Log("test gameOver () " + controladorEstados.GameOver);
}
}
In thetest gameOver () log I can see that the variable is set to true, but the script ControladorEstados does not work as I expect when doing the update. It does not work as expected.
I have put Debug.Log("Update"); To see if it was running the Update, and it is running, but it does not branch into the if statement when it is true.
I have no experience in C # or Unity, even though it seems embarrassing for me to ask this question, but I have been at this for a while and I do not see the error. Additionally, I am using a Linux version and I do not know if that could be part of the error.
Update:
I just added this code change:
void Update () {
Debug.Log("Update" + gameOver);
if (gameOver == true) {
Debug.Log("GameOver Game");
}
..//
Its output is False so I can see it is not changing. In the script 2 it is true, but in the script 1 it is false. Please give me some ideas as to what I am doing wrong.
I solve it
script 1
void Awake () {
//controladorEstados = new ControladorEstados ();
if (controladorEstados == null) {
controladorEstados = this;
}
}
..//
public static ControladorEstados getInstancia () {
return controladorEstados;
}
With the response of Peter Duniho I create the code above, You can see the changes and I also knew the use of Script Execution Order To use an order among them.
I do not know if it's the best way, but it seems to work, I post it in case someone helps with something similar.
Without a good, Minimal, Complete, and Verifiable code example that reliably reproduces the problem, it's impossible to say for sure what you need to change to fix the problem.
But, it's clear you are dealing with two different instances of ControladorEstados. The instance that is running (and so the one for which Unity3d is calling the ControladorEstados.Update() method) is not the same instance that is being referenced by the controladorEstados variable in whatever class you are describing as "script 2".
In your ControladorEstados.getInstancia() method, you should not be creating a new instance of ControladorEstados. Instead, you need to do two things:
Set ControladorEstados.controladorEstados in the ControladorEstados class when it's created, e.g. in the Start() method.
Make sure the class you describe as "script 2" does not call getInstancia() until after step #1 has occurred.
Note that the above is just the most minimal steps you can take to resolve the issue. In all likelihood, there is a better way to query the Unity3d API to allow "script 2" to retrieve the instance of ControladorEstados that you want (e.g. to return the current, correct controller instance from a scene, or something like that). But without a more complete code example, it's impossible to say for sure what that better approach would look like, exactly.
Your approach is not exactly the Unity way. You're basically mixing a singleton with a MonoBehaviour.
Remember that Unity is component-driven. If you want a single instance of an object that extends MonoBehaviour just create an object that's always in the scene (like a GameManager) and add ControladorEstados to it.
You can then reference it from there in scripts where you need to use it instead of using a singleton.

Categories