In Unity how do I stop perpetual math statements? - c#

The question is simple but I can't for the life of me, figure it out.
My logic goes like this
// Static floats are StatBase.maxHealth = 0 and rStat.maxHealth = 70
class rStat : Monobehaviour
{
public bool nomatter = false;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
nomatter = true;
}
if (nomatter == true)
{
healthcalc();
}
void healthcalc()
{
StatBase.maxHealth += rstat.maxHealth; // StatBase.maxHealth should = 70 but the
// number never stops adding
nomatter = false;
}
}
}

To be honest that logic is quite strange.
Why have this bool flag if you already have one you want to act on? You can simply do
void Update()
{
if (Input.GetMouseButtonDown(0))
{
healthcalc();
}
}
// in general rather put this on class level and don't nest it under Update
void healthcalc()
{
StatBase.maxHealth += rstat.maxHealth;
}
or if there is only one line anyway even
void Update()
{
if (Input.GetMouseButtonDown(0))
{
StatBase.maxHealth += rstat.maxHealth;
}
}

Related

Shuffling an array and assigning it to different buttons in Unity

I have an array that I am shuffling and then assigning to buttons. Depending on the item in the array, the button will perform a different function. This is currently my code, and it works, but is extremely inelegant.
I am currently shuffling the array first, then assigning the first 2 items of the shuffled array to the button text, and then using if statements to check whether or not the string matches so that it will execute a specific code for that string.
There is definitely a better way to do this, but I can't seem to figure it out.
public TextMeshProUGUI firstChoiceText;
public TextMeshProUGUI secondChoiceText;
public GameObject player;
public string[] upgrades =
{
"Speed Up",
"Fire Rate Up",
"Damage Up",
"Max Health Up"
};
public void Shuffle(string[] array)
{
for (int i = 0; i < array.Length; i++)
{
string tmp = array[i];
int rand = Random.Range(0, array.Length);
array[i] = array[rand];
array[rand] = tmp;
}
firstChoiceText.text = upgrades[0];
secondChoiceText.text = upgrades[1];
}
// Start is called before the first frame update
void Start()
{
Shuffle(upgrades);
}
public void FirstChoice()
{
Debug.Log("first choice clicked");
if (firstChoiceText.text == "Speed Up")
{
player.GetComponent<PlayerController>().playerSpeed += 1;
}
else if (firstChoiceText.text == "Fire Rate Up")
{
player.GetComponent<PlayerController>().fireRate -= 0.05f;
}
else if (firstChoiceText.text == "Damage Up")
{
player.GetComponent<PlayerController>().playerDamage *= 1.1f;
}
else if (firstChoiceText.text == "Max Health Up")
{
GameManager.maxHealth += 5;
player.GetComponent<PlayerController>().Heal(5);
}
Time.timeScale = 1;
gameObject.SetActive(false);
Shuffle(upgrades);
}
public void SecondChoice()
{
Debug.Log("second choice clicked");
if (secondChoiceText.text == "Speed Up")
{
player.GetComponent<PlayerController>().playerSpeed += 1;
}
else if (secondChoiceText.text == "Fire Rate Up")
{
player.GetComponent<PlayerController>().fireRate -= 0.05f;
}
else if (secondChoiceText.text == "Damage Up")
{
player.GetComponent<PlayerController>().playerDamage *= 1.1f;
}
else if (secondChoiceText.text == "Max Health Up")
{
GameManager.maxHealth += 5;
player.GetComponent<PlayerController>().Heal(5);
}
Time.timeScale = 1;
gameObject.SetActive(false);
Shuffle(upgrades);
}
One solution would be to create a Dictionary<string, Action<Player>> where your Action delegate corresponds to your string key and call the method via Action delegate based on the string key of the Dictionary, also moved your shuffle logic into it's own private method to reduce code duplication:
//Dictionary to hold string key/Action delegate pairs
private Dictionary<string, Action<Player>> _actions = new Dictionary<string, Action<string>>
{
{"Speed Up", (player) => player.GetComponent<PlayerController>().playerSpeed += 1;},
{"Fire Rate Up", (player) => player.GetComponent<PlayerController>().fireRate -= 0.05f;}
{"Damage Up", (player) => player.GetComponent<PlayerController>().playerDamage *= 1.1f;},
{"Max Health Up", (player) => { GameManager.maxHealth += 5;
player.GetComponent<PlayerController>().Heal(5); } }
};
//You could reduce your First And Second Choice down to using the
//dictionary to call the cooresponding Action delegate:
public void FirstChoice()
{
Debug.Log("first choice clicked");
_actions[firstChoiceText.text](player);
DoShuffle();
}
public void SecondChoice()
{
Debug.Log("second choice clicked");
_actions[secondChoiceText.text](player);
DoShuffle();
}
//Moved this into a method to reduce repetitive code
private void DoShuffle()
{
Time.timeScale = 1;
gameObject.SetActive(false);
Shuffle(upgrades);
}
You could create a base class for upgrades, and then use inheritance to split up all the logic that is now in the if statements to separate upgrade classes deriving from this base class. In the Unity world you might want to use ScriptableObjects for this.
public abstract class Upgrade : ScriptableOject
{
public abstract void Apply(PlayerController player);
}
[CreateAssetMenu(menuName = "Upgrades/Speed Up", fileName = "Speed Up")]
public sealed class SpeedUp : Upgrade
{
public override void Apply(PlayerController player)
{
player.playerSpeed += 1;
}
}
[CreateAssetMenu(menuName = "Upgrades/Fire Rate Up", fileName = "Fire Rate Up")]
public sealed class FireRateUp : Upgrade
{
public override void Apply(PlayerController player)
{
player.fireRate -= 0.05f;
}
}
Then you could create one scriptable object asset for each upgrade, and then assign all of them to your script into an Upgrade[] field.
[SerializeField] private TextMeshProUGUI firstChoiceText;
[SerializeField] private TextMeshProUGUI secondChoiceText;
[SerializeField] private PlayerController player;
[SerializeField] private Upgrade[] upgrades;
private Upgrade firstUpgrade;
private Upgrade secondUpgrade;
public void ApplyFirstUpgrade()
{
Debug.Log("first choice clicked");
ApplyUpgrade(firstUpgdade);
}
public void ApplySecondUpgrade()
{
Debug.Log("second choice clicked");
ApplyUpgrade(secondUpgrade);
}
private void Awake() => RandomizeUpgrades();
private void RandomizeUpgrades()
{
firstUpgrade = GetRandomUpgrade();
secondUpgdade = GetRandomUpgrade(firstChoice);
}
private Upgrade GetRandomUpgrade() => upgrades[Random.Range(0, upgrades.Length)];
private Upgrade GetRandomUpgrade(Upgrade ignore)
{
if(upgrades.Length < 2)
{
Debug.LogError("At least 2 upgrades need to be assigned before calling GetRandomUpgrade.", this);
return;
}
Upgrade resultCandiate = GetRandomUpgrade();
if(resultCandiate != ignore)
{
return result;
}
return GetRandomUpgdate(ignore);
}
private void ApplyUpgrade(Upgrade upgrade)
{
upgrade.Apply();
Time.timeScale = 1;
gameObject.SetActive(false);
RandomizeUpgrades();
}
The benefit with this sort of approach is that you can add more abilities easily without having to make any modifications to existing code, and without ending up with one giant class with hundreds of lines of code.

