How calculate autosize for group buttons in Unity3D - c#

I have button's horizontal layout group with TextMeshPro. How can I calculate auto sizing font for it and set minimum value on all buttons? It's need for relative UI.
I have now:
And how I want:
I tried this code:
public class FontSizeController: MonoBehaviour
{
private void Start()
{
SetMinFontForAnswers(transform,
FindMinFontSizeAnswerOptions(transform));
}
private void SetMinFontForAnswers(Transform answerPanel, float minFontSize)
{
for (var answerIndex = 0; answerIndex < answerPanel.childCount; answerIndex++)
{
var meshProUgui = answerPanel.GetChild(answerIndex).GetChild(1).GetComponent<TextMeshProUGUI>();
meshProUgui.fontSize = minFontSize;
}
}
private float FindMinFontSizeAnswerOptions(Transform answerOptions)
{
var minFontSize = -1f;
for (var answerIndex = 0; answerIndex < answerOptions.childCount; answerIndex++)
{
var component = answerOptions.GetChild(answerIndex).GetChild(1).GetComponent<TextMeshProUGUI>();
component.enableAutoSizing = true;
component.ForceMeshUpdate();
if (IsAnswerOptionActive(answerOptions, answerIndex) && IsMinFontSizeOrNotInitialized(component, minFontSize))
{
minFontSize = component.fontSize;
}
component.enableAutoSizing = false;
}
return minFontSize;
}
private bool IsAnswerOptionActive(Transform answerOptions, int answerIndex)
{
return answerOptions.GetChild(answerIndex).gameObject.activeSelf;
}
private bool IsMinFontSizeOrNotInitialized(TMP_Text textComponent, float minFontSize)
{
return textComponent.fontSize < minFontSize || minFontSize == -1f;
}
}
But it doesn't work on Start and work only in Update method. But when I used it in Update method I can see when text font size changing. It is quickly but I want do this before panel will be render.
Question panel is not active by default
AnswerOptionsPanel:
TextMeshPro Text in AnswerOptionsPanel:

You can get your Text assets current size with using this method;
Text.cachedTextGenerator.fontSizeUsedForBestFit
You can compare your texts instant size and then change.
For example;
UnityEngine.UI.Text[] myTexts;
void OptimiseTextSizes()
{
int minSize = 999;
foreach (UnityEngine.UI.Text t in myTexts)
{
if (t.cachedTextGenerator.fontSizeUsedForBestFit < minSize)
{
minSize = t.cachedTextGenerator.fontSizeUsedForBestFit;
}
}
foreach (UnityEngine.UI.Text t in myTexts)
{
t.resizeTextMaxSize = minSize;
}
}

Related

Unity's Button.onClick listener requires several clicks to be invoked

