Sync Random Numbers In UnityNetwork - c#

I want to assign random numbers to my objects that spawn when the server starts ,But every instance of the game makes its own random numbers and they don't sync together ,How can i fix that?
public class Health : NetworkBehaviour
{
public Text PlanetHealth;
public const int maxHealth = 100;
[SyncVar(hook = "OnChangeHealth")]
public int currentHealth = maxHealth;
void Start()
{
if(this.gameObject.name == "GrayPlanet(Clone)")
{
currentHealth = Random.Range(0, 50);
PlanetHealth.text = currentHealth.ToString();
}
else
PlanetHealth.text = maxHealth.ToString();
}
public void TakeDamage(int amount)
{
if (!isServer)
{
return;
}
currentHealth -= amount;
if (currentHealth <= 0)
{
currentHealth = 55;
}
}
void OnChangeHealth(int currentHealth)
{
PlanetHealth.text = currentHealth.ToString();
}
}

Just add this at the beginning of your Start() method:
if (!isServer)
{
return;
}
You should do the random generation only in the server side. After that, SyncVar will do the job of keeping the instances up to date.

Related

Unity C# how should I call the function and values from different script files?

I am new to Unity and c#. I am trying to create a dice game that rolls two dices and the total values of the two dice faces is added to the player's score. I am intending the game to continue until the player rolls two 1's or the total score reached or exceeds 50, and at the end display win or lose message and the score. I somehow managed to implement most of it. However, I can't manage to update score after rolling the dice. I tried on my own to do so, but now it's keep adding the dice rolls without break and prints win message right away.
This is the Dice code
public class Dice : MonoBehaviour
{
Rigidbody rb;
bool hasLanded;
bool thrown;
Vector3 initPosition; //Initial Position
public int diceValue;
public DiceSide[] diceSides;
void Start()
{
rb = GetComponent<Rigidbody>();
initPosition = transform.position;
rb.useGravity = false;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RollDice();
}
if(rb.IsSleeping() && !hasLanded && thrown)
{
hasLanded = true;
rb.useGravity = false;
//rb.isKinematic = true;
SideValueCheck();
}
else if(rb.IsSleeping() && hasLanded && diceValue == 0)
{
RollAgain();
}
}
void RollDice()
{
if(!thrown && !hasLanded)
{
thrown = true;
rb.useGravity = true;
rb.AddTorque(Random.Range(0, 500), Random.Range(0, 500), Random.Range(0, 500));
}
else if(thrown && hasLanded)
{
Reset();
}
}
void Reset()
{
transform.position = initPosition;
thrown = false;
hasLanded = false;
rb.useGravity = false;
//rb.isKinematic = false;
}
void RollAgain()
{
Reset();
thrown = true;
rb.useGravity = true;
rb.AddTorque(Random.Range(0, 500), Random.Range(0, 500), Random.Range(0, 500));
}
void SideValueCheck()
{
diceValue = 0;
foreach (DiceSide side in diceSides)
{
if (side.OnGround())
{
diceValue = side.sideValue;
Debug.Log(diceValue + " has been rolled!");
}
}
}
}
and this is the sides code(just in case. not really relevant to the problem I am having but I am still attaching just in case)
public class DiceSide : MonoBehaviour
{
bool onGround;
public int sideValue;
void OnTriggerStay(Collider col)
{
if(col.tag == "Ground")
{
onGround = true;
}
}
void OnTriggerExit(Collider col)
{
if(col.tag == "Ground")
{
onGround = false;
}
}
public bool OnGround()
{
return onGround;
}
}
and here's the code I am having the hardest time with...
public class Check : MonoBehaviour
{
public GameObject DiceGameObject1;
public GameObject DiceGameObject2;
private Dice dice;
private Dice dice2;
public int dicevalue1;
public int dicevalue2;
private int score;
private int totalScore;
public TMP_Text Score;
public TMP_Text TotalScore;
public TMP_Text Win;
public TMP_Text Lose;
public TMP_Text Player;
void Start()
{
totalScore = 0;
score = 0;
Win.text = "";
Lose.text = "";
}
void Awake()
{
dice = DiceGameObject1.GetComponent<Dice>();
dice2 = DiceGameObject2.GetComponent<Dice>();
}
void Update()
{
UpdateScore();
}
void SetScoreText()
{
if (dicevalue1 == 1 && dicevalue2 == 1)
{
Lose.text = "You Lose:(";
}
else if (totalScore >= 50)
{
Win.text = "You win!";
}
else
{
TotalScore.text = totalScore.ToString();
}
}
public void UpdateScore()
{
dicevalue1 = dice.diceValue;
dicevalue2 = dice2.diceValue;
score = dicevalue1 + dicevalue2;
Debug.Log(dicevalue1 + dicevalue2);
// how do I import diceValue variables from Dice class and use the values here?
totalScore += score;
SetScoreText();
}
}
So I first had problem using the diceValue variable(value) from Dice script at Check script, but I somehow managed to. I am quite not understanding how, but I tried GetComponent from Dice file to make it somehow work. I was wondering if I should do that with UpdateScore method as well, but I am stuck...
Eventually I am trying to make this a turn-based two-player game, where player take turns. So far I barely managed it to work as one player game...
Please help!!
You are calling UpdateScore() in Update(). Update() is called every frame, so when your SideValueCheck() has evaluated a dice number, the score will rise very quickly to 50. What you need is a condition to query when the dice numbers have been evaluated such that you update the score only once. You could add in your Check class a new flag bool scoreUpdated = false; and change UpdateScore() to
public void UpdateScore()
{
dicevalue1 = dice.diceValue;
dicevalue2 = dice2.diceValue;
// only update if not done already and the dice have meaningful side values
if (!scoreUpdated && dicevalue1 != 0 && dicevalue2 != 0) {
score = dicevalue1 + dicevalue2;
Debug.Log(dicevalue1 + dicevalue2);
// how do I import diceValue variables from Dice class and use the values here?
// (Edit: you did that already)
totalScore += score;
scoreUpdated = true; // track the score update
SetScoreText();
}
}
Now, if you do a new dice roll, you need to first reset the dice value of the rolling dice. Reset() would be edited to:
void Reset()
{
transform.position = initPosition;
thrown = false;
hasLanded = false;
diceValue = 0; // the dice is rolling again, so no valid side value
rb.useGravity = false;
//rb.isKinematic = false;
}
and of course you need to reset the score tracking which you could do maybe in Update() of Dice class, because you located the input code there:
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RollDice();
check.scoreUpdated = false;
}
How to get check? I dont know where you attached it, but you need to get a reference to that game object and then get the script component on it with Check check = go.GetComponent<Check>(); just like you did with the dice variables.

