Where to initialize a static variable in Unity? - c#

I'm using a static variable in a class but the problem is that it needs another value from another class for it's initial value(See the code snippet). I thought initializing it in Start function. But (correct me if I'm wrong) that means it will be reinitialized for every instance of the object which is something redundant since I want this variable to be initialized for just once at the creation of very first Unit which has UnitManager on.
So my question is at what place would be considered as a good practice to initialize this variable?
Thanks!
Code:
public class UnitManager : MonoBehaviour
{
// Distance in terms of Unity Unity from the target position to stop for units
static float distanceToStop;
private void Start()
{
if (WorldCoordController.OneUnityMeterToRealWorld < 10)
{
distanceToStop = 1 / WorldCoordController.OneUnityMeterToRealWorld;
}
else
{
distanceToStop = 0.1f;
}
}
}

public class UnitManager : MonoBehaviour
{
// Distance in terms of Unity Unity from the target position to stop for units
static float distanceToStop;
static bool distanceSet = false;
private void Start()
{
// If the distance is not set
if(!this.distanceSet)
{
if (WorldCoordController.OneUnityMeterToRealWorld < 10)
{
distanceToStop = 1 / WorldCoordController.OneUnityMeterToRealWorld;
} else {
distanceToStop = 0.1f;
}
this.distanceSet = true;
}
}
The "distanceSet" bool will be shared between the instances so you will only set the distance on the first one :D

Maybe consider calling UnitManager with an Init(); with the WorldCoordController value it needs.

You can create a custom class that will have a static reference to its self and be initialized only once (the first time it gets called).
Example:
public class ExampleClass
{
//Static Functionality
private static ExampleClass _inst;
public static ExampleClass Instance
{
get
{
if (_inst is null)
{
_inst = new ExampleClass();
_inst.Init();
}
return _inst;
}
}
//Class Values
public static int MyValue;
public int Value1;
//private Constructor
private ExampleClass()
{
}
//initialize values here
private void Init()
{
}
}
And then you can access the values like this:
//This will return the Value1 int
ExampleClass.Instance.Value1
or
//This will return the static MyValue int
ExampleClass.MyValue
From what you are asking, you can use only the Value1 from the above example and have it initialized only once in the init. If you want the value to be accessible only for read you can set it as property with "private set" operator.
The advantage of this is you dont need Start or Monobehaviour so it can work anywhere without having it in gameobjects.
Hope this helps, and happy coding!

Related

Send int from second script to bool[list] from first script

How can I achieve Bool[int] for current weapon type identification?
WeaponController
public class WeaponController : MonoBehaviour
{
public bool[] CurrentWeapon = new bool[2];
public bool Wrench;
public bool Pistol;
void Start()
{
Wrench = CurrentWeapon[0];
Pistol = CurrentWeapon[1];
}
}
WeaponSwitching
public class WeaponController : MonoBehaviour
{
public int selectedWeapon = 0;
void Update()
{
Player.GetComponent<WeaponController>().CurrentWeapon.bool[Paste here] = selectedWeapon;
}
}
In your WeaponSwitching class, change CurrentWeapon.bool[Paste here] = selectedWeapon; to CurrentWeapon[index] = value;
You'll need to change index to the array index you want to change and value to a boolean value. Currently you are using the int selectedWeapon which will become true if it's equal to 1 and false if it's equal to 0.
It also looks like your code may have a few other issues
Both classes say public class WeaponController. They need to have different names.
A Wrench and Pistol boolean are being assigned on start, but won't do anything afterwards if they don't have more code.
An easier way to do this might be to use a public int CurrentWeaponin the WeaponController class instead of bool and change that from the WeaponSwitching class.
Here is a similar question with someone having a similar problem

UnityEngine.MonoBehaviour:.ctor ()

I am trying to build a system that finds the nearest position of an object and uses it to transform the Player to a specific position. I understand the issue but I couldn't eliminate the yellow exclamation mark. The program is somehow working, but I'd like to see the solution.
The issue according to UnityConsole is: You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
UnityEngine.MonoBehaviour:.ctor ()
Scripts are:
#1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class ScoreMultiplerFinder : MonoBehaviour
{
private static ScoreMultiplerFinder instance;
private List<GameObject> checkpoints = new List<GameObject>();
public List<GameObject> Checkpoints { get { return checkpoints; } }
public static ScoreMultiplerFinder Singleton
{
get
{
if (instance == null)
{
instance = new ScoreMultiplerFinder();
instance.Checkpoints.AddRange(
GameObject.FindGameObjectsWithTag("Checkpoint"));
instance.checkpoints = instance.checkpoints.OrderBy(waypoint => waypoint.name).ToList();
}
return instance;
}
}
}
#2(inPlayerObject)
public void FindClosestMultiplier()
{
float lastDist = Mathf.Infinity;
for (int i = 0; i < ScoreMultiplerFinder.Singleton.Checkpoints.Count; i++)
{
GameObject thisWP = ScoreMultiplerFinder.Singleton.Checkpoints[i];
float distance = Vector3.Distance(transform.position, thisWP.transform.position);
if (distance < lastDist)
{
currentIndex = i;
lastDist = distance;
lastScoreMultip.position = ScoreMultiplerFinder.Singleton.Checkpoints[currentIndex].transform.position;
}
}
}
Thanks in advance and have a great day!
The error/warning is pretty much telling you what to do instead. A correct way to lazy initialize the Singleton would be
private List<GameObject> checkpoints;
// First of all be consequent and let nobody change the content of this
public IReadOnlyList<GameObject> Checkpoints => checkpoints;
public static ScoreMultiplerFinder Singleton
{
get
{
if(instance) return instance;
// before creating one check if there maybe is one already first
instance = FindObjectOfType<ScoreMultiplerFinder>();
if(instance) return instance;
// GameObject is one of the very few UnityEngine.Object types where using
// "new" is actually okey
instance = new GameObject(nameof(ScoreMultiplerFinder)).AddComponent<ScoreMultiplerFinder>();
return instance;
}
}
private void Awake ()
{
// Without this your "Singleton" is not complete and you rather only have a
// lazy factory property.
// For a valid singleton pattern you have to make sure that there actually
// exists only one single instance at a time!
if(instance && instance != this)
{
Destroy (gameObject);
return;
}
instance = this;
checkpoints = GameObject.FindGameObjectsWithTag("Checkpoint").OrderBy(waypoint => waypoint.name).ToList();
}
And then if you already use Linq you are already familiar with OrderBy.
Your second script can be shrinked down to
public void FindClosestMultiplier()
{
// Simply get the waypoints
// - "OrderBy" them by distance. using the "sqrMagnitude" is faster and for ordering has exactly the same effect as
// "Distance" which uses the more expensive "magnitude"
// - use "FirstOrDefault" to get either the closest item or "null" if there wasn't any at all
var closest = ScoreMultiplerFinder.Singleton.Checkpoints.OrderBy(c => (transform.position - c.transform.position).sqrMagnitude).FirstOrDefault();
// Was there any at all?
if(!closest) return;
// Otherwise "closest" is the closest waypoint -> do something with it
lastScoreMultip.position = closest.position;
}
Actually as mentioned I don't think you need a MonoBehaviour at all.
You could as well simply have
public static class ScoreMultiplerFinder
{
private static List<GameObject> checkpoints;
public static IReadOnlyList<GameObject> Checkpoints
{
get
{
if(checkpoints == null) checkpoints = GameObject.FindGameObjectsWithTag("Checkpoint").OrderBy(waypoint => waypoint.name).ToList();
return checkpoints;
}
}
}
and then accordingly do
public void FindClosestMultiplier()
{
var closest = ScoreMultiplerFinder.Checkpoints.OrderBy(c => (transform.position - c.transform.position).sqrMagnitude).FirstOrDefault();
if(!closest) return;
lastScoreMultip.position = closest.position;
}

Trigger a function when array values are changed

I am adding a set of array values through inspector window. I am trying to achieve to trigger a function, when I change my array values. That is, my script should check if the new values are not equal to old values and then call this function.
I do not want to use Update as it will take more memory and processing power, so what could be the alternative?
public class SetValues : MonoBehaviour {
[Serializable]
public struct SetValues
{
public float Position;
public float Value;
}
public SetValues[] setValues;
void Function_ValuesChanged()
{
Debug.Log("The Value is changed");
//Do Something
}
}
Try MonoBehavior.OnValidate()
Example:
public class SetValues : MonoBehaviour {
[Serializable]
public struct SetValues
{
public float Position;
public float Value;
}
public SetValues[] setValues;
void OnValidate()
{
Debug.Log("The Value is changed");
//Do Something
}
}
If something happens in game and you want to notify other scripts use events. Events are lightweight and easy to implement.
In class that contain array, create property and change array value, only from property. Never directly interact with array field.
// Create Event Arguments
public class OnArrayValueChangedEventArgs : EventArgs
{
public float Position;
public float Value;
}
// Crate Event
public event EventHandler<OnArrayValueChangedEventArgs> OnArrayValueChanged;
Array[] myArray;
// Change Array value only from this property, so when you change value, event will be called
public Array[] MyArray
{
get { return myArray; }
set { myArray = value; OnArrayValueChangedEvent(this, new OnArrayValueChangedEventArgs() { Position = myArray.Position, Value = myArray.Value };}
}
From second class you should just subscribe to this event and do the thing. I will call this event from my GameManager Singleton class.
private void Awake()
{
GameManager.Instance.OnArrayValueChanged += Instance_OnArrayValueChanged;
}
private void Instance_OnArrayValueChanged(object sender, GameManager.OnArrayValueChangedEventArgs e)
{
// Do the thing
}

Shared GameSettings with U-Net

I have a scene where the player has the option to choose settings for the match they are creating (number of rounds, time per round etc..), I also have a utility class MatchSettings that contains all of these settings, when I run the game on the host everything works fine, however when a client joins the game, the clients match settings are 0 for everything, The settings are used as part of a GameManager class that implements a singleton pattern with a MatchSettings member. So my question is how can I have all the participants of the game share the same settings? ( I am aware that u-net is deprecated)
The Relevant Code for the GameManager:
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public MatchSettings settings;
void Awake()
{
if(instance != null)
{
Debug.LogError("Too many game managers");
}
else
{
instance = this;
respawnCamera.SetActive(false);
settings = new MatchSettings();
timePassed = settings.roundTime * 60;
roundsPlayed = 0;
highestKills = 0;
}
}
void Update()
{
timePassed -= Time.deltaTime;
if (timePassed < 0 || highestKills >= settings.maxKills)
{
Debug.Log(settings.roundTime); //prints 0 at client runtime
RoundOver();
}
if(roundsPlayed >= settings.roundCount)
{
GameOver();
}
}
}
The relevant code for the MatchSettings:
[System.Serializable]
public class MatchSettings
{
public float roundovertime = 10f;
public static float roundtime; // the variables from the UI scene are stored in the static members and transferred
public static int maxkills; // into the regular ones when MatchSettings() is called [in awake in game manager]
public static int roundcount;
public float respawntime = 5f;
public float roundTime;
public int maxKills;
public int roundCount;
public MatchSettings()
{
roundTime = roundtime;
maxKills = maxkills;
roundCount = roundcount;
}
}
Thanks in advance!
Unless you synchronize MatchSettings to all clients, you will always have the default values there (zeros in this case).
One way about it using UNET is using SyncVar - You will need to have the settings on a gameobject in the scene, owned by the server which will be your "source of truth".
You only perform changes on the server side, and it will be automatically updated to all clients.
Pseudo-code example:
class GameSettings : NetworkBehaviour {
[SyncVar(hook=nameof(FragsRequiredAmountSyncVarChanged))] private int _fragsRequiredToWinSyncVar = 20;
public void ChangeFragsRequiredToWin(int newAmount) {
if (!isServer) {
Debug.LogError("Sync vars can only change on the server!");
return;
}
_fragsRequiredToWinSyncVar = newAmount;
}
private void FragsRequiredAmountSyncVarChanged(int newAmount)
{
Debug.Log($"Frag requirement changed to {newAmount}");
}
}
I've also included an example on how to attach hooks when the SyncVar changes; I'm pretty sure it gets called on both the server and the client, but my memory might fail me since it's been quite a while since I last used UNET.

Better way to count all existing citizens?

I've started making a simple city builder game, in the spirit of Zeus/Poseidon, but much simpler. I have the grid system ready and ability to add houses and roads. Yesterday I began to add citizens, in a simple way, that is, whenever a house is created, 5 people are created and move directly from one edge of the map to that particular house. Once they reach that particular house, I consider they became citizens, and add them to the list of residents of the house, and also to the list of citizens of the city.
For that, each house instance has a List of Human, and my Game class which contains all the information of the game also has one List of human.
To simplify it looks like this:
Game.cs
public class Game {
private static Game instance; // this is a singleton
private int currentAmount; //this is the value I'm using to display the number of citizens on screen
private List<Human> humen;
public List<Human> Humen
{
get { return humen; }
set
{
humen = value;
currentAmount = humen != null ? humen.Count : 0;
}
}
public void AddHuman(Human human)
{
humen.Add(human);
currentAmount = humen.Count;
}
/// <summary>
/// Private constructor to ensure it's only called when we want it
/// </summary>
private Game()
{
humen = new List<Human>();
}
public static void setGame(Game game)
{
instance = game;
}
/// <summary>
/// Returns the instance, creates it first if it does not exist
/// </summary>
/// <returns></returns>
public static Game getInstance() {
if (instance == null)
instance = new Game();
return instance;
}
}
House.cs
public class House : Building {
public static int CAPACITY = 5;
private List<Human> habitants;
public List<Human> Habitants
{
get { return habitants; }
set { habitants = value; }
}
public House() {
habitants = new List<Human>();
}
}
HumanEntity.cs
public class HumanEntity : MonoBehaviour {
private Human human;
private float speed;
public Human Human
{
get { return human; }
set { human = value; }
}
// Use this for initialization
void Start () {
speed = Random.Range(5.0f, 10.0f);
}
// Update is called once per frame
void Update () {
if (human != null)
{
Vector3 targetPosition = human.Target.GameObject.transform.position;
if (transform.position.Equals(targetPosition)) {
if (!human.HasAHouse)
{
human.HasAHouse = true;
Game.getInstance().AddHuman(human); // here I'm adding the human to the list of citizens
((House)human.Target).Habitants.Add(human); // here I'm adding it to the house list of habitants
}
}
else {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
}
}
}
}
And this is working as expected, but I'm wondering if having one list of human by house in addition with a global list in the game class is not a little overkill, and if there was maybe a more elegant way to achieve that count on the Game class, maybe something more "Unity friendly" if I may say so, as I don't really know a lot about the capacities of Unity. Do you have any advice on what to do, is that okay to keep it this way or is there a more elegant way?
Fast and appropriate way to know how many human would be to have a static counter on HumanEntity class:
public class HumanEntity : MonoBehaviour
{
public static int HousedHuman { get; private set; }
public static int HumanCount { get; private set; }
void Awake() { HumanCount++; }
void OnDestroy()
{
HumanCount--;
if(human.HasAHouse == true){ HousedHuman--; }
}
public static void ResetCounter() { HouseHuman = HumanCount = 0; }
void Update () {
if (human != null)
{
Vector3 targetPosition = human.Target.GameObject.transform.position;
if (transform.position.Equals(targetPosition)) {
if (!human.HasAHouse)
{
HouseHuman++; // Added
human.HasAHouse = true;
// Rest of code
}
}
// Rest of code
}
}
}
When a new instance is added, the counter is increased, when the instance is destroyed, the counter is decreased.
You can access via HumanEntity.HumanCount. You won't be able to set it elsewhere than in the HumanEntity class.
Make sure to reset the counter when you start/leave the scene.
EDIT: based on comment, I added a second static counter for HousedHuman. This is increased when the entity reaches the house. It gets decreased when the entity is destroyed if the entity was housed. It also gets reset when needed along with the overall counter.
Building on Everts's idea...
Game:
public class Game {
private static Game instance; // this is a singleton
public static int currentAmount { get; set; }
//rest of class
}
House:
public class House : Building {
public static int CAPACITY = 5;
private List<Human> habitants;
public List<Human> Habitants
{
get { return habitants; }
set { habitants = value; }
}
public House() {
habitants = new List<Human>();
}
public void AddHuman(Human human)
{
human.HasAHouse = true;
habitants.Add(human);
Game.currentAmount++;
}
}
UpdateLoop:
// Update is called once per frame
void Update () {
if (human != null)
{
Vector3 targetPosition = human.Target.GameObject.transform.position;
if (transform.position.Equals(targetPosition)) {
if (!human.HasAHouse)
((House)human.Target).AddHuman(human);
}
else {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
}
}
}
If checking house capacity is required, you can change the AddHuman method to a bool return type, do a capacity check inside and return whether or not it was successfully added.
You can also add a RemoveHuman method that would count humans down via Game.currentAmount--;
As for the list in Game, it really depends on the context. The List in your Game class could be useful to differentiate between wandering humans, and humans who are housed, if this behavior is required. (Wandering humans in the Game list, housed in the housed list)

Categories