The cards with the Button component and an onClick listener require numerous erratic clicks to trigger and fail to invoke after just one click.
The first two cards will always invoke via the first click. Afterwards, the other cards often require multiple clicks (between 2 and 7 clicks) before their listener is invoked, and they are flipped over. This occurs at random as sometimes even the first three pairs will flip after just one click, and then the rest won´t.
If I wait for a set time of five seconds, the next card I click will turn over with just one click. So the issue appears to depend on the interval between clicks, as opposed to the number of clicks. Although this behaviour is inconsistent, as clicking just once on a new card and then waiting those 5 seconds doesn´t always work and the card will stay unflipped; assuming because I clicked too soon.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
[SerializeField] private Sprite bgImage;
public Sprite[] puzzles;
public List<Sprite> gamePuzzles = new List<Sprite>();
public List<Button> btns = new List<Button>();
private bool firstGuess, secondGuess;
private int countGuesses;
private int countCorrectGuesses;
private int gameGuesses;
private int firstGuessIndex, secondGuessIndex;
private string firstGuessPuzzle, secondGuessPuzzle;
void Awake()
{
puzzles = Resources.LoadAll<Sprite>("Sprites/Cards");
}
void Start()
{
GetButtons();
AddListeners();
AddGamePuzzles();
Shuffle(gamePuzzles);
gameGuesses = gamePuzzles.Count / 2;
}
void GetButtons()
{
GameObject[] objects = GameObject.FindGameObjectsWithTag("Puzzle Button");
for (int i = 0; i < objects.Length; i++)
{
btns.Add(objects[i].GetComponent<Button>());
btns[i].image.sprite = bgImage;
}
}
void AddGamePuzzles()
{
int looper = btns.Count;
int index = 0;
for (int i = 0; i < looper; i++)
{
if (index == looper / 2)
{
index = 0;
}
gamePuzzles.Add(puzzles[index]);
index++;
}
}
void AddListeners()
{
foreach (Button btn in btns)
{
btn.onClick.AddListener(() => PickAPuzzle());
}
}
public void PickAPuzzle()
{
string name = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.name;
Debug.Log("You are clicking a button named" + name);
if (!firstGuess)
{
firstGuess = true;
firstGuessIndex =
int.Parse(UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.name);
firstGuessPuzzle = gamePuzzles[firstGuessIndex].name;
btns[firstGuessIndex].image.sprite = gamePuzzles[firstGuessIndex];
}
else if (!secondGuess)
{
secondGuess = true;
secondGuessIndex =
int.Parse(UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.name);
secondGuessPuzzle = gamePuzzles[secondGuessIndex].name;
btns[secondGuessIndex].image.sprite = gamePuzzles[secondGuessIndex];
countGuesses++;
StartCoroutine(CheckIfThePuzzlesMatch());
}
}
IEnumerator CheckIfThePuzzlesMatch()
{
yield return new WaitForSeconds(2f);
if (firstGuessPuzzle == secondGuessPuzzle && firstGuessIndex != secondGuessIndex)
{
yield return new WaitForSeconds(1f);
btns[firstGuessIndex].interactable = false;
btns[secondGuessIndex].interactable = false;
btns[firstGuessIndex].image.color = new Color(0, 0, 0, 0);
btns[secondGuessIndex].image.color = new Color(0, 0, 0, 0);
CheckIfTheGameIsFinished();
}
else
{
yield return new WaitForSeconds(1f);
btns[firstGuessIndex].image.sprite = bgImage;
btns[secondGuessIndex].image.sprite = bgImage;
}
yield return new WaitForSeconds(1f);
firstGuess = secondGuess = false;
}
void CheckIfTheGameIsFinished()
{
countCorrectGuesses++;
if (countCorrectGuesses == gameGuesses)
{
Debug.Log("Game Finished");
}
}
void Shuffle(List<Sprite> list)
{
for (int i = 0; i < list.Count; i++)
{
Sprite temp = list[i];
int randomIndex = Random.Range(i, list.Count);
list[i] = list[randomIndex];
list[randomIndex] = temp;
}
}
}

Unity Displaying Best Times

