I have an inventory of objects in a game, with image and description.
To represent it, I want a scrollable list with a grid (made with a UI canvas with a scroll view inside), and every element of the grid is a prefab, composed by a UI Panel with an Image and a Text.
I've tried assigning the values of the Image and the Text programmatically in the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PopulateGrid : MonoBehaviour
{
public GameObject prefab;
public Sprite mySprite;
private void Start()
{
Populate();
}
public void Populate()
{
GameObject newObj;
for (int i = 0; i < 20; i++)
{
newObj = (GameObject)Instantiate(prefab, transform); //prefab has image + text
newObj.GetComponentInChildren<Text>().text = "test" + i;
newObj.GetComponentInChildren<Image>().sprite = mySprite; //I've also tried .overrideSprite
}
}
}
The funny thing is, the Text gets correctly updated, while the Image doesn't. I'm accessing them in the same way, but one acts like I want it to and the other one does his thing.
This is the end result:
UPDATE: more details! Here's the code at runtime showing that the Image sprite does indeed get assigned
and here's my inspector:
Since you say that the Panel object itself also has an Image component you do not want to use GetComponentInChildren since it also returns any Image attached to the newObj itself!
Your code doesn't throw an exception but assigns the mySprite to the Image component of the Panel object, not its children called Image.
You would either have to use e.g.
newObj.transform.GetChild(0).GetComponent<Image>()
or as mentioned create your own class and attach it to the prefab like e.g.
public class TileController : Monobehaviour
{
// Reference these via the Inspector in the prefab
[SerializeField] private Text _text;
[SerializeField] private Image _image;
public void Initialize(Sprite sprite, String text)
{
_image.sprite = sprite;
_text.text = text;
}
}
and then do
// using the correct type here makes sure you can only reference a GameObject
// that actually HAS the required component attached
[SerializeField] private TileController _prefab;
public void Populate()
{
TileController newObj;
for (int i = 0; i < 20; i++)
{
// no cast needed since Instantiate returns the type of the prefab anyway
newObj = Instantiate(_prefab, transform);
newObj.Initialize(mySprite, "test" + i);
}
}
Are you sure GetComponentInChildren is pointing to the right Image component? I would create a custom MonoBehaviour class for your prefab, and create a SetImage() method that points to the correct Image. When using GetComponentInChildren, it will return the first component found, and this can lead to unexpected behaviour.
You can do a Debug on newObj.GetComponentInChildren<Image>() to verify that it's the right component. Your code seems to be OK.
Related
Scenario: User has different tile palettes for each season (Summer, Fall, etc.) and switching between the two using popular Unity techniques would be tedious if the number of tiles in the tileset is greater than 5 or 10. How would said user switch between their tile palettes programmatically, instead of using a wasteful solution like prefab tiles?
This problem seems significant enough that Unity docs would cover it. However I found myself digging through years-old forum posts to come up with my solution. Here's what I came up with, and it's not too complicated.
Create your tilesets. Import your tilesets as multi-sprite spritesheets, and split them using the Unity sprite editor. Do not bother naming each sprite. Each tileset should be organized the same exact way (bushes/trees/objects should be in the same spot on every season's tilesheet). When you are finished, create a folder "Resources" in your "Assets" folder. Inside of resources, create a folder "Spritesheets", and place your spricesheets in it.
Run this renaming script:
using UnityEngine;
using UnityEditor;
using System.Collections;
public class SpriteRenamer : MonoBehaviour
{
public Texture2D[] texture2Ds;
public string newName;
private string path;
private TextureImporter textureImporter;
void Start ()
{
foreach(Texture2D texture2D in texture2Ds){
path = AssetDatabase.GetAssetPath (texture2D);
textureImporter = AssetImporter.GetAtPath (path) as TextureImporter;
SpriteMetaData[] sliceMetaData = textureImporter.spritesheet;
int index = 0;
foreach (SpriteMetaData individualSliceData in sliceMetaData)
{
sliceMetaData[index].name = string.Format (newName + "_{0}", index);
print (sliceMetaData[index].name);
index++;
}
textureImporter.spritesheet = sliceMetaData;
EditorUtility.SetDirty (textureImporter);
textureImporter.SaveAndReimport ();
AssetDatabase.ImportAsset (path, ImportAssetOptions.ForceUpdate);
}
}
}
Attach it to an empty GameObject in an empty Scene (just for simplicity). Drag and drop your spritesheets into the Texture2D array. Set the newName field to whatever you want, it will be the prefix for the name of each sprite in each spritesheet. Finally, run the scene and each spritesheet's sprites will be renamed to make each corresponding sprite have the same name.
We now have each of our seasons' tilesheets modified so each tile is identically named. The next step is to create a Grid object with a child TilePalette. Draw all of your scenery, collision, etc. You can use as many TilePalettes as needed, as long as they are children of the Grid object. Now, create a script called ReskinnableTileBase:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class ReskinnableTileBase : TileBase
{
public Sprite sprite;
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
{
tileData.sprite = sprite;
}
}
Attach this Reskinner script to your Grid object:
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;
public class Reskinner : MonoBehaviour
{
Sprite[] subSprites;
Tilemap[] tilemaps;
void Start(){
tilemaps = GetComponentsInChildren<Tilemap>();
SetSkin("#INSERT DEFAULT TILE PALETTE NAME HERE#");
}
public void SetSkin(string name){
reloadSprites(name);
foreach(Tilemap tilemap in tilemaps){
for(int x = (int)tilemap.localBounds.min.x; x < tilemap.localBounds.max.x; x++){
for(int y = (int)tilemap.localBounds.min.y; y < tilemap.localBounds.max.y; y++){
TileBase tb = tilemap.GetTile(new Vector3Int(x, y, 0));
Debug.Log(tb);
ReskinnableTileBase rtb = (ReskinnableTileBase)ScriptableObject.CreateInstance(typeof(ReskinnableTileBase));
if(tb == null || rtb == null || tb.name.Length < 1){
continue;
}
Sprite replace = getSubSpriteByName(tb.name);
rtb.sprite = replace;
rtb.name = tb.name;
tilemap.SwapTile(tb, (TileBase)rtb);
}
}
}
}
void reloadSprites(string name){
subSprites = Resources.LoadAll<Sprite>("Spritesheets/" + name);
}
Sprite getSubSpriteByName(string name){
foreach(Sprite s in subSprites){
if(s.name == name){
return s;
}
}
return null;
}
}
And there you go! Now, any time you need to change the skin/season/tilesheet, just use a reference to the Grid's Reskinner script, and call the SetSkin method, like so:
Reskinner reskinner = gridObject.GetComponent<Reskinner>();
reskinner.SetSkin("summer");
kinda new to coding so help would be appreciated. I'm trying to duplicate this GameObject "cube" in unity and I'm having trouble with it. what im trying to do is duplicate the cube and get it to stack on top of each other over and over.
I know if i got this to work it would duplicte it in the same postion so you would only see it duplicate in the higharchy.
using System.Collections;
using UnityEngine;
public class cube : MonoBehaviour
{
public GameObject cube1;
public void update()
if(input.getKeyDown(KeyCode.Space))
{
instantiate cube1;
}
}
I assume you know the height of the cube, you are working with. In unity the default height is 1.0f(For the primitive cube).
Btw if your code is a pseudo code then its okey but if not, you need more training before writing such scripts, even tho this type of script is extremely easy to write.
(ps: i wrote this script in notepad++ hope it compiles :/)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// We start classes with capital letters in c# its a convention :)
public class Cube : MonoBehaviour
{
// Same applies to public class fields & Properties
// Marking a MonoBehaviour field as public will allow you to directly assign values to it
// inside the editor
public GameObject OriginalCube;
// Same can be achieved with private fields using the serializefield attribute
[SerializeField]
private float cubeHeight = 1.0f;
// In case you would like to store the duplicated cubes
public List<GameObject> Cubes = new List<GameObject>();
private void Awake()
{
// Adding the first cube to the list, i assume your cube is already in the scene
Cubes.Add(OriginalCube);
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
// We instantiate a new cube and add it to the list
Cubes.Add(Instantiate(Cubes[Cubes.Count - 1]);
// We ask the previous cube position (the one we copied)
Vector3 previousCubePosition = Cubes[Cubes.Count - 2].transform.position;
// then we assign a new position to our cube raised by "1 unit" on the y axis which is the up axis in unity
Cubes[Cubes.Count - 1].transform.position =
new Vector3(previousCubePosition.x, previousCubePosition.y + cubeHeight, previousCubePosition.z);
}
}
}
One way you can have your goal achieved, is to add to your newly instantiated object's y position a constant amount. This constant amount will be increased each time you create a new duplicate of your object.
public GameObject cube1;
private int instantiateCounter = 0;
public float PULL_UP_AMOUNT = 30f;
public void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
instantiateCounter++;
GameObject newCube = Instantiate(cube1);
newCube.transform.position = new Vector3(cube1.transform.position.x, cube1.transform.position.y + instantiateCounter * PULL_UP_AMOUNT, cube1.transform.position.z);
}
}
The constant amount we're talking about is PULL_UP_AMOUNT.
Keep in mind, you can access your new duplicate's properties by saving the result of Instantiate method inside a new GameObject, just like I did.
Hi My scene name is a game.In that scene I am having A main panel that's name is ITEMCONTAINER. In item container I am having a panel whoes name is ITEM. I attached a script in ITEM PANEL. IN that script i am having a game object public,a raw image ,text and how many times loop will continue are public.
In the place of game object i attached my prefab,that contain 1 text and 2 rawimage.
in place of text i attached text component of prefab and same like raw image.
When I run the game the text value I am getting correctly but rawimage is showing blank in runtime.Here i am running my loop 3 times and all three times it create clone of my prefab panel as a child in itempanel
I want rawimage dynamic at my run time
output
prefab
image= in this image , it contains output.here rawimage is blank but text valuecoming perfectly
image = it is my prefab that prefab will be clone during runtime, here it shows image but during runtime , in clone it shows blank
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DynamicData : MonoBehaviour {
public GameObject prefab;
public Text name;
public int numberToCreate;
public RawImage profile;
void Start () {
for (int i = 0; i < numberToCreate; i++)
{
name.text = "a"+i;
StartCoroutine( ImageDownload( profile));
Instantiate <GameObject>(prefab, transform);
}
}
IEnumerator ImageDownload ( RawImage img) {
WWW www = new WWW("https://www.w3schools.com/w3images/fjords.jpg");
yield return www;
Texture2D texure = new Texture2D (1, 1);
texure.LoadImage (www.bytes);
texure.Apply ();
img.texture = texure;
}
}
You are assigning the downloaded image to the profile variable which is not a variable from the instantiated Object. In the loop, you need to get the child of each instantiated Object with transform.Find("profile") then get the RawImage component attached to it with GetComponent and pass it to the ImageDownload function.
Replace your for loop with this:
for (int i = 0; i < numberToCreate; i++)
{
name.text = "a" + i;
GameObject instance = Instantiate<GameObject>(prefab, transform);
//Get RawImage of the current instance
RawImage rImage = instance.transform.Find("profile").GetComponent<RawImage>();
StartCoroutine(ImageDownload(rImage));
}
Please rename public Text name; to something else like public Text userName; because the name is already declared in MonoBehavior you derived your script from.
I want to change the UI image in random order. I have a gameobject in UI(canvas) containing Image component and it has null image initially. I have a script attached to it(gameobject) to change the image on run time.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class changeImg : MonoBehaviour {
public Sprite sprite1;
public Sprite sprite2;
public Sprite sprite3;
void Start()
{
ChangeImg();
}
void ChangeImg()
{
int rand=Random.Range(0,3);
if(rand==0)
{
gameObject.GetComponent<Image> ().sprite = sprite1;
//gameObject.GetComponent<UnityEngine.UI.Image> ().sprite = sprite1;
}
else if(rand==1)
{
gameObject.GetComponent<Image> ().sprite = sprite2;
// gameObject.GetComponent<UnityEngine.UI.Image> ().sprite = sprite2;
}
else if(rand==2)
{
gameObject.GetComponent<Image> ().sprite = sprite3;
//gameObject.GetComponent<UnityEngine.UI.Image> ().sprite = sprite3;
}
}
}
I have assigned the public field (sprite1,sprite2,sprite3) in inspector. And I tried the both option as I had commented in code. I did not get an error but also the image did not get change as I want. During runtime of a program, GameObject(to which the script is attached) has null image source as it was initially.
Use overrideSprite field instead of sprite - documentation
Unfortunately, unity ui is full of such pitfalls and it's api is totally counter-intuitive, so you have to be careful and check the docs regularly
You can also just use Image if you are in Unity 2017.3 (not sure if this works for older versions). For example:
using UnityEngine.UI;
-----
public Image ObjectwithImage;
public Sprite spriteToChangeItTo;
void Start () {
ObjectwithImage.sprite = spriteToChangeItTo;
}
Works great for me.
Have you checked the position of the Gameobject? Also the color of the image?
To change the Image from a Button, don't use GetComponent<Image> () as you can potentially get another Image component that does not belong to the button. It can also return null if the object is disabled.
Use the Button.image.sprite or Button.image.overrideSprite variable instead.
public Button pb;
public Sprite newSprite;
void Start()
{
pb.image.sprite = newSprite;
}
Or
pb.image.overrideSprite = newSprite;
It really doesn't matter which one is used. Any of these two should work.
I'm having an issue where I can't disable a script from the other script - they are both public and within the same package (I think).
Here is my code for the script I'm trying to disable from:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
using RTS;
public class PauseMenu : MonoBehaviour {
Canvas canvas;
private Player player;
public Button Button2;
void Start()
{
Debug.Log ("asdf");
player = transform.root.GetComponent< Player >();
canvas = GetComponent<Canvas>();
canvas.enabled = false;
ResourceManager.MenuOpen = false;
Button2.GetComponent<Button>().onClick.AddListener(() => { Resume();});
if(player) player.GetComponent< UserInput >().enabled = false;
}
And the code for the other script:
//sets up what resources we are using
using UnityEngine;
using System.Collections;
using RTS;
public class UserInput : MonoBehaviour {
//sets up a private variable for only this class - our player
private Player player;
// Use this for initialization
void Start () {
//this goes to the root of the player ie the object player and allows us to
player = transform.root.GetComponent< Player > ();
}//end Start()
So the part that is not working is:
if(player) player.GetComponent< UserInput >().enabled = false;
And the code runs and then causes the runtime error:
NullReferenceException: Object reference not set to an instance of an object
PauseMenu.Pause () (at Assets/Menu/PauseMenu.cs:40)
PauseMenu.Update () (at Assets/Menu/PauseMenu.cs:29)
Here is a picture showing my scene hierarchy and components:
The issue here is that you try to execute transform.root.GetComponent< Player >(); from within PauseMenu, which is on the "Canvas" object.
The problem with that is that the topmost transform in the hierarchy of your "Canvas" object (which is what transform.root returns) is, well, the transform of the "Canvas" object - which is in no way related to the UserInput script you are trying to access. For this script to actually work, you would need the transform of your "Player" object, which is the object that actually has the UserInput script.
My suggestion is to eliminate the need to run GetComponent() at all - create a public UserInput variable in your PauseMenu class, then (while selecting your "Canvas") in the editor, drag the "Player" object into that new field. This will cause the UserInput script of your "Player" object to be accessible within the PauseMenu.
So your PauseMenu script might look like:
public class PauseMenu : MonoBehaviour {
Canvas canvas;
public UserInput playerInput; // Drag the Player object into this field in the editor
public Button Button2;
void Start()
{
Debug.Log ("asdf");
canvas = GetComponent<Canvas>();
canvas.enabled = false;
ResourceManager.MenuOpen = false;
Button2.GetComponent<Button>().onClick.AddListener(() => { Resume();});
playerInput.enabled = false;
}
}
Hope this helps! Let me know if you have any questions.
(An alternative is to use GameObject.Find("Player") to get GameObject of "Player". This needs a bit more code but doesn't use the editor.)
I would say your player = transform.root.GetComponent< Player >(); arrives null.
So you are trying to disable something that doesnt exist.
Enter debug mode and see if your player is null or not.