Run more than expected number

Please do not get too hard about my grammar.
I write follow class for delay
public class Queue_System_Of_Begin_Game : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Game_Controller.Player1_First_throws_true && Game_Controller.Player2_First_throws_true)
{
StartCoroutine(ExecuteAfterTime(1));
}
}
//--------------------------------------
public GameObject player1_icon, player2_icon, dice1_p1, dice2_p1, dice1_p2, dice2_p2;
void determine_the_turn()
{
Debug.Log("update");
}
IEnumerator ExecuteAfterTime(float time)
{
yield return new WaitForSeconds(time);
determine_the_turn();
}
}
I receive 62 times the word update on the console.
This problem will cause my next round of games to run 62 times, which slowed down my game.
The Update() method is called once per frame, thats the reason you get 62 "updates".
You can try adding a boolean so it only gets called once like this:
bool ischecked = false;
void Update(){
if (!ischecked){
if (Game_Controller.Player1_First_throws_true && Game_Controller.Player2_First_throws_true) {
ischecked = true;
StartCoroutine (ExecuteAfterTime (1));
}
}
}
I find solution for my problem .I must use a boolean variable in my if commond like this :
public class Queue_System_Of_Begin_Game : MonoBehaviour
{
private bool coroutineStarted;
// Update is called once per frame
void Update()
{
if (!coroutineStarted && Game_Controller.Player1_First_throws_true && Game_Controller.Player2_First_throws_true)
{
coroutineStarted = true ;
StartCoroutine(ExecuteAfterTime(1));
}
}
//--------------------------------------
public GameObject player1_icon, player2_icon, dice1_p1, dice2_p1, dice1_p2, dice2_p2;
void determine_the_turn()
{
Debug.Log("update");
}
IEnumerator ExecuteAfterTime(float time)
{
yield return new WaitForSeconds(time);
determine_the_turn();
}
}