I am creating a small game with five levels and a timer to see how fast the player can complete all five levels. I have a script that is supposed to take the finished time from the timer and convert it to a PlayerPref so that it can always be saved to the leaderboard. Eash leaderboard entry is its own separate text object, with its own script.
public class ScoreTimer01 : MonoBehaviour
{
public Text theText;
public void Awake()
{
theText.text = GetComponent<Text>().text;
if (PlayerPrefs.HasKey("time0"))
{
theText.text = PlayerPrefs.GetFloat("time0").ToString("mm':'ss'.'ff");
}
else
{
theText.text = "0";
}
}
}
But the leaderboard texts never change, is there something that I am missing?
Here is the timer script:
public class Timer : MonoBehaviour
{
Text text;
float theTime;
public float speed = 1;
public static bool playing;
static List<float> bestTimes = new List<float>();
static int totalScores = 5;
void Awake()
{
LoadTimes();
}
// Start is called before the first frame update
void Start()
{
text = GetComponent<Text>();
playing = true;
}
static public void EndTimer()
{
playing = false;
CheckTime(FinalTime.finalTime);
}
// Update is called once per frame
void Update()
{
if(playing == true)
{
TimerController.theTime += Time.deltaTime * speed;
int minutes = (int)(TimerController.theTime / 60f) % 60;
int seconds = (int)(TimerController.theTime % 60f);
int milliseconds = (int)(TimerController.theTime * 1000f) % 1000;
text.text = "Time: " + minutes.ToString("D2") + ":" + seconds.ToString("D2") + ":" + milliseconds.ToString("D2");
}
}
static public void LoadTimes()
{
for (int i = 0; i < totalScores; i++)
{
string key = "time" + i;
if (PlayerPrefs.HasKey(key))
{
bestTimes.Add(PlayerPrefs.GetFloat(key));
}
}
}
static public void CheckTime(float time)
{
// if there are not enough scores in the list, go ahead and add it
if (bestTimes.Count < totalScores)
{
bestTimes.Add(time);
// make sure the times are in order from highest to lowest
bestTimes.Sort((a, b) => b.CompareTo(a));
SaveTimes();
}
else
{
for (int i = 0; i < bestTimes.Count; i++)
{
// if the time is smaller, insert it
if (time < bestTimes[i])
{
bestTimes.Insert(i, time);
// remove the last item in the list
bestTimes.RemoveAt(bestTimes.Count - 1);
SaveTimes();
break;
}
}
}
}
static public void SaveTimes()
{
for (int i = 0; i < bestTimes.Count; i++)
{
string key = "time" + i;
PlayerPrefs.SetFloat(key, bestTimes[i]);
}
}
void OnDestroy()
{
PlayerPrefs.Save();
}
}
I can't figure out if the PlayerPrefs are not saving correctly or the script to change the text is incorrect. I am very new to C# and unity as a whole, so any help is appreciated.

Is there a way to use EditorGUILayout.LabelField in EditorWindow inside GUILayout.Button?

using UnityEngine;
using System.Collections.Generic;
using Object = UnityEngine.Object;
using System.Reflection;
using UnityEditor;
using System.Linq;
public class SearchableWindow : EditorWindow
{
string searchString = "";
static List<GameObject> gameobjecttest = new List<GameObject>();
Vector2 scrollPos;
[MenuItem("Tools/Searching")]
private static void CreateReplaceWithPrefab()
{
const int width = 340;
const int height = 420;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
GetWindow<SearchableWindow>().position = new Rect(x, y, width, height);
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.FlexibleSpace();
searchString = EditorGUILayout.TextField(searchString, EditorStyles.toolbarTextField);
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("Search"))
{
var items = Selection.gameObjects;
// Do comparison here. For example
var selected = GetChildrenRecursive(items);
gameobjecttest.AddRange(selected);
}
EditorGUILayout.BeginHorizontal();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(300), GUILayout.Height(400));
foreach (GameObject go in gameobjecttest)
{
EditorGUILayout.LabelField(go.name);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndHorizontal();
}
private void OnSelectionChange()
{
Repaint();
}
private static IEnumerable<GameObject> GetChildrenRecursive(GameObject root)
{
var output = new List<GameObject>();
//add the root object itself
output.Add(root);
// iterate over direct children
foreach (Transform child in root.transform)
{
// add the children themselves
output.Add(child.gameObject);
var childsOfchild = GetChildrenRecursive(child.gameObject);
output.AddRange(childsOfchild);
}
return output;
}
private static IEnumerable<GameObject> GetChildrenRecursive(IEnumerable<GameObject> rootObjects)
{
var output = new List<GameObject>();
foreach (var root in rootObjects)
{
output.AddRange(GetChildrenRecursive(root));
}
// remove any duplicates that would e.g. appear if you select a parent and its child
return output.Distinct();
}
}
I want to make that when clicking the button it will make the search and will add once the items to the EditorGUILayout.LabelField but the EditorGUILayout.LabelField need ot be inside the OnGUI if it's inside the if (GUILayout.Button("Search")) it will not add the items to the EditorGUILayout.LabelField
Now it keep adding nonstop to the EditorGUILayout.LabelField.
Your question is a bit unclear.
If your question is only whether it is possible or not:
No! (at least not how you expect it)
anything within this block:
if (GUILayout.Button("Search"))
{
...
}
is only executed exactly the moment, the button is actually pressed.
So you have to do it the way you did it already. Maybe adding an additional check for only showing those fields if the list is not empty:
// only show the button while the list is empty
if(gameobjecttest.Count == 0)
{
if (GUILayout.Button("Search"))
{
...
}
// skip the rest
return;
}
// otherwise show the list
EditorGUILayout.BeginHorizontal();
{
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(300), GUILayout.Height(400));
{
foreach (GameObject go in gameobjecttest)
{
EditorGUILayout.LabelField(go.name);
}
}
EditorGUILayout.EndScrollView();
}
EditorGUILayout.EndHorizontal();
(I usually add those { } for cleaning up the code a bit)
Or keep the button but disable it instead
EditorGUI.BeginDisabledGroup(gameobjecttest.Count != 0);
{
if (GUILayout.Button("Search"))
{
...
}
}
EditorGUI.EndDisabledGroup();

