Shared GameSettings with U-Net - c#

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.

Related

Upgrade multiplier for clicker game such as Adventure Capitalist

Hello community I am making a clicker game but I don't know how to do the upgrade multiplier button such as Adventure Capitalist, I have a single script attached to each GameObject, total of six objects with the same script attached, where I have the Text object which shows a double value which is an array with six indexes, the problem here is that I have the multiplier button object attached with another script, I should do something as static (singleton) because each script has many objects, I only need to access to a single text which is upgrade price displayer crop which is non-static. Pls help me.
This is the code for the multiplier button
public class MultiplyManager : MonoBehaviour {
public void multiply_Crop_PerOne()
{
UpgradeButtonCrop.multiplier = 1;
}
public void multiply_Crop_PerTen()
{
UpgradeButtonCrop.multiplier = 10;
}
public void multiply_Crop_PerHundred()
{
UpgradeButtonCrop.multiplier = 50;
}}
And this is the code for the text UI which I am working, what I want to do is make the multiplier button multiply the cost of the upgrade and increase its level, for example if I have an upgrade which costs 1 gold, the multiplier per ten should make that upgrade cost for 10 and increase 10 levels.
public class UpgradeButtonCrop : MonoBehaviour{
public static int multiplier;
public Button upgradeButton_crop;
public TMP_Text level_displayer_crop;
public TMP_Text perSec_displayer_crop;
public TMP_Text upgrade_price_displayer_crop;
[HideInInspector]
public double startGoldByUpgrade_crop;
public double startCurrentCost_crop;
public double[] crop_upgrade_price_arr = new double[6];
public int[] level_crop_arr = new int[6];
public void First_Crop_Button()
{
purchaseUpgradeCrop(0);
}
public void Second_Crop_Button()
{
purchaseUpgradeCrop(1);
}
public void Third_Crop_Button()
{
purchaseUpgradeCrop(2);
}
public void Fourth_Crop_Button()
{
purchaseUpgradeCrop(3);
}
public void Fifth_Crop_Button()
{
purchaseUpgradeCrop(4);
}
public void Last_Crop_Button()
{
purchaseUpgradeCrop(5);
}
public void purchaseUpgradeCrop(int index)
{
if ((level_crop_arr[index] + multiplier) <= max_level)
{
if (DataController.Instance.gold >= crop_upgrade_price_arr[index] * multiplier)
{
updateUI_Crop(index);
updateUpgrade_Crop();
upgrade_price_displayer_crop.text = LargeNumber.ToString(crop_upgrade_price_arr[index]).ToString();
DataController.Instance.saveUpgradeButton_Crop(this);
}
}
else
{
UpgradeMessageManager.instance.Start_coroutine_cant_upgrade();
return;
}
}
}
You can find components in the scene;
https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html
So you could do something like this;
class UpgradeButtonCrop : Monobehaviour {
MultiplyManager _manager;
void Start()
{
// Find the manager and save a reference
_manager = FindObjectsOfType<MultiplyManager>().First();
}
}
and since you now have a reference to that manager you can do something with it.
Also see this tutorial;
https://www.youtube.com/watch?v=-kd68uKt4jk

Destroying exact object

