Create a list prefab buttons in the script in Unity - unity3d

I'm currently working on a school project and have encountered the following issue.
I'm trying to create a gameboard made out of a prefab (the prefab is a button). For that I have the following code:
public GameObject tilePrefab;
public Transform newParent;
public int xAxis = 0;
public int yAxis = 0;
private int boardWidth = 9;
public int boardHeight = 5;
private void GenerateBoard()
{
int buttonNumber = 0;
for (int i = 0; i < boardWidth; i++)
{
for (int j = 0; j < boardHeight; j++)
{
GameObject button = Instantiate(tilePrefab, new Vector3(xAxis, yAxis, 0), Quaternion.identity);
button.transform.SetParent(newParent);
boardButtons[buttonNumber] = button; //.GetComponentInChildren<Button>()
yAxis += 300;
}
yAxis = 0;
xAxis += 300;
}
}
tilePrefab is the Button prefab, newParent is the canvas. This works for the most part, but when I try to add the created buttons to a list or an button[] array, there is a conflict. Because even though my tilePrefab is a ui button obect, generating it like this means that "button" is a GameObject.
How can i fix that so that i can create a list or an button[] array and successfully store the newly created button in that list/array? I tried "public BoardPiece tilePrefab" or "public Button tilePrefab", but then the Instantiate doesn't work. (BoardPiece is the name of the Prefab)
Thanks for any help that you can give.

Because even though my tilePrefab is a ui button obect
The tilePrefab is a GameObject that has a Button Component attached, they are two different things that seem to be causing confusion. I'd suggest renaming GameObject Button to GOButton to make it clear that it is a GameOject and not a Button.
If you want to store a list of Button, you have to make sure you are adding a Button, in order to do this, I'd suggest you create a list of Buttons:
List<Button> buttonList = new List<Button>();
Then every time you instantiate the GOButton, you then add the Button Component to your list:
buttonList.Add(GOButton.GetComponent<Button>());
This way you then have a list of Buttons that you can loop and handle how you please:
foreach (Button button in buttonList)
{
//change button state here
}

Related

Unity - How do I make an array of UI game objects beneath the cursor?

I want to be able to make an array, which has all the UI elements, under the cursor , inside the array.
This is because I need to be able to tell if there is a UI element beneath multiple other UI elements, I've tried other techniques, but it only shows the UI element on top.
Edit: To be more clear, I am trying to make a list of all the pieces of UI that are layered beneath the cursor, and not only being able to tell which UI is on the top.
Sounds like you are looking for UI.GraphicRaycaster.Raycast.
The GraphicRaycaster is usually attached to each Canvas (otherwise you wouldn't be able to interact with anything in them).
So without referencing a specific canvas you could just go and get them all:
// Because this is dynamically changing I recommend to stick to a List instead of an array
private List<RaycastResult> _hits = new List<RaycastResult>();
private EventSystem _eventSystem;
private void Awake()
{
//Fetch the Event System from the Scene
_eventSystem = FindObjectOfType<EventSystem>();
}
private void Update()
{
//Check if the left Mouse button is clicked
if (Input.GetKeyDown(KeyCode.Mouse0))
{
//Set up the new Pointer Event
var eventData = new PointerEventData(_eventSystem) {position = Input.mousePosition};
//Create a list of Raycast Results
_hits.Clear();
// Get all existing graphicsRaycaster
// NOTE: Of course you should rather cache this result e.g. in Awake
// and only update it if a Canvas is added to or removed from the scene
var graphicsRaycasters = FindObjectsOfType<GraphicRaycaster>();
foreach(var graphicsRaycaster in graphicsRaycasters)
{
graphicsRaycaster.Raycast(eventData, _hits);
}
// Do something with the hits
foreach (var result in _hits)
{
Debug.Log(result.gameObject.name, result.gameObject);
}
}
}
Render mode screen space overlay will always render ui in front. One way to achieve gameobject in front of ui is to change the camera render mode to screen space camera so that you can use sprite with with higher negative z value position in front of ui.
or you can use GraphicRaycaster
To make a raycast tooltip list by name:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
using System.Linq;
public class UI : MonoBehaviour
{
private GraphicRaycaster rc;
private PointerEventData pt;
EventSystem eventSystem;
public TMP_Text text;
public List<RaycastResult> results;
private void Start()
{
//initialize
rc = GetComponent<GraphicRaycaster>();
eventSystem = GetComponent<EventSystem>();
pt = new PointerEventData(eventSystem);
results = new List<RaycastResult>();
}
private void Update()
{
//get screen mouse position
pt.position = Input.mousePosition;
results.Clear();
rc.Raycast(pt, results);
if (results.Count > 0)
{
//enable tooltip
text.gameObject.SetActive(true);
text.transform.position = pt.position;
//reset text if results.count change
text.text = "";
foreach (RaycastResult res in results)
{
text.text += res.gameObject.name + "\n";
}
}
else
{
text.gameObject.SetActive(false);
text.text = "";
}
}
}
make text mesh pro anchored on top left
and also raycast target turned off for textmesh pro
result

How to update Unity prefab dynamically?

1) How can I find the height of a prefab? In my code below I'd like to make the 232 value dynamic, so I can safely update my prefab in the future.
2) How can I get a reference to a Text object in my rendered prefab?
Player[] players = JsonHelper.getJsonArray<Player>(www.downloadHandler.text);
float nextY = 0;
for (int i = 0; i < players.Length; i++)
{
Player player = players[i];
Vector3 pos = new Vector3(0, -nextY, spawnPoint.position.z);
GameObject spawnedItem = Instantiate(listViewItem, pos, spawnPoint.rotation);
nextY += 232;
// spawnedItem.? <- need to put a Player name into a Text in here
spawnedItem.transform.SetParent(spawnPoint, false);
}
You can create a c# script that will be your interface between your prefab and other components, something as such :
public class MyCustomPrefab : MonoBehaviour
{
[SerializedField] private float height;
[SerializedField] private TextMeshProUGUI text;
// other variable, methods, anything you want
}
Then instead of instantiating it as a GameObject, use your Type :
MyCustomPrefab prefab = Instantiate(.....);
// Then you can access/use any variable/method that you want
nextY += prefab.height;
prefab.text = .....;