Unity Crashing on Play, most likely infinite While loop, but cannot locate issue

thanks for reading.
I'm currently building a small memory card game in Unity using C#. I have the main portion of code finished but when I press the play button on a certain scene Unity freezes.
I believe it is due to an infinite While loop, but I can not find the issue. I would really appreciate any help anyone can offer. I will leave my code below. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UnityEngine;
public class Pairs : MonoBehaviour {
public Sprite[] face; //array of card faces
public Sprite back;
public GameObject[] deck; //array of deck
public Text pairsCount;
private bool deckSetUp = false;
private int pairsLeft = 13;
// Update is called once per frame
void Update () {
if (!deckSetUp)
{
SetUpDeck();
}
if (Input.GetMouseButtonUp(0)) //detects left click
{
CheckDeck();
}
}//Update
void SetUpDeck()
{
for (int ix = 0; ix < 2; ix++)
{
for(int i = 1; i < 14; i++)//sets up card value (2-10 JQKA)
{
bool test = false;
int val = 0;
while (!test)
{
val = Random.Range(0, deck.Length);
test = !(deck[val].GetComponent<Card>().SetUp);
}//while
//sets up cards
deck[val].GetComponent<Card>().Number = i;
deck[val].GetComponent<Card>().SetUp = true;
}//nested for
}//for
foreach (GameObject crd in deck)
{
crd.GetComponent<Card>().setUpArt();
}
if (!deckSetUp)
{
deckSetUp = true;
}
}//SetUpDeck
public Sprite getBack()
{
return back;
}//getBack
public Sprite getFace(int i)
{
return face[i - 1];
}//getFace
void CheckDeck()
{
List < int > crd = new List<int>();
for(int i = 0; i < deck.Length; i++)
{
if(deck[i].GetComponent<Card>().State == 1)
{
crd.Add(i);
}
}
if(crd.Count == 2)
{
CompareCards(crd);
}
}//CheckDeck
void CompareCards(List<int> crd)
{
Card.NO_TURN = true; //stops cards turning
int x = 0;
if(deck[crd[0]].GetComponent<Card>().Number ==
deck[crd[1]].GetComponent<Card>().Number)
{
x = 2;
pairsLeft--;
pairsCount.text = "PAIRS REMAINING: " + pairsLeft;
if(pairsLeft == 0) // goes to home screen when game has been won
{
SceneManager.LoadScene("Home");
}
}
for(int j = 0; j < crd.Count; j++)
{
deck[crd[j]].GetComponent<Card>().State = x;
deck[crd[j]].GetComponent<Card>().PairCheck();
}
}//CompareCards
}
I believe the issue lies in the while(!test) but i do not know why test never become true.
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
public class Card : MonoBehaviour {
public static bool NO_TURN = false;
[SerializeField]
private int cardState; //state of card
[SerializeField]
private int cardNumber; //Card value (1-13)
[SerializeField]
private bool _setUp = false;
private Sprite back; //card back (Green square)
private Sprite face; //card face (1-10 JQKA)
private GameObject pairsManager;
void Begin()
{
cardState = 1; //cards face down
pairsManager = GameObject.FindGameObjectWithTag("PairsManager");
}
public void setUpArt()
{
back = pairsManager.GetComponent<Pairs>().getBack();
face = pairsManager.GetComponent<Pairs>().getFace(cardNumber);
turnCard();//turns the card
}
public void turnCard() //handles turning of card
{
if (cardState == 0)
{
cardState = 1;
}
else if(cardState == 1)
{
cardState = 0;
}
if (cardState == 0 && !NO_TURN)
{
GetComponent<Image>().sprite = back; // shows card back
}
else if (cardState == 1 && !NO_TURN)
{
GetComponent<Image>().sprite = face; // shows card front
}
}
//setters and getters
public int Number
{
get {return cardNumber;}
set { cardNumber = value;}
}
public int State
{
get { return cardState; }
set { cardState = value; }
}
public bool SetUp
{
get { return _setUp; }
set { _setUp = value; }
}
public void PairCheck()
{
StartCoroutine(pause ());
}
IEnumerator pause()
{
yield return new WaitForSeconds(1);
if (cardState == 0)
{
GetComponent<Image>().sprite = back;
}
else if (cardState == 1)
{
GetComponent<Image>().sprite = face;
}
}
}
Thank you for reading, I will post a link to the github repository if that helps.
github repository
Your deck array has at least one card in it that has _setUp set to true which would make it go in a infinite loop.
The reason it goes in a infinite loop is because it will have set all available _setUp to true and it would keep looking for _setUp that are set to false and it will never find any.
The reason you need at least 26 object that have _setUp to false is because in the nested for loop you loop 13 times and then you do that twice which gives a total of 26 loops. So you need at least 26 objects.
What you can do to make sure that they're all false is to set them all to false before entering the for loop
for(int i = 0; i < deck.Length; i++)
{
deck[i].GetComponent<Card>().SetUp = false;
}

