Shuffling an array and assigning it to different buttons in Unity - c#

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.

Related

Why isn't the number subtracted in PlayerPrefs?

Hello everyone In my code, when you click on the button, a number should be subtracted and saved in PlayerPrefs , the number is subtracted, but does not save . I also learned that the number is saved when you exit the game because of the OnApplicationQuit method, how to make the number normally saved?
public class Chest_Money : MonoBehaviour
{
public int NumCharector;
public int money;
public int Rand;
public Text text;
public GameObject NotMoney;
public GameObject[] Charectors;
public GameObject Panel;
public GameObject Panel2;
public bool bla2;
void Start()
{
money = PlayerPrefs.GetInt("Money_S", 0);
}
private void Awake()
{
}
#if UNITY_ANDROID && !UNITY_EDITOR
private void OnApplicationPause(bool pause){
if(pause){
//PlayerPrefs.SetInt("Money_S" , money);
}
}
#endif
private void OnApplicationQuit()
{
PlayerPrefs.SetInt("Money_S" , money);
}
void Update()
{
PlayerPrefs.SetInt("Money_S", money);
text.text = "$:" + money;
if(bla2 == true){
Panel2.SetActive(true);
}
}
public void Chest(){
if(money >= 100){
money -= 100;
PlayerPrefs.SetInt("Money_S", money);
StartCoroutine(Wait2());
}
else {
NotMoney.SetActive(true);
StartCoroutine(Wait4());
}
}
IEnumerator Wait4(){
yield return new WaitForSeconds(2);
NotMoney.SetActive(false);
}
IEnumerator Wait2()
{
yield return new WaitForSeconds(0);
SceneManager.LoadScene("Scins");
}
}
In general you can/should use PlayerPrefs.Save to force a save on certain checkpoints
By default Unity writes preferences to disk during OnApplicationQuit(). In cases when the game crashes or otherwise prematuraly exits, you might want to write the PlayerPrefs at sensible 'checkpoints' in your game. This function will write to disk potentially causing a small hiccup, therefore it is not recommended to call during actual gameplay.
I then also suggest you rather use a property like
private int _money;
public int money
{
get => _money;
set
{
_money = value;
PlayerPrefs.SetInt("Money_S");
PlayerPrefs.Save();
text.text = "$:" + money;
}
}
So it only updates the text and saves the value once it actually changes.
This way you don't need to update the text or set the value every frame in Update.

Syncing UI Canvas to Photon Network | PunRPC