I am creating a small 2d game in which you need to survive. Each tree has its own strength = 5. When the player collides and presses the left mouse button then strength is -1 and player wood stat is +1. when the tree strength is equal or less than 0 then the tree is destroyed. Here is my code : (Question is after the code)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Stats: MonoBehaviour
{
//Player Stats
public float hp = 100;
public float wood = 0;
//Tree stats
public float treeLogStrenth = 5;
//Text
public Text woodText;
void Start ()
{
woodText.text = "0";
}
void Update ()
{
woodText.text = wood.ToString();
if (treeLogStrenth <= 0)
{
Destroy(GetComponent<PlayerCollisions>().;
}
}
}
here is another code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCollisions: MonoBehaviour
{
public void OnCollisionStay2D (Collision2D collisionInfo)
{
if (collisionInfo.gameObject.tag == "Tree" && Input.GetMouseButtonDown(0))
{
string treeName = collisionInfo.gameObject.name;
GetComponent<Stats>().wood += 1;
GetComponent<Stats>().treeLogStrenth -= 1;
}
}
}
MY QUESTION : How to make instead of creating all the time another game object for each tree and destroying it, only like one simple code that will do it. please help (UNITY NEWEST VERSION)
So to clear things up: Stats should be attached to the player, right?
You should not do things every frame in Update but rather event driven like
public class Stats : MonoBehaviour
{
// You should never allow your stats to be set via public fields
[SerializeField] private float hp = 100;
[SerializeField] private float wood = 0;
// if you need to read them from somewhere else you can use read-only properties
public float HP => hp;
public float Wood => wood;
[SerializeField] private Text woodText;
private void Start ()
{
woodText.text = "0";
}
public void AddWood(int amount)
{
wood += amount;
woodText.text = wood.ToString();
}
}
Then each tree should rather have its own component instance attached like e.g.
public class Tree : MonoBehaviour
{
[SerializeField] private float treeLogStrenth = 5;
public void HandleClick(Stats playerStats)
{
// if this tree has wood left add it to the player stats
if(treeLogStrength > 0)
{
playerStats.AddWood(1);
treeLogStrenth -= 1;
}
// destroy this tree when no wood left
if (treeLogStrenth <= 0)
{
Destroy(gameObject);
}
}
}
and then finally also attached to the Player
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store the currently collided tree in order to reuse it
private Tree currentlyCollidedTree;
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// Get the Tree component of the tree object you are currently colliding with
// but only once and store the reference in order to reuse it
if(!currentlyCollidedTree) currentlyCollidedTree= collisionInfo.gameObject.GetComponent<Tree>();
// tell the tree to handle a click and pass in your stats reference
currentlyCollidedTree.HandleClick(stats);
}
}
// reset the currentlyCollidedTree field when not colliding anymore
private void OnCollisionExit2D()
{
currentlyCollidedTree = null;
}
}
Well yes there would be an alternative where not every tree needs its own component instance but I would recommend to not use it actually!
You could make your player remember which tree he already clicked in something like
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store all trees we ever clicked on in relation to the according available wood
private Dictionary<GameObject, int> encounteredTrees = new Dictionary<GameObject, int>();
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// did we work on this tree before?
if(encounteredTrees.Contains(collisionInfo.gameObject))
{
// if so gain one wood and remove one from this tree
stats.AddWood(1);
encounteredTrees[collisionInfo.gameObject] -= 1;
// destroy the tree if no wood available and remove it from the dictionary
if(encounteredTrees[collisionInfo.gameObject] <= 0)
{
encounteredTrees.RemoveKey(collisionInfo.gameObject);
Destroy(collisionInfo.gameObject);
}
}
else
{
// the first time we work this tree gain one wood and add
// the tree as new entry to the dictionary with 4 wood left
stats.AddWood(1);
encounteredTrees.Add(collisionInfo.gameObject, 4);
}
}
}
}
this however limits you extremely and it is not possible anymore that you have e.g. different Tree prefabs with different amount of available wood ...

Where to initialize a static variable in Unity?

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!

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)

How to achieve awareness of "kill" events in a scene