Unity: How to create a selection box around gameObjects (Text or images) similar to those in Unity editor?

In my Unity app, the user has the option to input text and import images. I have implemented these using TMP_Input Field and Image/Sprites respectively. The app also requires that the user knows the currently selected text/image and can also drag these text and images and sometimes resize them.
To achieve this what I need is a UI like this.
This is the actual Unity editor. But I want to have similar box around my text and images in my app. So that the user knows the current selection and may also drag the text/Image or resize it using the blue circles in the corners.
I am not sure how I can achieve this. One way which I see is to have views which wrap my text and images and these views will have these boxes as the backgrounds.
But I thought that there may be an easier way to do this, probably something that comes with Unity since this looks like a general requirement to me. I am not sure though. What would be the best way to achieve this? WIll I have to do everything from scratch or are there any existing UI components or assets which can make this easier? Any suggestions are appreciated. Thanks!
I decided to take a look at this and came up with a solution.
Each Resizable element has 4 line images and 4 corner images.
Each corner has this script attached to it to make it draggable and to send its position change when being dragged to the main ResizableElement script.
public class ResizableElementDraggable : MonoBehaviour, IDragHandler
{
#pragma warning disable 0649
[SerializeField] private ResizableElement _resizableElement;
[SerializeField] private ResizableElementCorners _corner;
#pragma warning restore 0649
private Vector2 _lastKnownPosition;
private void Awake()
{
_lastKnownPosition = transform.position;
}
void IDragHandler.OnDrag(PointerEventData eventData)
{
_resizableElement.UpdateSize(_corner, _lastKnownPosition - eventData.position);
}
public void UpdatePosition()
{
_lastKnownPosition = transform.position;
}
}
The main ResizableElement script is as follows which includes the public enum to easily see which corner is sending the position change.
public enum ResizableElementCorners
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
public class ResizableElement : MonoBehaviour, IPointerClickHandler
{
#pragma warning disable 0649
[SerializeField] private GameObject[] _lines;
[SerializeField] private ResizableElementDraggable[] _corners;
#pragma warning disable 0649
private RectTransform _rectTransform;
public bool IsSelected { get; private set; }
private void Awake()
{
_rectTransform = GetComponent<RectTransform>();
}
public void UnSelect()
{
if (IsSelected)
{
IsSelected = false;
for (int l = 0; l < _lines.Length; l++)
{
_lines[l].SetActive(false);
}
for (int c = 0; c < _lines.Length; c++)
{
_corners[c].gameObject.SetActive(false);
}
}
}
public void Select()
{
if (!IsSelected)
{
IsSelected = true;
for (int l = 0; l < _lines.Length; l++)
{
_lines[l].SetActive(true);
}
for (int c = 0; c < _lines.Length; c++)
{
_corners[c].gameObject.SetActive(true);
}
}
}
public void UpdateSize(ResizableElementCorners corner, Vector2 change)
{
Vector2 offsetMin = _rectTransform.offsetMin;
Vector2 offsetMax = _rectTransform.offsetMax;
switch (corner)
{
case ResizableElementCorners.TopLeft:
_rectTransform.offsetMax = new Vector2(offsetMax.x, offsetMax.y - change.y);
_rectTransform.offsetMin = new Vector2(offsetMin.x - change.x, offsetMin.y);
break;
case ResizableElementCorners.TopRight:
_rectTransform.offsetMax = new Vector2(offsetMax.x - change.x, offsetMax.y - change.y);
break;
case ResizableElementCorners.BottomLeft:
_rectTransform.offsetMin = new Vector2(offsetMin.x - change.x, offsetMin.y - change.y);
break;
case ResizableElementCorners.BottomRight:
_rectTransform.offsetMax = new Vector2(offsetMax.x - change.x, offsetMax.y);
_rectTransform.offsetMin = new Vector2(offsetMin.x, offsetMin.y - change.y);
break;
}
for (int c = 0; c < _lines.Length; c++)
{
_corners[c].UpdatePosition();
}
}
public void OnPointerClick(PointerEventData eventData)
{
Select();
}
}
It uses the OnPointerClick to know when it has been selected- you will need to track these elements and send to unselect others on this trigger but for this demo I didn't add it.
I have created a demo project and uploaded it to Github and it can be found here
Disclaimer - this is just a demo
Update:
I have made a basic asset package that includes 2 prefabs a ResizableUI main script holder that controls the ResizableObjects in the Scene and a Prefab that when added as a child to any UI object with a RectTransform makes it a Resizable Element.
There are a couple of basic settings that can be set on each Resizable element including Resize Directions, min size and locking corners.
The whole project is being hosted in the Git Repo.
Hope someone finds it useful.
Update 2
I rewrote the project and it has now become a fully fledged component, once the required scripts (4) are in the project, all thats needed is to add a ResizableUIComponent to the desired UI Element to make it in app resizable.
To tailor it it has a few settings, corner tabs sprite and color, and the outline width and color.
If you come across this looking for something check out the repo for more info.