I have an issue where I cannot sync UI Canvas information to PhotonNetwork. It syncs just fine without Photon, and I get correct information when I debug it. But if i start it with photon, it does not sync.
Code below:
void Start()
{
PV = GetComponent<PhotonView>();
Debug.Log("I know who is : " + PV);
}
void Update()
{
PV.RPC("DisplayHP", RpcTarget.All);
DisplayHP();
ShootTest();
Debug.Log("Player HP : " + playerHP);
}
}
[PunRPC]
void DisplayHP()
{
if(PV.IsMine)
{
showHP.text = playerHP.ToString();
}
}
}
possibly I am using it in wrong way, can someone please shed some lights what I am doing wrong in here? This is just an example from Health Canvas, other canvas also acting exactly same as health canvas.
Full script below :
[SerializeField] float playerHP = 100f;
[SerializeField] TextMeshProUGUI showHP;
[SerializeField] AudioClip audioClip;
public int armorCheck;
public int energyCheck;
float energyBack = 25;
//public PhotonView PV;
void Start()
{
// PV = GetComponent<PhotonView>();
}
public void EnergyPot()
{
//if(PV.IsMine)
//{
Ammo currentAmmo = GetComponent<Ammo>();
int energyCheck = GetComponent<Ammo>().GetCurrentAmmo(AmmoType.EnergyDrink);
if (energyCheck > 0)
{
playerHP = energyBack + playerHP;
if (playerHP >= 100)
{
playerHP = 100;
currentAmmo.ReduceCurrentAmmo(AmmoType.EnergyDrink);
}
}
//}
}
public void TakeDamage(float damage)
{
// if (PV.IsMine)
//{
Ammo currentAmmo = GetComponent<Ammo>();
int armorCheck = GetComponent<Ammo>().GetCurrentAmmo(AmmoType.Armor);
if (armorCheck <= 0)
{
playerHP -= damage;
if (playerHP <= 0)
{
GetComponent<DeathHandler>().HandleDeath();
AudioSource.PlayClipAtPoint(audioClip, Camera.main.transform.position);
GetComponent<Animator>().SetTrigger("Dead_Player");
}
}
else
{
currentAmmo.ReduceCurrentAmmo(AmmoType.Armor);
}
}
//}
//public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
//{
// if(stream.IsWriting)
// {
// //DisplayHP();
// stream.SendNext(playerHP);
// }
// else if (stream.IsReading)
// {
// //DisplayHP();
// playerHP = (float)stream.ReceiveNext();
// }
//}
void Update()
{
DisplayHP();
ShootTest();
}
void ShootTest()
{
if (Input.GetMouseButtonDown(0))
{
GetComponent<Animator>().SetBool("Shoot_Player", true);
// GetComponent<Animator>().SetTrigger("ShootPlayer");
}
// GetComponent<Animator>().SetBool("Shoot_Player", false);
}
void DisplayHP()
{
//if(PV.IsMine)
//{
showHP.text = playerHP.ToString();
//Debug.Log("PV works under DisplayHP, code : " + PV);
Debug.Log("Player HP : " + playerHP);
//}
}
}
You are only syncing a parameter less method call which updates a display .. but your are never updating the information to display: playerHP
PhotonView.RPC takes the method name, RpcTarget and then optionally as many parameters as you like / your method expects.
void Start()
{
PV = GetComponent<PhotonView>();
Debug.Log("I know who is : " + PV);
}
private void Update()
{
if(PV.IsMine)
{
ShootTest();
Debug.Log("Player HP : " + playerHP);
}
}
// only executed every 0.2 seconds to reduce traffic
void FixedUpdate()
{
if(PV.IsMine)
{
// pass in the value to sync
PV.RPC(nameof(DisplayHP), RpcTarget.Others, playerHP);
DisplayHP(playerHP);
}
}
[PunRPC]
void DisplayHP(float hp)
{
// receive the synced value if needed
playerHP = hp;
showHP.text = hp.ToString();
}
Actually to avoid more redundant network traffic you should use a property which automatically updates the display whenever it is changed and only sends the remote call whenever the playerHP actually changes like e.g.
[SerializeField] private float _playerHP;
private float playerHP
{
get => _playerHP;
set
{
_playerHP = value;
showHP.text = playerHP.ToString();
if(PV.IsMine)
{
// sync the change to others
PV.RPC(nameof(RemoteSetHP), RpcTarget.Others, _playerHP);
}
}
}
[PunRPC]
void RemoteSetHP(float hp)
{
// this executes the setter on all remote players
// and thus automatically also updates the display
playerHP = hp;
}
so whenever you set
playerHP = XY;
it automatically updates the display and if it is your photon view it sends the remote call to others and also update the value and display there.
Additional to my comment, here is an example. Let's assume you have Text for your HP and for other player HP. First, you change your HP by calling UpdateHP method, you update Text for your HP and send RPC to others, that updates otherHp on their clients (because you will be 'other' on their machines) with your new value for your HP.
public Text myHpText;
public Text otherHpText;
void UpdateHp(int hp)
{
myHpText.text = hp.ToString();
PV.RPC("UpdateOtherText", RpcTarget.Others, myHp);
}
[PunRPC]
void UpdateOtherText(int hp)
{
otherHpText.text = hp.ToString();
}

Passing data between scenes Errors

