Better way to count all existing citizens? - c#

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)

Related

How do I store the same types of classes that have a different generic inside of a list?

I've been tinkering with this and I have a 'RespawnManager' that I want to use to manage my multiple 'SpawnPoint' classes with different generics but it ended up forcing me to use generics for my 'RespawnManager' which I don't want.
Let's say I had a SpawnPoint<T> class and I made a SpawnPoint<Enemy1>, SpawnPoint<Enemy2>, and SpawnPoint<Enemy3>. Is there any way I can make a list that can just manage multiple 'SpawnPoint's of any generic?
Base class:
public abstract class SpawnPoint<T> : MonoBehaviour
{
//how big the range of the spawn protection is
public int spawnProtectionRadius = 20;
public bool Occupied { get; set; }
public bool IsInSpawn(Transform target)
{
Debug.Log((target.position - transform.position).magnitude);
if ((target.position - transform.position).magnitude <= spawnProtectionRadius)
{
return true;
}
return false;
}
public abstract T Get();
}
Class that Inherits this
public class SeaMineSpawnPoint : SpawnPoint<Seamine>
{
public override Seamine Get()
{
return SeaMineObjectPool.PoolInstance.Get();
}
private void Start()
{
RespawnManager<Seamine>.respawnManager.AddSpawn(this);
}
}
Respawn manager:
public class RespawnManager<T> : MonoBehaviour where T : Component
{
public static RespawnManager<T> respawnManager;
[SerializeField]
private List<Transform> playerList;
[SerializeField]
private List<SpawnPoint<T>> spawnpoints;
private float respawnCounter;
private void Awake()
{
respawnManager = this;
}
private void Start()
{
foreach (SpawnPoint<T> sp in spawnpoints)
{
Debug.Log(sp.transform.position);
}
}
public void AddSpawn(SpawnPoint<T> spawnPoint)
{
spawnpoints.Add(spawnPoint);
}
public void RespawnSeaMines()
{
if (respawnCounter > 5)
{
respawnCounter = 0;
foreach (SpawnPoint<T> sp in spawnpoints)
{
foreach (Transform playerT in playerList)
{
if (sp.Occupied == false && !sp.IsInSpawn(playerT))
{
Component ourGameObj = sp.Get();
ourGameObj.transform.position = sp.transform.position;
ourGameObj.gameObject.SetActive(true);
sp.Occupied = true;
return;
}
}
}
}
}
private void Update()
{
respawnCounter += Time.deltaTime;
Debug.Log(respawnCounter);
RespawnSeaMines();
}
}
ObjectPool
//Class that's used for object pooling of different types.
//'T' must be a Unity component or it will error.
public abstract class ObjectPool<T> : MonoBehaviour where T : Component
{
//An object with this specific component that we use to copy.
[SerializeField]
private T prefab;
//Makes sure that only 1 coroutine runs at a time
private bool coroutineIsRunning;
//The singleton instance to our object pool.
public static ObjectPool<T> PoolInstance { get; private set; }
//A queue is used to organize plus activate and deactivate objects which
//have this component.
protected Queue<T> objects = new Queue<T>();
private void Awake()
{
//Set the instance of this pool to this class instance. Only one of these can be set.
if (PoolInstance != null)
{
throw new System.Exception("Singleton already exists. Cannot make another copy of this");
}
PoolInstance = this;
}
public T Get()
{
//If the queue happens to be empty, then add a brand new component.
if (objects.Count == 0) AddObjects(1);
//Returns the generic component and removes it from the queue.
return objects.Dequeue();
}
public void ReturnToPool(T objectToReturn)
{
//Disables the game object that the T component is attached to.
objectToReturn.gameObject.SetActive(false);
//Stores the T component in the queue.
objects.Enqueue(objectToReturn);
}
public void AddObjects(int count)
{
for (int i = 0; i < count; i++)
{
//Create a new copy of the prefab.
//The prefab is a game object with the T component attached to it.
T newObject = Instantiate(prefab);
//Disable the game object.
newObject.gameObject.SetActive(false);
//Add the T component to the queue.
//The T component is attached to the game object we created earlier.
objects.Enqueue(newObject);
}
}
public T GetWithDelay(int time)
{
T genericToReturn = null;
if (!coroutineIsRunning)
{
coroutineIsRunning = true;
StartCoroutine(GetCoroutine(time, genericToReturn));
}
return genericToReturn;
}
private IEnumerator GetCoroutine(int time, T generic)
{
float counter = 0;
while (counter < time)
{
counter += Time.deltaTime;
yield return null;
}
generic = Get();
generic.gameObject.SetActive(true);
coroutineIsRunning = false;
}
}
You should be able to declare your spawnpoints property in RespawnManager as a List<SpawnPoint<Component>> instead of List<SpawnPoint<T>>. That will allow you to get rid of the <T> type parameter entirely from RespawnManager and make it non-generic.

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

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.

Save object variables at some point and reuse them exactly as they were saved?

