Change the UI image using script in unity c# - c#

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.

Related

How to programmatically switch tile palettes in Unity2D

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");

Change Unity3D prefab image during runtime

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.

Why Camera.current returns null when I am in Unity Editor? And is there any other ways to catch the current camera?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteAlways]
public class SkyBox : MonoBehaviour
{
public Material[] skyboxes;
public Camera skyboxCamera;
public float skyboxMoveSpeed = 2f;
private int index = 0;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
SwitchSkybox();
}
if (RenderSettings.skybox == skyboxes[1])
{
RenderSettings.skybox.SetFloat("_Rotation", Time.time * skyboxMoveSpeed);
}
}
public void SwitchSkybox()
{
index++;
if (index == skyboxes.Length)
{
index = 0;
}
RenderSettings.skybox = skyboxes[index];
if (RenderSettings.skybox == skyboxes[1])
{
skyboxCamera.enabled = true;
Camera.current.enabled = false;
Time.timeScale = 1.0f;
}
else
{
skyboxCamera.enabled = false;
Camera.current.enabled = true;
Time.timeScale = 0.0f;
}
}
}
The script switch between skyboxes the default and my skybox and also switch between the currently active camera and the sky box camera.
But when I'm hitting the escape key it's throwing null exception in the editor on the line number 46 :
Camera.current.enabled = false;
The current of the Camera is null
I want to make that when I press the escape key it will switch to my skybox and to the skybox camera and also will pause the game (Later I will make a main menu when the game is paused).
This is the Camera.current, from the manual.
The camera we are currently rendering with.
Also worth noting the comment from Ruzihm.
The Unity engine typically assigns an already-instantiated instance of
Camera to Camera.current
So, from your scripts, I see 2 issues. The one directly related to this questions happens just in editor mode and I will start from that one.
Editor Issue: Camera.current is null
When working in the editor, Camera.current won't be just your own application's camera, but it could be any camera. It could even refer to the editor's scene view camera.
In this last case, if your scene view is not in focus (IE when you've focus on Game Window) Camera.current will be null.
Logical Issue: you couldn't switch back
When you try to switch back from skyboxCamera, your Camera.current will be the same skyboxCamera, and not your default camera. So you won't be able to retrieve the previous camera.
SOLUTION
Do not use Camera.current, but store all of your cameras in your script (this solution is also better for perfomance, since both Camera.current and Camera.Main are not performant scripts).
In your case you will need to add this piece of code to your script and use the EnableSkyBoxCamera method.
public Camera defaultCamera;
public Camera skyBoxCamera;
private Camera _currentCamera;
public void EnableSkyBoxCamera(bool enableSkyBox)
{
defaultCamera.enabled = !enableSkyBox;
skyBoxCamera.enabled = !enableSkyBox;
if (enableSkyBox) _currentCamera = skyBoxCamera;
else _currentCamera = defaultCamera;
}
If camera is null you can't set the enabled to false without getting a nullpointerexception. Instantiate the camera first or remove that line of code.
*** Edit ill take another crack at this
try
Camera.main.enabled = false;
instead of
Camera.current.enabled = false;
As per Unity docs in reference to Camera.current: 'Most of the time you will want to use Camera.main instead. Use this function only when implementing one of the following events: MonoBehaviour.OnRenderImage, MonoBehaviour.OnPreRender, MonoBehaviour.OnPostRender'

Unity 5.3 with C# - Toggle Sound On/Off

I'm working with Unity 5.3 with C# as code editor
These are what I have :
I have 2 scenes in my project : home and options. I have bg object on both scenes. Both bg objects have Audio Source component, which contains same background music that play on awake. I don't use any codes for these background musics, I only click the Add Component button from Unity and add Audio Source.
This is what I want :
Options scene can toggle the background music on/off for all scenes. Therefore, there are btnOn and btnOff in Options scene.
This is my code in Audio Manager.cs :
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class AudioManager : MonoBehaviour {
public Button btnOn;
public Button btnOff;
// Use this for initialization
void Start () {
btnOn = GetComponent<Button>();
btnOff = GetComponent<Button>();
btnOn.onClick.AddListener(() => PlayAudio());
btnOff.onClick.AddListener(() => StopAudio());
}
void PlayAudio()
{
AudioSource.volume = 0.5f;
}
void StopAudio()
{
AudioSource.volume = 0f;
}
}
This is the problem :
I have this error : An object reference is required to access non-static member UnityEngine.AudioSource.volume. Maybe, this is because I don't write public AudioSource audioSource in my code. But, if I write this, I have to add another audio in Get Component box, and I will have double Audio Source in one scene. What should I do? Thanks
As you have a bg object in both scenes and as your AudioManager isn't marked as [DontDestroyOnLoad] (then I assume you also have one AudioManager in both scenes), it's Start() function will fire each time you load a scene.
So you can declare a private AudioSource and get a reference of it by finding your bg object in your scene and calling GetComponent on it :
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class AudioManager : MonoBehaviour
{
public Button btnOn;
public Button btnOff;
private AudioSource audioSource;
// Use this for initialization
void Start()
{
btnOn = GetComponent<Button>();
btnOff = GetComponent<Button>();
btnOn.onClick.AddListener(() => PlayAudio());
btnOff.onClick.AddListener(() => StopAudio());
audioSource = GameObject.Find("bg").GetComponent<AudioSource>();
}
void PlayAudio()
{
audioSource.volume = 0.5f;
}
void StopAudio()
{
audioSource.volume = 0f;
}
}
The error is raised because you need to assign the property on an individual object; the volume is not shared between sources. For this reason you will either need a field to assign in the inspector, or get a reference with GetComponent.
While using different scenes for handling options is not wrong, it is a tad clumsy; the current scene has to be unloaded (destroying all object not marked as DontDestroyOnLoad and information associated with them), after which the options are loaded, to then load the previous scene. While unloading the music most likely stops playing, which, after loading, starts at the beginning again. Not mention any settings on these objects are lost (volume, change of track, etc.).
The afore mentioned DontDestroyOnLoad can help since you make your changes on the same object, but you will have to deal with duplicates each time a scene is loaded where such an object exists... you can use the function OnLevelWasLoaded (documentation is a tad lacking at the moment) as a moment to determine which objects to destroy.
Another point is that you currently have public Button fields. This allows the for the assignment of them via the inspector, but that is rather moot as you overwrite them in Start (new value being the first button component assigned to the same object). I would make these fields private, or at least make sure they are not assigned. But I'm getting slightly of topic on how to keep settings persistent between scenes.
Here is some code to give you an idea, but be warned it is untested as I currently have no access to an environment to test it. This solution uses an object which is persistent between scenes. Keep in mind that any connections established to this object via the editor are lost after another scene has loaded.
public class AudioManager : MonoBehaviour
{
private AudioSource source;
// Called regardless of whether the object is enabled or not.
// Should not be called in a new scene.
private void Awake()
{
// Protect this object from being destroyed so its volume
// is maintained between scenes.
DontDestroyOnLoad(this.gameObject);
source = GetComponent<AudioSource>();
}
public void DestroyPossibleDuplicates()
{
AudioManager[] managers = FindObjectsOfType(typeof(AudioManager)) as AudioManager[];
foreach (AudioManager manager in managers)
{
// Use something to determine whether a manager is a duplicate.
// It must not be this, but have something in common; a name perhaps?
if ((manager != this) && (manager.gameObject.name == this.gameObject.name))
{
// Destroy the duplicates so their sound won't interfere.
Destroy(manager.gameObject);
}
}
}
private void BindToInterface()
{
Slider[] sliders = FindObjectsOfType(typeof(Slider)) as Slider[];
foreach (Slider slider in sliders)
{
// Determine whether the specified slider should have effect on this object.
// If the slider's name contains this object's name assume it should.
if (slider.gameObject.name.indexOf(this.gameObject.name)!=-1)
{
slider.onValueChanged.addListener((float value)=>
{
// In this case a slider is used for more control over the volume.
// Different elements require other logic to function.
source.volume = value;
});
}
}
}
// If my memory serves correct this method is only called on objects
// that were in the scene before it started loading.
// Just to be safe, don't have it do anything depending on this difference.
private void OnLevelWasLoaded()
{
DestroyPossibleDuplicates();
BindToInterface();
}
}

Code not allowing letting me disable a script from another script

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.

Categories

Resources