I have made a game manager making sure my data gets passed on from the first scene on to the next scene. Within the game manager, I have added certain scripts to ensure it gets passed. As you can see in the picture underneath.
The problem is that the score adds up at the first level, let's say I have 5 points. I go to level 2 and the UI shows my score as 0 (even tho I have nothing put as text within the score text) I kill 1 monster and the UI shows 6. So how can I put the UI to be showing it at all times? (Constant refresh?)
The second problem is that while the score manager does work. The health script cancels everything out when switching levels.
The user starts with 10 health. Takes damage in the first scene, but in the second scene, the user still has 10 health for some reason?
Game Manager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour {
public ActionButton PopupPrefab;
private ActionButton currentlySpawnedPopup;
public static GameManager instance = null;
void Awake () {
if (instance == null) {
instance = this;
} else if (instance != this) {
Destroy (gameObject);
}
Application.targetFrameRate = 60;
}
void Update () {
//if (Input.GetKeyDown(KeyCode.R)) {
// RestartScene ();
//}
}
public void InvokeRestartScene (float time) {
Invoke ("RestartScene", time);
}
public void RestartScene () {
SceneManager.LoadScene (0);
}
public void SpawnPopup (Vector2 position) {
DespawnPopup ();
currentlySpawnedPopup = Instantiate (PopupPrefab, position, Quaternion.identity) as ActionButton;
}
public void DespawnPopup () {
if (currentlySpawnedPopup != null) {
currentlySpawnedPopup.DestroySelf();
currentlySpawnedPopup = null;
}
}
public void FadePopup () {
if (currentlySpawnedPopup != null) {
currentlySpawnedPopup.FadeMe ();
currentlySpawnedPopup = null;
}
}
}
Score Manager
using UnityEngine;
using System;
using System.Collections;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
public int Score { get; private set; }
public int HighScore { get; private set; }
public bool HasNewHighScore { get; private set; }
public static event Action<int> ScoreUpdated = delegate {};
public static event Action<int> HighscoreUpdated = delegate {};
private const string HIGHSCORE = "HIGHSCORE";
// key name to store high score in PlayerPrefs
void Awake()
{
if (Instance)
{
DestroyImmediate(gameObject);
}
else
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
void Start()
{
Reset();
}
public void Reset()
{
// Initialize score
Score = 0;
// Initialize highscore
HighScore = PlayerPrefs.GetInt(HIGHSCORE, 0);
HasNewHighScore = false;
}
public void AddScore(int amount)
{
Score += amount;
// Fire event
ScoreUpdated(Score);
if (Score > HighScore)
{
UpdateHighScore(Score);
HasNewHighScore = true;
}
else
{
HasNewHighScore = false;
}
}
public void UpdateHighScore(int newHighScore)
{
// Update highscore if player has made a new one
if (newHighScore > HighScore)
{
HighScore = newHighScore;
PlayerPrefs.SetInt(HIGHSCORE, HighScore);
HighscoreUpdated(HighScore);
}
}
}
Health Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class Health : MonoBehaviour {
public UnityEvent OnTakeDamageEvent;
public UnityEvent OnTakeHealEvent;
public UnityEvent OnDeathEvent;
[Header ("Max/Starting Health")]
public int maxHealth;
[Header ("Current Health")]
public int health;
[Header ("IsDeathOrNot")]
public bool dead = false;
[Header ("Invincible")]
public bool invincible = false;
public bool becomeInvincibleOnHit = false;
public float invincibleTimeOnHit = 1f;
private float invincibleTimer = 0f;
[Header ("Perform Dead Events after x time")]
public float DieEventsAfterTime = 1f;
void Start () {
health = maxHealth;
}
void Update () {
if (invincibleTimer > 0f) {
invincibleTimer -= Time.deltaTime;
if (invincibleTimer <= 0f) {
if (invincible)
invincible = false;
}
}
}
public bool TakeDamage (int amount) {
if (dead || invincible)
return false;
health = Mathf.Max (0, health - amount);
if (OnTakeDamageEvent != null)
OnTakeDamageEvent.Invoke();
if (health <= 0) {
Die ();
} else {
if (becomeInvincibleOnHit) {
invincible = true;
invincibleTimer = invincibleTimeOnHit;
}
}
return true;
}
public bool TakeHeal (int amount) {
if (dead || health == maxHealth)
return false;
health = Mathf.Min (maxHealth, health + amount);
if (OnTakeHealEvent != null)
OnTakeHealEvent.Invoke();
return true;
}
public void Die () {
dead = true;
if (CameraShaker.instance != null) {
CameraShaker.instance.InitShake(0.2f, 1f);
}
StartCoroutine (DeathEventsRoutine (DieEventsAfterTime));
}
IEnumerator DeathEventsRoutine (float time) {
yield return new WaitForSeconds (time);
if (OnDeathEvent != null)
OnDeathEvent.Invoke();
}
public void SetUIHealthBar () {
if (UIHeartsHealthBar.instance != null) {
UIHeartsHealthBar.instance.SetHearts (health);
}
}
}
I have thought of adding the following script on to my Health Script
But then I get the following error messages:
" Cannot implicitly convert type 'int' to 'bool'"
"The left-hand side of an assignment must be a variable, property or indexer"
void Awake()
{
if (health)
{
DestroyImmediate(gameObject);
}
else
{
(int)health = this;
DontDestroyOnLoad(gameObject);
}
}
The problem is that the score adds up at the first level, let's say I have 5 points. I go to level 2 and the UI shows my score as 0 (even tho I have nothing put as text within the score text) I kill 1 monster and the UI shows 6. So how can I put the UI to be showing it at all times? (Constant refresh?)
You can make one of the scripts set the UI text score when the scene is loaded.
void Start(){
// Loads the scoreText on start
scoreText.text = yourCurrentScore.ToString();
// Will work unless it has a "DontDestroyOnLoad" applied to it
}
The second problem is that while the score manager does work. The
health script cancels everything out when switching levels. The user
starts with 10 health. Takes damage in the first scene, but in the
second scene, the user still has 10 health for some reason?
In your health script, you had this:
void Start () {
health = maxHealth;
}
This resets your health everytime the scene loads and starts (Aka when you load to the next level). This causes the issue.
" Cannot implicitly convert type 'int' to 'bool'"
if (health)
The () for the if statement should be a condition (a question).
For example, doing health < 0 is valid since its saying "Is health less than 0?"
Doing health is not, since its just saying "10" (or some number).
"The left-hand side of an assignment must be a variable, property or
indexer"
(int)health = this;
If you wanted to change the value of health, just do health = 10 or health = some_Variable_That_Is_An_Integer