Cardboard Magnet Detection

I got some trouble with my unity cardboard app. May some of you guys can help me.
I have build a little Island with Animations and A second island as a main menu.
So when the apps starts, you see the Island from above and the Logo of the App.
When the user pull down the magnet button on side the app will starts another level.
I used this scripts:
http://www.andrewnoske.com/wiki/Unity_-_Detecting_Google_Cardboard_Click
Detecting Google Cardboard Magnetic Button Click - Singleton Implementation
CardboardMagnetSensor.cs and CardboardTriggerControlMono.cs
I created a script in my asset folder(CardboardMagnetSensor.cs) like in the description from Link. Than I created a second script(CardboardTriggerControlMono.cs) like in the discription an dragged it onto my CardboardMain in may Projekt.
The CardboardTriggerControlMono.cs looks like:
using UnityEngine;
using System.Collections;
public class CardboardTriggerControlMono : MonoBehaviour {
public bool magnetDetectionEnabled = true;
void Start() {
CardboardMagnetSensor.SetEnabled(magnetDetectionEnabled);
// Disable screen dimming:
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void Update () {
if (!magnetDetectionEnabled) return;
if (CardboardMagnetSensor.CheckIfWasClicked()) {
Debug.Log("Cardboard trigger was just clicked");
Application.LoadLevel(1);
CardboardMagnetSensor.ResetClick();
}
}
}
The CarboardMagnetSensor:
using UnityEngine;
using System.Collections.Generic;
public class CardboardMagnetSensor {
// Constants:
private const int WINDOW_SIZE = 40;
private const int NUM_SEGMENTS = 2;
private const int SEGMENT_SIZE = WINDOW_SIZE / NUM_SEGMENTS;
private const int T1 = 30, T2 = 130;
// Variables:
private static bool wasClicked; // Flips to true once set off.
private static bool sensorEnabled; // Is sensor active.
private static List<Vector3> sensorData; // Keeps magnetic sensor data.
private static float[] offsets; // Offsets used to detect click.
// Call this once at beginning to enable detection.
public static void SetEnabled(bool enabled) {
Reset();
sensorEnabled = enabled;
Input.compass.enabled = sensorEnabled;
}
// Reset variables.
public static void Reset() {
sensorData = new List<Vector3>(WINDOW_SIZE);
offsets = new float[SEGMENT_SIZE];
wasClicked = false;
sensorEnabled = false;
}
// Poll this once every frame to detect when the magnet button was clicked
// and if it was clicked make sure to call "ResetClick()"
// after you've dealt with the action, or it will continue to return true.
public static bool CheckIfWasClicked() {
UpdateData();
return wasClicked;
}
// Call this after you've dealt with a click operation.
public static void ResetClick() {
wasClicked = false;
}
// Updates 'sensorData' and determines if magnet was clicked.
private static void UpdateData() {
Vector3 currentVector = Input.compass.rawVector;
if (currentVector.x == 0 && currentVector.y == 0 && currentVector.z == 0) {
return;
}
if(sensorData.Count >= WINDOW_SIZE) sensorData.RemoveAt(0);
sensorData.Add(currentVector);
// Evaluate model:
if(sensorData.Count < WINDOW_SIZE) return;
float[] means = new float[2];
float[] maximums = new float[2];
float[] minimums = new float[2];
Vector3 baseline = sensorData[sensorData.Count - 1];
for(int i = 0; i < NUM_SEGMENTS; i++) {
int segmentStart = 20 * i;
offsets = ComputeOffsets(segmentStart, baseline);
means[i] = ComputeMean(offsets);
maximums[i] = ComputeMaximum(offsets);
minimums[i] = ComputeMinimum(offsets);
}
float min1 = minimums[0];
float max2 = maximums[1];
// Determine if button was clicked.
if(min1 < T1 && max2 > T2) {
sensorData.Clear();
wasClicked = true; // Set button clicked to true.
// NOTE: 'wasClicked' will now remain true until "ResetClick()" is called.
}
}
private static float[] ComputeOffsets(int start, Vector3 baseline) {
for(int i = 0; i < SEGMENT_SIZE; i++) {
Vector3 point = sensorData[start + i];
Vector3 o = new Vector3(point.x - baseline.x, point.y - baseline.y, point.z - baseline.z);
offsets[i] = o.magnitude;
}
return offsets;
}
private static float ComputeMean(float[] offsets) {
float sum = 0;
foreach(float o in offsets) {
sum += o;
}
return sum / offsets.Length;
}
private static float ComputeMaximum(float[] offsets) {
float max = float.MinValue;
foreach(float o in offsets) {
max = Mathf.Max(o, max);
}
return max;
}
private static float ComputeMinimum(float[] offsets) {
float min = float.MaxValue;
foreach(float o in offsets) {
min = Mathf.Min(o, min);
}
return min;
}
}
And my steps:
http://www.directupload.net/file/d/3887/mtjygjan_jpg.htm
(sorry I´m not able to upload pictures here)
How ever, it wont work. When I start the app and pull down the magnet, nothing happens. May I did something wrong with switching the level over level index?
I use a nexus 4 and 5 for testing the app
Thanks allot and greetz to you!
Phillip
If you are using the Google Cardboard SDK for Unity, it currently has a bug that prevents Unity from seeing the magnet (and gyro, and accelerometer). That is probably why your script is not working. Until the bug is fixed, there is no good workaround, but you can instead use the property Cardboard.CardboardTriggered to detect if the magnet was pulled.
Update for Unity 5: The sensor bug is gone. Cardboard SDK does not block the sensors.

Categories

Resources