I have a script which handles many sprite arrays. I didn't want to drag them one by one so I wrote a CustomEditor for it that can allow me to assign multiple sprites at once using drag and drop operation:
[CustomEditor(typeof(MyMonoBehaviour))]
public class MyMonoBehaviourEditor : Editor
{
Sprite[] sprites;//actually Sprite[,][] but simplified here
Object[] DropAreaGUI()
{
Event evt = Event.current;
Rect drop_area = GUILayoutUtility.GetRect(0.0f, 20.0f, GUILayout.ExpandWidth(true));
GUI.Box(drop_area, "Drop here!");
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!drop_area.Contains(evt.mousePosition))
return null;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
return DragAndDrop.objectReferences;
}
break;
}
return null;
}
void OnInspectorGUI(){
var drops = DropAreaGUI();
if (drops != null)
{
//the following line gives me error
sprites = drops.Select(x => (x as Sprite)).ToArray();
}
}
}
I lock the inspector, select 12 sprites from Project, drag them onto the box and when I drop them it gives me this error: ArgumentException: GUILayout: Mismatched LayoutGroup.DragPerform
I noticed that DragAndDrop.objectReferences returns an Object[] which is in my case a Texture2D[] and I can't cast it to Sprite[]. I tried Sprite.Create but it asks for rect and pivot which I don't have.
How can I make DragAndDrop recognize that I am dropping Sprites and not Texture2Ds?
If you're trying to assign multiple sprites to an array in the inspector, you can actually just drag and drop them onto the array itself.
What you'd typically do is select the asset you want to drag them into, then click the padlock in the upper-right of the inspector to lock it. Then you can select multiple objects and drag them onto the array itself (the name of the array with the drop-down menu) rather than into any particular slot of the array. Unity will then automatically populate the array with what you dropped there.
This works with arrays and lists (and possibly other container types, if unity displays them in the inspector).
I found the workaround.
instead of return DragAndDrop.objectReferences; I wrote return DragAndDrop.paths;
and then loading is possible:
sprites = drops.Select(x => AssetDatabase.LoadAssetAtPath<Sprite>(x)).ToArray();
Related
I'm trying to create Texts via script. I want to create texts with the name of objects of a certain tag.
"For example, if I have two objects named Cube and Sphere and both have a tag of "TargetObj" then their names should be displayed as texts on the screen(in the case Cube, Sphere)"
I want to achieve this regardless of the number of objects. so a loop is needed.
Here is what I've tried so far.
[SerializeField] GameObject LevelCanvas;
targetObjects = GameObject.FindGameObjectsWithTag("TargetObj");
foreach (var obj in targetObjects)
{
Text mytext = LevelCanvas.AddComponent<Text>();
mytext.text = "Find " + obj.name;
Font ArialFont = (Font)Resources.GetBuiltinResource(typeof(Font), "Arial.ttf");
mytext.font = ArialFont;
mytext.material = ArialFont.material;
}
it only shows one object name although I have 2 more objects that were supposed to be shown as well.
You need to create a text object on a new gameobject.
static Text CreateText(Transform parent)
{
var go = new GameObject();
go.transform.parent = parent;
var text = go.AddComponent<Text>();
return text;
}
var mytext = CreateText(LevelCanvas.transform);
myext.text = ...
Many components in unity can only be added once per game object. That means you need a separate game object for every text you want to show.
A typical way to approach this is to create a "prefab" of a game object which has a text element already. This way you can set a default font and other settings.
Then in code you use the Instantiate method on that prefab and via GetComponent() you can access the text component instance to set the desired string to display.
I am making a game in unity which is mainly made up of UI. The UI is made up of a lot of parent gameobjects with buttons below them in the hierarchy. I require getting the name of the parent (an empty gameobject) when clicking on the button. So far I have tried this:
CountryText.text = transform.parent.name;
But it has not worked. Is there something I can do to make this work?
Assuming you are not talking about the parent, since transform.parent would have been the answer, but one of the ancestors in the hierarchy, a good approach would be to tag the specific object using the gameObject.tag and traverse the hierarchy.
You can assign tag in the inspector, and execute a search by calling:
public Transform FindParentWithTag(Transform child, string tag)
{
Transform next = child;
while (next.parent != null)
{
if (next.parent.tag == tag)
{
return next.parent;//found ancestor with tag
}
next = next.parent.transform;
}
//search failed
return null;
}
How do I assign different prefabs to different images?
right now, I have all my prefabs loading in on top of each other but how do I get it so each prefab loads in only once on top of one image so each image has a different prefab?
I've modified the sample code (the frame corners) to load in my own prefab and used a dictionary to pair the prefabs with images from the database but when the program runs it instatiates all the prefabs in the same place rather than putting one prefrab on each image it puts all the prefabs on every image - this is the code I've been using:
public GameObject obj1, obj2, obj3, obj4;
Dictionary<int, GameObject> imagePrefabPairs;
public AugmentedImage Image;
void Start()
{
instantiateDictionary();
}
// TODO: make initialisation dynamic, to match the size of the db.
public void instantiateDictionary()
{
imagePrefabPairs = new Dictionary<int, GameObject>
{
{ 0, obj1 },
{ 1, obj2 },
{ 2, obj3 },
{ 3, obj4 }
};
}
public void Update()
{
if (Image == null || Image.TrackingState != TrackingState.Tracking)
{
obj1.SetActive(false);
obj2.SetActive(false);
obj3.SetActive(false);
obj4.SetActive(false);
return;
}
Pose _centerPose = Image.CenterPose;
imagePrefabPairs[Image.DatabaseIndex].transform.localPosition = _centerPose.position;
imagePrefabPairs[Image.DatabaseIndex].transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
imagePrefabPairs[Image.DatabaseIndex].SetActive(true);
}
I figure that I need to have some kind of if statement to ask if one prefab is loaded in and then just choose to load in the next one and have them instantiate one at a time but I am not sure how to do that, or if there is a more direct way to make that happen...?
You could change your AugmentedImageVisualizer prefab, so that at the start all your different objects are inactive (.SetActive(false);) For changing a prefab you can add it to your hierarchy, set all the objects inactive and then apply the changes. After apply you can delete the prefab from your game hierarchy.
Img 1: Add existing prefab to game hierarchy
Img 2: set all the attached objecty to inactive (1) and apply the changes to the prefab (2)
So when you detect a image from your imagedatabase the AugmentedImageVisualizer prefab is attached and only the object with the given index is set to active. Then your code should work.
Because at the start all your objects are inactive you could change this part of your code:
if (Image == null || Image.TrackingState != TrackingState.Tracking)
{
obj1.SetActive(false);
obj2.SetActive(false);
obj3.SetActive(false);
obj4.SetActive(false);
return;
}
so that you only deactivate the active object:
if (Image.TrackingState != TrackingState.Tracking)
{
imagePrefabPairs[Image.DatabaseIndex].SetActive(false);
return;
}
I assign different prefabs to different images by this way:
I modified the AugmentedImageExampleController.cs:.
I added a list for prefabs:
public List<AugmentedImageVisualizer> prefabs = new List<AugmentedImageVisualizer>();
For the related image for the prefab I did a reference by using the image.DatabaseIndex in the visualizer:
visualizer = (AugmentedImageVisualizer)Instantiate(prefabs[image.DatabaseIndex], anchor.transform);
In the inspector of ExampleController you can put in the prefabs (AugmentedImageVisualizer) now.
That's it, and its working fine!
I just want to have two buttons #1, #2 on canvas and after plane detection when I select button #1 and tap on screen one 3D asset should be placed and when I click on button #2 and tap on screen second 3D asset should be placed and the previous asset needs to be removed.
At a time one asset needs to be placed and when selecting another asset the previously placed asset needs to be removed.
Did you look at the HelloAR Example? There you have: public GameObject AndyAndroidPrefab; This will be placed on touch. So you can modify that code from the example project to fit your needs.
You can add two public GameObjects where you place your 3D assets and remove the AndyAndroidPrefab asset.
public GameObject obj1_prefab;
public GameObject obj2_prefab;
Then you cann add a bool variable that shows you which button is clicked.
private bool firstBtnClicked = true;
Then you can make two public void functions wich handles the button clicks:
public void firstButtonClick()
{
firstBtnClicked = true;
}
public void secondButtonClick()
{
firstBtnClicked = false;
}
Then you can add the function to your canvas buttons:
Then you have to make changes where you instantiate the object:
//Test if already an object exists and delete it:
if (GameObject.Find("Anchor/myobject") != null)
{
GameObject parentanchor = GameObject.Find("Anchor/myobject").transform.parent.gameObject;
Destroy(parentanchor);
}
GameObject obj = null;
if (firstBtnClicked == true)
{
// Instantiate first Asset model at the hit pose.
obj = Instantiate(obj1_prefab, hit.Pose.position, hit.Pose.rotation);
}
else
{
// Second button clicked: Instantiate second Asset model at the hit pose.
obj = Instantiate(obj2_prefab, hit.Pose.position, hit.Pose.rotation);
}
// Compensate for the hitPose rotation facing away from the raycast (i.e. camera).
obj.transform.Rotate(0, k_ModelRotation, 0, Space.Self);
obj.name = "myobject";
// Create an anchor to allow ARCore to track the hitpoint as understanding of the physical
// world evolves.
var anchor = hit.Trackable.CreateAnchor(hit.Pose);
// Make the object model a child of the anchor.
obj.transform.parent = anchor.transform;
So now if you click button one you select your first object and after a touch on the screen the first asset is placed. When you click on the second button you'll place the second object on screen touch.
I'm trying to create a scroll grid view in which every cell object is tapable.
When a cell object is tapped I want to scale and traslate it to the center of the screen and render it above other cells.
I was able to make it tapable and scale it in its position. Now I want to move the cell object to the center of the screen and render it above other cells.
I've tried many solutions but none of them works.
This is my hierarchy:
This is the grid in normal state:
This is the grid when a cell was tapped:
I'm populating the grid from a C# script dynamically.
void Populate()
{
GameObject cardContainerInstance, cardInstance;
foreach (var c in cardsCollection.GetAll())
{
if (c.IsOwned)
{
cardContainerInstance = Instantiate(cardContainer, transform);
cardInstance = cardContainerInstance.transform.Find("Card").gameObject;
var cardManager = cardInstance.GetComponent<CardManager>();
cardManager.card = c;
cardManager.AddListener(this);
}
else
{
Instantiate(cardSlot, transform);
}
}
}
public void OnCardClick(GameObject cardObject, Card card)
{
Debug.Log("OnCardClick " + card.name);
if (openedCard != null) {
if (openedCard.Number == card.Number)
{
CloseCard(openedCardObject);
}
else
{
CloseCard(openedCardObject);
OpenCard(cardObject, card);
}
}
else
{
OpenCard(cardObject, card);
}
}
void OpenCard(GameObject cardObject, Card card)
{
//cardObject.GetComponent<Canvas>().sortingOrder = 1;
var animator = cardObject.GetComponent<Animator>();
animator.SetTrigger("Open");
openedCard = card;
openedCardObject = cardObject;
}
void CloseCard(GameObject cardObject)
{
var animator = cardObject.GetComponent<Animator>();
animator.SetTrigger("Close");
openedCard = null;
openedCardObject = null;
}
I can't figure out how to move the cell to the center and render it above others.
Note that all is animated using an animator attached to the object itself.
Could anyone help me please? Thank you very much!
EDIT: more details
All cell object have the following hierarchy:
where:
CardContainer is an empty object added to use animator on Card child object
Card is the object itself that has a script, a canvas renderer and an animator
StatsImage is the object that slide out when the card is tapped
Image is a calssic UIImage with Image script, Shadow script and canvas renderer
Other component are simple texts.
EDIT: fix in progress
Trying to apply this suggestions I was able to manage the rendering order (as you see on the image below) but it seems that prevent touch events to be detected on the game object.
I've added a GraphicsRaycaster too and now the bottom horizontal scroll view scrolls again but only if I click and drag a card.
Moreover, with the GraphicsRaycaster, the main grid card still are not clickable and it's possible to open the card only if it is behind the bottom panel (if I click on the red spot in the image below the card behind the panel receives che click)
This is the CardContainer at runtime(note that I'm attaching new Canvas and GraphicsRaycaster on the CardContainer, which is the "root" element):
You didn't clarify whether you are using a sprite renderer or some other method but here is an answer for each.
Sprite renderer:
this the simple one. In each sprite renderer, there is a variable called "sortingOrder" in script and "Order in layer" in the inspector. sprite renderer with sorting Orders that are higher is rendered first. All you would need to do is call:
cardObject.GetComponent<SpriteRenderer>().sortingOrder = 1;
when you click the card, and
cardObject.GetComponent<SpriteRenderer>().sortingOrder = 0;
when you unclick it. I hope that makes sense!
Other Method:
this one is a bit harder and I would suggest that you switch to sprite renderers and it will be much easier and more stable down the road, but I can understand if you have already written a lot of scripts and don't want to go back and change them.
Anyway, all you will need to do Is create two layers: cardLower and cardUpper. then create a new camera and call it topCamera. now under the top camera object in the inspector, change the culling mask (it's near the top) and make sure cardUpper is selected. then change the Clear flags (first one) to "Don't Clear" finally change the depth to 0 (if that doesn't work change it to -2). Now objects in the cardUpper will always be rendered above everything else. You can change the layer through script with
cardObject.layer = "cardUpper"
or
cardObject.layer = "cardLower"
I hope that helps!
Ok, so its pretty simple. So you are going to want to add another canvas component to the game object, and check the override sorting to true. Then use
cardObject.GetComponent<Canvas>().sortingOrder = 1;
to place it in the front and
cardObject.GetComponent<Canvas>().sortingOrder = 0;
to put it in the back.
you are also going to need to put a GraphicsRaycaster on to each of the cardObjects
Ignore my other answer about sprite renderers, they are not needed here