I have been doing a RPG game in Unity with C # and when doing a system of quests, specifically those of killing a certain number of enemies, I found the problem of having 3 enemies in the scene and being the target of the quest: Kill 3 enemies. If I kill them before activating the quest and later active the quest does not give me the reward (in this case experience). How can I tell the enemies and make that if the quest detects that I have already killed the necessary enemies to get the quest give me the reward equally?
Here the two needed scripts i think:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestObject : MonoBehaviour {
public int questNumber;
public QuestManager qManager;
public string startText;
public string endText;
public bool isItemQuest;
public string targetItem;
public bool isEnemyQuest;
public string targetEnemy;
public int enemiesToKill;
private int enemyKillCount;
private PlayerStats playerStats;
public int EXPToGive;
void Start () {
playerStats = FindObjectOfType <PlayerStats> ();
}
void Update () {
if (isItemQuest) {
if (qManager.itemCollected == targetItem) {
qManager.itemCollected = null;
EndQuest ();
}
}
if (isEnemyQuest) {
if (qManager.enemyKilled == targetEnemy) {
qManager.enemyKilled = null;
enemyKillCount++;
}
if (enemyKillCount >= enemiesToKill) {
EndQuest ();
}
}
}
public void StartQuest (){
qManager.ShowQuestText (startText);
}
public void EndQuest (){
qManager.ShowQuestText (endText);
playerStats.AddEXP (EXPToGive);
qManager.questCompleted [questNumber] = true;
gameObject.SetActive (false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public int startingHealth;
public int currentHealth;
public GameObject damageBurst;
private PlayerStats playerStats;
public int EXPToGive;
public string enemyQuestName;
private QuestManager qManager;
void Start ()
{
// Setting up the references.
//anim = GetComponent <Animator> ();
//enemyAudio = GetComponent <AudioSource> ();
//enemyMovement = GetComponent <EnemyMovement> ();
//enemyAttacking = GetComponentInChildren <EnemyAttack> ();
// Set the initial health of the player.
currentHealth = startingHealth;
playerStats = FindObjectOfType <PlayerStats> ();
qManager = FindObjectOfType <QuestManager> ();
}
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
public void TakeDamage (int amountDamage)
{
// Reduce the current health by the damage amount.
currentHealth -= amountDamage;
Instantiate (damageBurst, transform.position, transform.rotation);
}
public void SetMaxHelth () {
currentHealth = startingHealth;
}
}
One Aproach would be to create some type of "WorldManager" which counts every Enemy which has been slain. And when Starting a quest this quest could check the WorldManagers kill count and add it to it's own count.
public void StartQuest (){
qManager.ShowQuestText (startText);
this.enemyKillCount += worldManager.GetKillCount();
}
In your enemy class you have to add a kill to your worldManager.
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
this.worldManager.AddKill(this)
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
Alternative:
Make your QManager be aware of every kill in a Scene.
You can achieve this through many ways.
One of them is passing your EnemyObject an reference of your Qmanager and do the same as with the "WorldManager" provided above, or you use Messaging and fire a Message targeting the QManager when an enemy is slain.
Alternative 2:
Throw an Event when an enemy has been slain and subscribe to it on your QManager/WorldManager. This way u can reuse your enemy class in every game. From my point of view static dependencies are evil, but there are many discussions and SO and everywhere on the internet about that.
You can several approach. The most straight-forward is to use static.
The purpose of static is for the variable/method to belong to the class instead of an instance of the class.
In your case, you want each enemy to have its own health, this cannot be static.
And you want to count how many instances there are in the scene from the class. So static is fine.
public class Enemy:MonoBehaviour
{
private static int enemyCount = 0;
public static int EnemyCount {get{ return enemyCount;} }
public event Action<int> RaiseEnemyDeath;
public static void ResetEnemyCount(){
enemyCount = 0;
}
private int health;
public void Damage(int damage)
{
CheckForDamage(); // here you check that damage is not neg or too big...
this.health -= damage;
if(this.health <= 0)
{
OnDeath();
}
}
void OnActivate()
{
enemyCount++;
this.health = 20;
}
void OnDeath()
{
enemyCount--;
RaiseEnemyDeath(enemyCount); // Should check for nullity...
}
}
This one is fairly simple. The first part is all static and is relevant to the class. The second part is relevant to the instance. If you use a pool of enemy and then reuse the same instance multiple times, the OnActivate method is called when you make the enemy alive in the scene (it may have been there for a while as inactive). Then when the health is down, kill the enemy (there are not all the required actions there...) and trigger the event.
Using the public static property, you can know what is the enemy count from a GameManager (Enemy should not affect the gameplay, only takes care of the enemy).
public class GameManager:MonoBehaviour
{
void Start()
{
Enemy.RaiseEnemyDeath += Enemy_RaiseEnemyDeath;
}
void Enemy_RaiseEnemyDeath(int count)
{
if(count < 0){ // End of level }
// You can also access enemyCount
int count = Enemy.EnemyCount;
}
}
The good point of using this principle is that Enemy has no clue about GameManager and can be reused in another game without any modification. The GameManager is a higher level entity and knows about it.

Categories