Getting text to stay when changing scenes and then destroying when entering another

I'm creating a game designed for vision-impaired gamers. My score text currently shows up during the gameplay. Once you lose it loads a new scene (End screen), but I want the score to stay when the end scene is loaded and then have it reset to 0 when the game screen is loaded again, then removed if the player decides to go back to the main menu.
This is what loads the next scene.
public int amount;
public void ChangeScene(int changeTheScene)
{
SceneManager.LoadScene(changeTheScene);
}
void Start()
{
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
amount += 1;
Debug.Log(amount);
if (amount == 10)
{
SceneManager.LoadScene(2);
}
}
}
}
And this is how i track the score
void Start()
{
count = 0;
SetCountText();
float x = Random.Range(325f, -600f);
float y = Random.Range(250f, -450f);
Debug.Log(x + "," + y);
prefab.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, y);
loseObject.GetComponent<Lose>().amount = 0;
}
public void move()
{
float x = Random.Range(325f, -600f);
float y = Random.Range(250f, -450f);
Debug.Log(prefab.transform.position.x + "," + prefab.transform.position.y);
prefab.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, y);
loseObject.GetComponent<Lose>().amount = 0;
count = count + 1;
SetCountText();
}
void SetCountText()
{
countText.text = "Count: " + count.ToString();
}
}
I'm not entirely sure if this would work, but to answer your question, I'm guessing this would do what you'd like.
using UnityEngine;
//This would be attached to said gameobject that displays the amount.
public class Amount : MonoBehaviour
{
public static Amount instance = null;
void Awake()
{
if (instance == null)
instance = this;
if (instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
}
//Here would be what's needed to set the amount's parent.
public class SetThatAmountsParentToThisGameObject : MonoBehaviour
{
void SetAmountParent()
{
Amount.instance.transform.SetParent(transform);
}
}
//And here you would destroy said gameobject that displays the amount.
public class DestroyThatAmount : MonoBehaviour
{
void DestroyIt()
{
Destroy(Amount.instance.gameObject);
}
}
Be advised that I haven't tested this and therefor am not sure if this will actually work. I'm writing it as an answer because you asked for an example and code is much cleaner and easier to read in answers.

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