Better way to ensure a method logic executed once without boolean flag

This is a way to execute Dosomething logic once using flag. (C# code and Update is always called once per frame.)
And it's not so complicated, simple, very plain and well used way.
class Monster {
bool isCalled = false;
float energy = 0.0f;
void Update()
{
energy += Random.Range(0f, 1f);
if((isCalled == false) && (energy>100.0f))
{
isCalled = true;
DoSomething();
}
}
void DoSomething(){}
}
But, I think the management of boolean flag is a kind of tiresome task. So I am trying to find better alternatives.
Is there any better or elegant way to do this (executing Dosomething once) without boolean flag?For example, another design pattern's way, etc.
I'd rather combine all such flags into a single enum, like that:
class Monster {
[Flags]
private enum Status {
Updated,
Called,
Killed,
...
}
private Status status;
void Update() {
if ((status & Status.Updated) == Status.Updated)
return;
try {
....
}
finally {
status |= Status.Updated;
}
}
}
Using dedicated well-named boolean flag is clear and common pattern.
Often you don't need dedicated flag, e.g. old singleton patterns doesn't use bool, but rather testings special value:
if(instance == null)
{
.. // do something
}
Logic is clear enough as you can see. People often use other special values to avoid necessity of introducing flag: string.IsNullOrEmpty, double.IsNaN, negative value, etc.
Important is to have intentions clear, don't obscure logic with too many small details. If there are too many things to take care about - rather introduce a dedicated flag.
In your case you may want to start using state-machine more obviously, because I'd assume what Monster can be in many different states which influence what various methods do:
class Monster
{
enum States { NotInitialized, Dead, Normal, EnergyMax, ... }
States _state;
float _energy;
void Update()
{
_energy += Random.Range(0f, 1f);
switch(_state)
{
case States.Normal:
if(_energy > EnergyMax)
{
DoSomething(); // called once when energy become max
_state = States.EnergyMax;
}
break;
...
}
}
...
}
Well, you can always replace DoSomething with NOP action once it is executed:
class Monster {
float energy = 0.0f;
Action onUpdate;
public Monster()
{
onUpdate = DoSomething;
}
void Update()
{
onUpdate();
}
void DoSomething()
{
energy += Random.Range(0f, 1f);
if(energy > 100.0f)
{
// whatever you need to do
}
onUpdate = () => {};
}
}
However, I believe that most developers are accustomed to using boolean flag to track this, and you might get less raised eyebrows if you go that route.
You could do something like this:
class Monster
{
private Action _doSomething;
public Monster()
{
_doSomething = DoSomething;
}
float energy = 0.0f;
void Update()
{
energy += Random.Range(0f, 1f);
if (energy > 100.0f)
if (_doSomething != null)
_doSomething();
}
void DoSomething()
{
// logic...
_doSomething = null;
}
}
But still i think a flag is a better practice. Something has to be toggled. a flag/reference..
You could use the state pattern like this:
class Monster
{
float energy = 0.0f;
DoSomethingState state;
public Monster()
{
this.state = new DoSomethingStateNotCalled(this);
}
public void Update()
{
energy += Random.Range(0f, 1f);
this.state.Update();
}
public void DoSomething() {
System.Diagnostics.Debug.Write("done something");
}
public float GetEnergy() {
return this.energy;
}
public void SetState(DoSomethingState state) {
this.state = state;
}
}
abstract class DoSomethingState {
protected Monster Monster;
public DoSomethingState(Monster monster) {
this.Monster = monster;
}
public abstract void Update();
}
class DoSomethingStateCalled : DoSomethingState
{
public DoSomethingStateCalled(Monster monster)
: base(monster)
{
}
public override void Update()
{
}
}
class DoSomethingStateNotCalled : DoSomethingState
{
public DoSomethingStateNotCalled(Monster monster)
: base(monster)
{
}
public override void Update()
{
if (this.Monster.GetEnergy() > 100.0f)
{
this.Monster.DoSomething();
this.Monster.SetState(new DoSomethingStateCalled(this.Monster));
}
}
}

Unity3D C# not going to the else statement (Invoke Repeating)

I'm currently making my first game using Unity3D written in C#. I've faced some game bug just now, and it's been hours that I've been thinking of what might have been wrong with my code, but I can't see anything wrong.
I have a stat called healthRegen which add HP to the character every second. What I have noticed is even when my player HP drops to zero, it just keep adding HP to my player, thus making it alive again. I have a method that checks if my character HP drops to zero but it didn't call it and I don't know why.
public bool fullHealth() {
if(currentHealth >= maxHealth) {
return true;
} else {
return false;
}
}
public void adjustHealth() {
if(currentHealth > maxHealth) {
currentHealth = maxHealth;
}
if(currentHealth < 0) {
currentHealth = 0;
}
}
That is my method and this is my player script
void Start() {
InvokeRepeating("regenerate", 0f, 1f);
}
// Check if the player is still alive
public bool isDead() {
if (attribute.currentHealth == 0) {
return true;
} else {
return false;
}
}
// Die method
public void dead() {
animation.Play(dieClip.name);
}
private void regenerate() {
if (!attribute.fullHealth()) {
attribute.currentHealth += attribute.healthRegen;
} else {
attribute.adjustHealth();
}
}
This maybe a dumb question for others but I'm sorry, I don't know what to do anymore.
Looks like your problem is that regenerate() is adding health to your player if his health is at 0.
You just need to add a call to isDead() to prevent this.
private void regenerate() {
if (!attribute.fullHealth() && !attribute.isDead()) {
attribute.currentHealth += attribute.healthRegen;
} else {
attribute.adjustHealth();
}
}
I just found out a solution right now, I forgot the CancelInvoke() method
private void regenerate() {
if (!attribute.fullHealth()) {
attribute.currentHealth += attribute.healthRegen;
} else {
CancelInvoke("regenerate");
attribute.adjustHealth();
}
}
Now it's working perfectly!

Unity3d Interactive pause menu that has uses Input.GetAxis to Navigate the new UI

Okay So I'm trying to create an interactive pause menu that has access to Input.GetAxis using Unity 4.6's new UI.
As long as the Navigation relationships are set up for the selectable elements (In my case a series of buttons) and Interactable is set to true then the UI works just fine but, when I set the Time.timeScale = 0.0f; then keyboard input navigation no longer works. However the mouse input clicks and animation still work as intended.
I've tried several different ways to get around the time scale problem and the best I've got so far is checking the value returned from Input.GetAxis() while in the body of the Update message of the MonoBehavor base object. This somewhat works but my results are either the very top or very bottom of the Button selected collection. I'm thinking this is because update gets called a great deal more than FixedUpdate and would make sense if my console printed out more call to the method that moves up and down the selection. So with that I'm thinking its one of those "office space" type errors, off by 1 decimal place or something silly but I just can't seem to see it. Otherwise I think my solution would be a fairly viable work around.
The following is an image of my Unity Setup with mentioned game objects in Unity 4.6 followed by my code.
public class PlayerInGameMenu : MonoBehaviour
{
public EventSystem eventSystem;
Selectable SelectedButton;
public Selectable Status;
public Selectable Settings;
public Selectable Save;
public Selectable Quit;
public bool Paused;
List<Selectable> buttons;
int selecteButtonIndex = 0;
public Canvas Menu;
void Start()
{
Menu.enabled = false;
buttons = new List<Selectable>();
buttons.Add(Status);
buttons.Add(Settings);
buttons.Add(Save);
buttons.Add(Quit);
SelectedButton = buttons[0];
}
void Update()
{
CheckInput();
if (Paused && !Menu.enabled)
{
ShowMenu();
}
else if (!Paused && Menu.enabled)
{
HideMenu();
}
}
void ShowMenu()
{
Paused = true;
Menu.enabled = true;
Time.timeScale = 0.0f;
}
void HideMenu()
{
if (Menu.enabled)
{
Paused = false;
Menu.enabled = false;
Time.timeScale = 1.0f;
}
}
void CheckInput()
{
if (cInput.GetKeyDown("Pause"))
{
Paused = !Paused;
SelectedButton = buttons[selecteButtonIndex];
eventSystem.SetSelectedGameObject(SelectedButton.gameObject, new BaseEventData(eventSystem));
}
if (Paused)
{
float v = cInput.GetAxis("Vertical");
//to attempt to cut down on the input sensitity I am checking 0.5 instead of just 0.0
if (v >= 0.5)
{
GoDown();
}
else if (v <= -0.5)
{
GoUp();
}
}
}
void GoDown()
{
//go to the last button available if we go past the index
if (selecteButtonIndex > buttons.Count - 1)
{
selecteButtonIndex = buttons.Count - 1;
}
else
{
selecteButtonIndex = selecteButtonIndex + 1;
}
}
//go to the first button available if we go past the index
void GoUp()
{
if (selecteButtonIndex < 0)
{
selecteButtonIndex = 0;
}
else
{
selecteButtonIndex = selecteButtonIndex - 1;
}
}
}
I know its in beta but I'm wondering if you are going to implement navigation why would you design it in such a way that Time.timeScale=0.0f; (the easy way to pause a game) does not work with the UI button navigation naturally. Problems for minds greater than I maybe? Or there is a simple way to do it and I just do not know what bit I need to flip.
I've also considered just freezing rigid bodies on pause but that seems like will require a huge time investment in my existing code base and will not be a universal solution across all game objects particularly colliders that do not rely on Rigid bodies and particle systems. I'm pretty open minded about solutions but it seems like there should be a really easy way to do this.
This worked like a charm:
var v = Input.GetAxisRaw("JoyY1"); // Or "Vertical"
if (Math.Abs(v) > ButtonThreashold)
{
var currentlySelected = EventSystem.currentSelectedGameObject
? EventSystem.currentSelectedGameObject.GetComponent<Selectable>()
: FindObjectOfType<Selectable>();
Selectable nextToSelect = null;
if (v > ButtonThreashold)
{
nextToSelect = currentlySelected.FindSelectableOnUp();
}
else if (v < -ButtonThreashold)
{
nextToSelect = currentlySelected.FindSelectableOnDown();
}
if (nextToSelect)
{
EventSystem.SetSelectedGameObject(nextToSelect.gameObject);
}
}
Okay so my solution to this problem was to utilize Time.realtimeSinceStartup to check for input on fixed intervals and develop an abstract class that inherits from MonoBehavior. What that looks like in code:
public abstract class RealtimeMonoBehavior:MonoBehaviour
{
public float updateInterval = 0.5F;
private double lastInterval;
void Start()
{
DefaultIntervalStart();
lastInterval = Time.realtimeSinceStartup;
RealtimeIntervalStart();
}
void Update()
{
DefaultIntervalUpdate();
float timeNow = Time.realtimeSinceStartup;
if (timeNow > lastInterval + updateInterval)
{
lastInterval = timeNow;
RealtimeIntervalUpdate();
}
}
public virtual void DefaultIntervalUpdate(){}
public virtual void DefaultIntervalStart(){}
public virtual void RealtimeIntervalStart(){}
public virtual void RealtimeIntervalUpdate(){}
}
And here is what my code looks like after implementing the change
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using Extensions;
using UnityEngine.EventSystems;
public class PlayerInGameMenu : RealtimeMonoBehavior
{
public EventSystem eventSystem;
Selectable SelectedButton;
public Selectable Status;
public Selectable Settings;
public Selectable Save;
public Selectable Quit;
public float ButtonThreashold;
public bool Paused;
List<Selectable> buttons;
int selectedButtonIndex;
public PlayerMovement PlayerMovement;
public Canvas Menu;
public override void RealtimeIntervalStart()
{
base.RealtimeIntervalStart();
Menu.enabled = false;
buttons = new List<Selectable>();
buttons.Add(Status);
buttons.Add(Settings);
buttons.Add(Save);
buttons.Add(Quit);
selectedButtonIndex = 0;
SelectedButton = buttons[selectedButtonIndex];
eventSystem.SetSelectedGameObject(SelectedButton.gameObject, new BaseEventData(eventSystem));
}
public override void DefaultIntervalUpdate()
{
base.DefaultIntervalUpdate();
if (cInput.GetKeyDown("Pause"))
{
Paused = !Paused;
}
}
public override void RealtimeIntervalUpdate()
{
base.RealtimeIntervalUpdate();
CheckInput();
if (Paused && !Menu.enabled)
{
ShowMenu();
}
else if (!Paused && Menu.enabled)
{
HideMenu();
}
}
void ShowMenu()
{
Paused = true;
Menu.enabled = true;
Time.timeScale = 0.0f;
}
void HideMenu()
{
if (Menu.enabled)
{
Paused = false;
Menu.enabled = false;
Time.timeScale = 1.0f;
}
}
void CheckInput()
{
if (Paused)
{
float v = Input.GetAxisRaw("Vertical");
if (v > ButtonThreashold)
{
GoUp();
}
else if (v < -ButtonThreashold)
{
GoDown();
}
SelectedButton = buttons[selectedButtonIndex];
eventSystem.SetSelectedGameObject(SelectedButton.gameObject, new BaseEventData(eventSystem));
}
}
void GoDown()
{
if (selectedButtonIndex < buttons.Count - 1)
{
selectedButtonIndex = selectedButtonIndex + 1;
}
}
void GoUp()
{
if (selectedButtonIndex > 0)
{
selectedButtonIndex = selectedButtonIndex - 1;
}
}
}
Nod to imapler, Input.GetAxisRaw feels better for checking the input.

Categories