Let's say I have an object of type Player which has health, position and color variables.
The game goes through several stages and all variables change during this stages. For example:
Stage1 the player has 80 health, 50.3f position, and green color.
Stage2 the player has 50 health, 90f position, and blue color.
In stage 3 I would like to choose the exact same copy of Stage1 or Stage2 variables (at random).
One way I could do this is that in each stage I assign these variables to another Player object like
stage1PlayerSaved.health = 80;
stage1PlayerSaved.position = 50.3f;
stage1PlayerSaved.color = Color.Green;
And the same I would do for Stage2 and then reuse one of these objects in Stage3. But what if I have a lot of variables to save? For example, in addition to those 3 I would have more than 20, 30. The process of saving each variable is ugly.
Is there any other (easier) approach to save and then reuse the variables I need at certain stages? For example, saving a copy of object at the exact stage and then just reuse its variables later when needed?
to copy an object you can either use concept of copy constructor or you can use Cloning.
but as you mentioned in question that number of player may go high, I would recommended you to store only those information which is going to be changed over stages (exclude not changing information from saving)
you code should look somewhat similar to this,
Suppose you have Player class then you should also have PlayerInfo class too, for e.g.
public class Player
{
public int PId; //This will not change
public string Name; //This will not change
public int Energy; //This may change over stanges
//you should have a method which returns PlayerInfo object
public PlayerInfo GetPlayerInfoToBeStored()
{
return new PlayerInfo(this.Energy);
}
}
//this class will have only those information which can change
public class PlayerInfo
{
public int Energy;
public PlayerInfo(int playerEnergey)
{
Energy = playerEnergey;
}
}
and while actual using can be like this,
you can have a gloable Dictionary
public Dictionary<int, Dictionary<int, PlayerInfo>> StoredPlayerState =
new Dictionary<int, Dictionary<int, PlayerInfo>>();
and a method to add infromation in it.
public void SavePlayerInfo(int stageNum, List<Player> playerList)
{
Dictionary<int, PlayerInfo> playerInfoTable = new Dictionary<int, PlayerInfo>();
foreach(Player player in playerList)
{
playerInfoTable.Add( player.PId, player.GetPlayerInfoToBeStored());
StoredPlayerState.Add(stageNum, playerInfoTable);
}
}
and one method to set current player with previously set value
public void ResetPlayerWithOldValues(int stageNum, List<Player> originalPlayerList)
{
if (StoredPlayerState.ContainsKey(stageNum))
{
foreach(Player originalPlayer in originalPlayerList)
{
if (StoredPlayerState[stageNum].ContainsKey(originalPlayer.PId))
{
PlayerInfo playerInfo = StoredPlayerState[stageNum][originalPlayer.PId];
originalPlayer.Energy = playerInfo.Energy;
}
}
}
}
now using this flow will be too much easy,
public void MainMethod()
{
List<Player> playerList = new List<Player>();
//add players in this list
//after stage 1
SavePlayerInfo(1, playerList);
//after stage 2
SavePlayerInfo(2, playerList);
//At stage 3 :setting current players with any of those two state's values
ResetPlayerWithOldValues(randomStageNum, playerList);
//and simply keep using playerList
}
When I develop games, I usually create a singleton to store global game variables that I want to keep or update through out the different scenes, If I understood you correctly, this may help you:
public class PlayerState
{
public int PlayerHealth { get; set; }
public float PlayerPosition { get; set; }
}
This is the singleton:
public sealed class GameVariables
{
private static readonly _instance = new GameVariables();
public static GameVariables Intance { get { return _instance; } }
private GameVariables()
{
Reset();
}
public void Reset()
{
//Initialize your variables here when you start a new game
TotalScore = 0;
PlayerStates = new Dictionary<int, PlayerState>();
[...]
}
public Dictionary<int, PlayerState> PlayerStates { get; private set; }
public int TotalScore { get; set; }
[...]
}
Then during the game just initialize player state and get the instance whenever you need it:
public class Scene3
{
void Start()
{
GameVariables.Instance.PlayerStates.Add(3, new PlayerState());
}
void Update()
{
//Get player state for scene 1
PlayerState playerStateScene1 = GameVariables.Instance.PlayerStates[1];
//Get player state for scene 2
PlayerState playerStateScene2 = GameVariables.Instance.PlayerStates[2];
//Update player state for this scene
GameVariables.Instance.PlayerStates[3].PlayerHealth = 80;
GameVariables.Instance.PlayerStates[3].TotalScore += 100;
[...]
}
}
Don't forget to reset the game if you start again from the main menu:
public class MainMenu
{
void Play()
{
GameVariables.Instance.Reset();
}
}

Generics in Unity C#

I am having a lot of trouble with the syntax and the rules for using Generics. I am trying to make a structure, where different classes, can use the WaitAction class to disable input while a couroutine is running, an re-enable it once the coroutine is finished.
This example is a simplified version, and in reality I will not be using a count float to define the length of the coroutine, but the length will based on animations and translation.
Is what I am trying to do at all possible?
"Somehow use "T _ready" to change the "bool ready" in "Main Class" back to "true""
public class Main : Monobehaviour {
WaitAction _waitAction = new WaitAction();
public bool ready;
float delay = 5f;
void Update()
{
if(Input.GetMouseButton(0) && ready)
{
ready = false;
StartCoroutine(_waitAction.SomeCoroutine((delay, this));
}
}
public class WaitAction {
public IEnumerator SomeCoroutine<T>(float count, T _ready)
{
float time = Time.time;
while(Time.time < time + count)
{
yield return null;
}
// Somehow use "T _ready" to change the "bool ready" in "Main Class" back to "true"
}
}
The solution is to constrain the generic type, such that the generic method knows how to set the ready flag. This is easily done using an interface:
public interface IReady
{
bool ready { get; set; }
}
public class Main : Monobehaviour, IReady {
...
public bool bool ready { get; set; }
...
}
public class WaitAction {
public IEnumerator SomeCoroutine<T>(float count, T _ready) where T : IReady
{
...
_ready.Ready = true;
}
}

Categories