Why is canvas still rebuilding and rebatching its data?

I want to avoid the rebuild / rebatch process from occurring when showing/hiding UI elements so I followed the advice from HERE : workaround is to place the UI to be shown/hidden onto its own Canvas ... and ... enable/disable the Canvas component on this object.
Tested by placing 5000 images under a separate empty Canvas and only enabled/disabled the canvas component on that canvas parent gameobject using this script :
public class CanvasScript : MonoBehaviour
{
public Canvas canvas;
private Image[] images = new Image[5000];
public void CreateImages()
{
for (int i = 0; i < 5000; i++)
{
GameObject NewObj = new GameObject(); //GameObject
images[i] = NewObj.AddComponent<Image>(); //Image Script
NewObj.GetComponent<RectTransform>().SetParent(canvas.transform); //set child
}
}
public void EnableDisableSubCanvas()
{
canvas.enabled = !canvas.enabled;
}
}
Issue : Calling the EnableDisableSubCanvas() still generates big spikes (~100ms) on UGUI.Rendering.UpdateBatches calls. Why is this happening or what am I doing wrong ?

Manually edit Unity3D collider coordinates?

Trying to create a 2D game where I need a 2D polygon collider with exact symmetry, so I'd like to set the coordinates by hand/numerically, rather than using a mouse.
How can this be done?
I suppose the game could adjust the coordinates at start-up, but I'd prefer to have them correct "design time", if possible. Also, if I'm to do it programmatically at start-up, I'd appreciate a how-to or suitable link to help on that.
You can set collider vertices in script using PolygonCollider2D.points
or you can enable debug mode in inspector and enter them manually, but this is for unity 4 only:
For Unity 5 you can use this workaround. Place script below to the Editor folder.
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(PolygonCollider2D))]
public class PolygonCollider2DEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var collider = (PolygonCollider2D)target;
var points = collider.points;
for (int i = 0; i < points.Length; i++)
{
points[i] = EditorGUILayout.Vector2Field(i.ToString(), points[i]);
}
collider.points = points;
EditorUtility.SetDirty(target);
}
}
I solve this by creating other script to be added with PolygonCollider2D. This extra script that edit polygon points. So, it's a script to edit other and "Edit Collider" button stay.
print: http://i.stack.imgur.com/UN2s8.jpg
[RequireComponent(typeof(PolygonCollider2D))]
public class PolygonCollider2DManualPoins : MonoBehaviour { }
[UnityEditor.CustomEditor(typeof(PolygonCollider2DManualPoins))]
public class PolygonCollider2DManualPoinsEditor : UnityEditor.Editor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
var collider = ((PolygonCollider2DManualPoins)target).GetComponent<PolygonCollider2D>();
var points = collider.points;
for (int i = 0; i < points.Length; i++){
points[i] = UnityEditor.EditorGUILayout.Vector2Field(i.ToString(), points[i]);
}
collider.points = points;
UnityEditor.EditorUtility.SetDirty(collider);
}
}