OnCollisionEnter() not working, how i can fix it?

I make random generator and me need destroy object if it touch myself or house, i give colliders to objects.
`
using UnityEngine;
public class TouchObject : MonoBehaviour
{
private void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.name == "Structure") // this is tree
{
Destroy(gameObject);
}
if(collision.gameObject.name == "Floor") // this is details of house
{
Destroy(gameObject);
}
}
}
using static System.Random;
using UnityEngine;
public class RandomGenerator : MonoBehaviour
{
[Header("Игровые объекты")] // game object
public GameObject Tree;
[Header("Лимит строений")] // limit for building
public int StuctLimit;
[Header("Рандомные координаты")] // random position
public int randomPosX;
public int randomPosZ;
private void Generating()
{
if(StuctLimit == 0)
{
System.Random randomPos = new System.Random();
for (int i = 1; i <= 1500; i++)
{
randomPosX = randomPos.Next(10, 290);
randomPosZ = randomPos.Next(10, 290);
Instantiate(Tree, new Vector3(randomPosX, 0.0f, randomPosZ), Quaternion.identity);
}
StuctLimit++;
}
}
private void Start()
{
Generating();
}
}
using UnityEngine;
public class HouseGenerator : MonoBehaviour
{
[Header("Строение дома")] // game object
public GameObject House;
[Header("Лимит строений")] // limit for building
public int StuctLimit;
[Header("Рандомные координаты")] // random position
public int randomPosX;
public int randomPosZ;
private void Generating()
{
if(StuctLimit == 0)
{
System.Random randomPos = new System.Random();
for (int time = 0; time <= 20; time++)
{
randomPosX = randomPos.Next(10, 290);
randomPosZ = randomPos.Next(10, 290);
Instantiate(House, new Vector3(randomPosX, 0.5f, randomPosZ), Quaternion.identity);
}
StuctLimit++;
}
}
private void Start()
{
Generating();
}
}
It was worked, but i round position for set block, i use it(Mathf.Round(range))
I use Instantiate(Object, new Vector3(positions), Quaternion.identity) for create structure
Here code for: Check Touch objects; Generating Trees; Generating House.

Unity C# Health/Damage Script works as intended but returns Object Reference error [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 4 months ago.
Does anyone know why im getting this error?
Object reference not set to an instance of an object
HealthBar.TakeDamage (System.Int32 damage) (at Assets/Scripts/HealthBar.cs:61)
HealthBar.Update () (at Assets/Scripts/HealthBar.cs:36)
the code seems to work as intended when i try to run "TakeDamage()" but i recieve that error after it executes even though its giving me the correct behavior.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HealthBar : MonoBehaviour
{
public CharacterStats stats;
public Slider slider;
// Start is called before the first frame update
void Start()
{
slider.value = slider.maxValue;
}
// Updates the Current Hp Text
public Text valueText;
public void OnSliderChanged(float value)
{
valueText.text = value.ToString();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
HealDamage(30);
Debug.Log("Space");
}
if (Input.GetKeyDown(KeyCode.E))
{
TakeDamage(10);
Debug.Log("E");
}
if (Input.GetKeyDown(KeyCode.Q))
{
TempMaxHealth(5f,20);
Debug.Log("Q");
}
if (Input.GetKeyDown(KeyCode.T))
{
SetHealth(20);
Debug.Log("T");
}
if (Input.GetKeyDown(KeyCode.P))
{
PoisonDamage(2f,10);
Debug.Log("P");
}
}
// Take X amount of Damage
void TakeDamage(int damage)
{
if (stats.Armor.GetValue() <= damage) {
damage -= stats.Armor.GetValue();
damage = Mathf.Clamp(damage, 0, int.MaxValue);
slider.value -= damage;
}
if (slider.value <= 0)
{
Dead();
}
}
// Death Status
void Dead()
{
Debug.Log("You Are Dead");
//filler for death scene
}
// Heal X amount of Damage
void HealDamage(int damage)
{
if (slider.value < slider.maxValue)
{
slider.value += damage;
}
}
// Set Max Health Permanently
public void SetMaxHealth(int health)
{
slider.maxValue = health;
}
// Set Current Health
public void SetHealth(int health)
{
slider.value = health;
}
// Increase MaxHealthPermanently
public void IncreaseMaxHealth(int health)
{
slider.maxValue = slider.maxValue + health;
}
// Temporarily increase Max Health. waitTime = 5f ~ 5 seconds
public void TempMaxHealth(float waitTime,int health)
{
StartCoroutine(TempHealthTime(waitTime, health)); // start time function
}
IEnumerator TempHealthTime(float waitTime, int health)
{
slider.maxValue += health; // adds boost
yield return new WaitForSeconds(waitTime);
Debug.Log("Coroutine ended: " + Time.time + " seconds");
slider.maxValue -= health; // remove boost
}
public void PoisonDamage(float waitTime,int health)
{
StartCoroutine(PoisonDuration(waitTime, health));
}
IEnumerator PoisonDuration(float waitTime,int health)
{
slider.value -= health;
if (slider.value <= 0)
{
slider.value = 1;
}
yield return new WaitForSeconds(waitTime);
if (slider.value > 1)
{
PoisonDamage(waitTime, health);
}
else Debug.Log("Coroutine ended: " + Time.time + " seconds" + slider.value);
}
}
This is because either stats or stats.Armor returns null. If that's excepted, you can do this
// Take X amount of Damage
void TakeDamage(int damage)
{
// note here the use of `?`, this basically says that if is null, use null and don't check after the dot.
var armorValue = stats?.Armor?.GetValue();
if (armorValue is null)
return null;
if (armorValue <= damage) {
damage -= armorValue;
damage = Mathf.Clamp(damage, 0, int.MaxValue);
slider.value -= damage;
}
if (slider.value <= 0)
{
Dead();
}
}

Multiple dice throw mechanic C# Unity

I would like to istantiate multiple dices (you should be able to add and substract dices) and roll them.
For now I can roll a dice and get the readout in the console.
My problem: I can't get multiple dice to work...
These are the scripts:
the dice controller:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiceController : MonoBehaviour
{
public Dice dice;
public GameObject dicePre;
public int count = 1;
void Update()
{
GameObject[] dices = GameObject.FindGameObjectsWithTag("Dice");
if(count - 1 == dices.Length){
for (int i = 0; i < count; i++)
{
Instantiate(dicePre, new Vector3(i * 1.1F, 0, 0), Quaternion.identity);
}
}
else if(count -1 < dices.Length){
return;
}
}
public void Throw()
{
GameObject[] dices = GameObject.FindGameObjectsWithTag("Dice");
foreach(GameObject dic in dices){
dice = dic.GetComponent<Dice>();
dice.RollDice();
}
}
public void Plus(){ //add dice
count++;
}
public void Minus(){ //substract dice
count--;
}
}
the dice sides:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiceSide : MonoBehaviour
{
bool onGround;
public int sideValue;
void OnTriggerStay(Collider col) {
if(col.tag == "ground"){
onGround = true;
}
}
void OnTriggerExit(Collider col) {
if(col.tag == "ground"){
onGround = false;
}
}
public bool OnGround(){
return onGround;
}
}
the main dice script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Dice : MonoBehaviour
{
Rigidbody rb;
bool hasLanded;
bool thrown;
Vector3 initPos;
public int diceValue;
public DiceSide[] diceSides;
private void Start(){
rb = GetComponent<Rigidbody>();
initPos = transform.position;
rb.useGravity = false;
}
private void Update(){
if(Input.GetKeyDown(KeyCode.T)){
RollDice();
}
if(rb.IsSleeping() && !hasLanded && thrown){
hasLanded = true;
rb.useGravity = false;
rb.isKinematic = true;
SideValueCheck();
}
else if(rb.IsSleeping() && hasLanded && diceValue == 0){
RollAgain();
}
}
public void RollDice(){
if(!thrown && !hasLanded){
thrown = true;
rb.useGravity = true;
rb.AddTorque(Random.Range(0,500), Random.Range(0,500), Random.Range(0,500));
}
else if(thrown && hasLanded){
Reset();
}
}
void Reset(){
transform.position = initPos;
thrown = false;
hasLanded = false;
rb.useGravity = false;
rb.isKinematic = false;
}
void RollAgain(){
Reset();
thrown = true;
rb.useGravity = true;
rb.AddTorque(Random.Range(0,500), Random.Range(0,500), Random.Range(0,500));
}
void SideValueCheck(){
diceValue = 0;
foreach(DiceSide side in diceSides){
if(side.OnGround()){
diceValue = side.sideValue;
Debug.Log("Eine " + diceValue + " wurde gewürfelt!");
}
}
}
}
How can I get this to work?
also here you can download the unitypackage with everything i got right now:
https://workupload.com/file/7brN4gTCeLu
First as said I would directly make the prefab field of type
public Dice dicePre;
then I would not use FindGameObjectsWithTag all the time to get current instances.
I would rather keep track of them in a List like e.g.
public class Dice : MonoBehaviour
{
// every instance will add itself to this list
private static List<Dice> instances = new List<Dice>();
// public read only access
public static ReadOnlyCollection<Dice> Instances => instances.AsReadOnly();
// Add yourself to the instances
private void Awake()
{
instances.Add(this);
}
// Remove yourself from the instances
private void OnDestroy()
{
instances.Remove(this);
}
}
So later you can simply use
foreach(var dice in Dice.Instances)
{
dice.RollDice();
}
The main issue
Then currently you are checking
if(count - 1 == dices.Length)
and if so you instantiate count dices.
So what if your dices is empty and your count is 3? -> nothing would happen
Or what if you already have 2 dices but count is 3 -> you spawn 3 dices and end up with 5!
You would need to actually check the difference between the dices amount and count and either add or remove only the difference.
In order to fix this I would not do this in Update but rather using a property like
[SerializeField] private int _count;
public int Count
{
get => _count;
set
{
// Count can not be negative
_count = Mathf.Max(0, value);
// Now do something with this new value
// check difference
var dif = Dice.Instances.Count - _count;
// if 0 -> nothing to do
if(dif == 0)
{
return;
}
// if smaller then 0 -> need more dices
if(dif < 0)
{
for(var i = dif; i < 0; i++)
{
Instantiate(dicePre, Vector3.right * Dice.Instances.Count, Quaternion.identity);
}
}
// dif bigger then 0 -> have to many dices
else
{
for(var i = 0; i < dif; i++)
{
DestroyImmediate(Dice.Instances[Dice.Instances.Count - 1]);
}
}
}
}
[ContextMenu(nameof(Plus))]
public void Plus()
{
Count++;
}
[ContextMenu(nameof(Minus))]
public void Minus()
{
Count--;
}
i do not know unity... so if this is off base with regards to that then i will gladly delete.
public class DiceController : MonoBehaviour
{
public List<Dice> dices;
void DiceController()
{
dices = new list<Dice>();
dices.add(new Dice); //ensure always have at least 1 on start
}
public void Plus()
{
dices.Add(new Dice);
}
//caller can decided, could be random which one get removed.
//assume they all equal
public void Minus(Dice dice){
dices.Remove(dice);
}
public void Throw()
{
foreach(Dice dice in dices){
dice.RollDice();
}
}
}

Unity: How do I start my weapon reload countdown when I am reloading?

I have a Timer that counts down every 3 seconds (The white circle). It has a script attached called ReloadTimer.
I have a script that fires bullets (TankShooter) and reloads for 3 seconds.
How do I make it so that my countdown starts when I am reloading?
I tried looking at a lot of Unity forums and the advice didn't work.
ReloadTimer.cs
[ExecuteInEditMode]
public class ReloadTimer : MonoBehaviour
{
public Image filled;
public Text text;
public float maxValue = 3;
public float value = 0;
// UpdateReload is called once per frame
public void UpdateReload ()
{
value = Mathf.Clamp(value, 0, maxValue);
float amount = value / maxValue;
filled.fillAmount = amount;
text.text = value.ToString();
}
}
TankShooter
public int m_PlayerNumber = 1;
public Rigidbody m_Shell;
public Transform m_FireTransform;
public AudioSource m_ShootingAudio;
public AudioClip m_FireClip;
public float m_ShellVelocity = 100f;
private string m_FireButton;
public int maxAmmo = 5;
private int currentAmmo;
public float reloadTime = 2f;
private bool isReloading = false;
public ReloadTimer reloadTimer;
public class TankShootingT : NetworkBehaviour
{
public ReloadTimer reloadTimer;
private void Start()
{
if (!isLocalPlayer)
{
return;
}
currentAmmo = maxAmmo;
m_FireButton = "Fire" + m_PlayerNumber;
}
private void Update()
{
if (isReloading)
return;
if (currentAmmo <= 0)
{
StartCoroutine(Reload());
return;
}
reloadTimer.UpdateReload();
if (m_FireButton == "Fire1" && Input.GetButtonUp(m_FireButton))
{
// we released the button, have not fired yet
CmdShoot();
}
}
IEnumerator Reload()
{
isReloading = true;
Debug.Log("Reloading...");
yield return new WaitForSeconds(reloadTime);
currentAmmo = maxAmmo;
isReloading = false;
}
[Command]
private void CmdShoot()
{
currentAmmo--;
// Instantiate and launch the shell.
Rigidbody shellInstance = Instantiate(m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody;
shellInstance.velocity = m_ShellVelocity * m_FireTransform.forward;
// Server spawns the shell
NetworkServer.Spawn(shellInstance.gameObject);
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play();
}
}
For starters, There isn't such thing as UpdateReload that would be "called once per frame" as this is not a predetermined Unity function, it is just a function that you created (You can read about this here). Another problem is that you didn't even call that function anywhere else in your scripts. And even if you did, Mathf.Clamp() needs to be placed in an Update() function so it can update it's value each frame.
I made some modifications to the scripts that you posted, but I haven't tested them yet. give it a try and let me know how it goes:
ReloadTimer.cs
public class ReloadTimer : MonoBehaviour
{
public static ReloadTimer Instance { set; get; }
public Image filled;
public Text text;
public float coolDownTime = 3;
public bool isCoolingDown = false;
void Awake()
{
Instance = this;
}
void Update()
{
if (isCoolingDown == true)
{
filled.fillAmount += 1.0f / coolDownTime * Time.deltaTime;
int percentageInt = Mathf.RoundToInt((filled.fillAmount / coolDownTime) * 10);
text.text = percentageInt.ToString();
}
}
}
TankShootingT.cs
public int m_PlayerNumber = 1;
public Rigidbody m_Shell;
public Transform m_FireTransform;
public AudioSource m_ShootingAudio;
public AudioClip m_FireClip;
public float m_ShellVelocity = 100f;
private string m_FireButton;
public int maxAmmo = 5;
private int currentAmmo;
public float reloadTime = 2f;
private bool isReloading = false;
public ReloadTimer reloadTimer;
public class TankShootingT : NetworkBehaviour
{
public ReloadTimer reloadTimer;
private void Start()
{
if (!isLocalPlayer)
{
return;
}
currentAmmo = maxAmmo;
m_FireButton = "Fire" + m_PlayerNumber;
}
private void Update()
{
if (isReloading)
return;
if (currentAmmo <= 0)
{
StartCoroutine(Reload());
return;
}
reloadTimer.UpdateReload();
if (m_FireButton == "Fire1" && Input.GetButtonUp(m_FireButton))
{
// we released the button, have not fired yet
CmdShoot();
}
}
IEnumerator Reload()
{
isReloading = true;
ReloadTimer.Instance.isCoolingDown = true;
Debug.Log("Reloading...");
yield return new WaitForSeconds(reloadTime);
currentAmmo = maxAmmo;
isReloading = false;
ReloadTimer.Instance.isCoolingDown = false;
ReloadTimer.Instance.filled.fillAmount = 0.0f;
}
[Command]
private void CmdShoot()
{
currentAmmo--;
// Instantiate and launch the shell.
Rigidbody shellInstance = Instantiate(m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody;
shellInstance.velocity = m_ShellVelocity * m_FireTransform.forward;
// Server spawns the shell
NetworkServer.Spawn(shellInstance.gameObject);
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play();
}
}
Hope this helps a bit.

Categories