Get single sprite from sprite atlas - c#

I'd like to get a single sprite which GameObject has in a SpriteRenderer component.
Unfortunately, this code returns the whole atlas, but I need to a part of this atlas.
Texture2D thumbnail = GetComponent<SpriteRenderer>().sprite.texture;

There is no native API to get single sprite from the SpriteRenderer and no API to access individual Sprite by name. You can vote for this feature here.
You can make your own API to get single sprite from Atlas located in the Resources folder like the image included in your question.
You can load all the sprites from the Atlas with Resources.LoadAll then store them in a dictionary.A simple function can then be used to access each sprite with the provided name.
A simple Atlas Loader script:
public class AtlasLoader
{
public Dictionary<string, Sprite> spriteDic = new Dictionary<string, Sprite>();
//Creates new Instance only, Manually call the loadSprite function later on
public AtlasLoader()
{
}
//Creates new Instance and Loads the provided sprites
public AtlasLoader(string spriteBaseName)
{
loadSprite(spriteBaseName);
}
//Loads the provided sprites
public void loadSprite(string spriteBaseName)
{
Sprite[] allSprites = Resources.LoadAll<Sprite>(spriteBaseName);
if (allSprites == null || allSprites.Length <= 0)
{
Debug.LogError("The Provided Base-Atlas Sprite `" + spriteBaseName + "` does not exist!");
return;
}
for (int i = 0; i < allSprites.Length; i++)
{
spriteDic.Add(allSprites[i].name, allSprites[i]);
}
}
//Get the provided atlas from the loaded sprites
public Sprite getAtlas(string atlasName)
{
Sprite tempSprite;
if (!spriteDic.TryGetValue(atlasName, out tempSprite))
{
Debug.LogError("The Provided atlas `" + atlasName + "` does not exist!");
return null;
}
return tempSprite;
}
//Returns number of sprites in the Atlas
public int atlasCount()
{
return spriteDic.Count;
}
}
Usage:
With the Example image above, "tile" is the base image and ball, bottom, people, and wallframe are the sprites in the atlas.
void Start()
{
AtlasLoader atlasLoader = new AtlasLoader("tiles");
Debug.Log("Atlas Count: " + atlasLoader.atlasCount());
Sprite ball = atlasLoader.getAtlas("ball");
Sprite bottom = atlasLoader.getAtlas("bottom");
Sprite people = atlasLoader.getAtlas("people");
Sprite wallframe = atlasLoader.getAtlas("wallframe");
}

You could put the image you need in the Resources folder by itself, then use the Resources.Load("spriteName") to get it. If you want to get it as a sprite, you would do the following:
Sprite thumbnail = Resources.Load("spriteName", typeof(Sprite)) as Sprite;
Source: https://forum.unity3d.com/threads/how-to-change-sprite-image-from-script.212307/

Well with the new Unity versions you can do it easily using SpriteAtlas class and GetSprite method:
https://docs.unity3d.com/ScriptReference/U2D.SpriteAtlas.html
So if you are working with the Resources folder you can do:
Resources.Load<SpriteAtlas>("AtlasName")

Related

Unity Changing Material Tiling per Gameobject

I've written a script that is supposed to make tiling materials easier. I was tired of constantly re-adjusting the tiling settings of materials on my walls as I resized them.
[ExecuteInEditMode]
public class AutoTiler : MonoBehaviour
{
public float xScaleMult = 1;
public float yScaleMult = 1;
private Material texture;
// Update is called once per frame
void Update()
{
if (!texture)
{
texture = GetComponent<MeshRenderer>().material; //get the material if it is null
}
//set the material scale based on the size of the object
texture.mainTextureScale = new Vector2(Mathf.Max(transform.localScale.x, transform.localScale.z) * xScaleMult, transform.localScale.y * yScaleMult);
}
}
However, every time I reload the scene, I get an error message saying:
Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.
I've looked up this error but I can't quite figure out how to solve it. I understand that I'm copying the material in order to change it, but that is the desired result, as it needs different tiling settings for every wall, so that it does not appear stretched. Currently the code works, but fills the console with this error message. Is there a better way to do this?
in editor you can use sharedmaterial instead material property, something like this:
using UnityEngine;
[ExecuteInEditMode]
public class AutoTiler : MonoBehaviour
{
public float xScaleMult = 1;
public float yScaleMult = 1;
private Material material;
private Material Material
{
get
{
if (material == null)
{
MeshRenderer render = GetComponent<MeshRenderer>();
#if UNITY_EDITOR
material = render.sharedMaterial;
#else
material = render.material;
#endif
}
return material;
}
}
// Update is called once per frame
void Update()
{
//set the material scale based on the size of the object
Material.mainTextureScale = new Vector2(Mathf.Max(transform.localScale.x, transform.localScale.z) * xScaleMult, transform.localScale.y * yScaleMult);
}
}
for more details about difference between material and sharedmaterial see :
http://gyanendushekhar.com/2018/09/23/difference-material-shared-material-unity-tutorial/
https://docs.unity3d.com/ScriptReference/Renderer-sharedMaterial.html

Load Spritesheet/Atlas through Streaming Assets

I am trying to allow a degree of modding in my Unity game by utilizing streaming assets. I can import a single sprite no problem, but I am not sure how to set an imported streaming assets sprite to Sprite Mode: Multiple and slice that sprite into its sub parts.
Here is a test class I am using for the import right now:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PixelsoftGames.Tools2D;
using System.IO;
public class Sandbox : MonoBehaviour
{
SpriteRenderer sRenderer = null;
private void Awake()
{
sRenderer = GetComponent<SpriteRenderer>();
}
private void Start()
{
DirectoryInfo directoryInfo = new DirectoryInfo(Application.streamingAssetsPath);
FileInfo[] allFiles = directoryInfo.GetFiles("*.*");
foreach(FileInfo file in allFiles)
if(file.Name.Contains("Laser"))
StartCoroutine("LoadSprite", file);
}
IEnumerator LoadSprite(FileInfo file)
{
if (file.Name.Contains("meta"))
yield break;
else
{
string fileWithoutExtension = Path.GetFileNameWithoutExtension(file.ToString());
string finalPath;
WWW localFile;
Texture2D texture;
finalPath = "file://" + file.ToString();
localFile = new WWW(finalPath);
Debug.Log(finalPath);
yield return localFile;
texture = localFile.texture;
texture.filterMode = FilterMode.Point;
Sprite sprite = Sprite.Create(texture as Texture2D, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 32f);
sRenderer.sprite = sprite;
}
}
}
You can't just drop a Sprite sheet in the StreamingAssets folder and expect to access it directly in a build. Since Sprite sheet is in Unity format, you have to use one of Unity's resources/asset API to access it. There are two ways to do this:
1.With the Resources API. This means you must use the Resources folder instead of the StreamingAssets folder. Put the Sprite atlas in the Resources folder then read as follow:
Sprite[] sprite = Resources.LoadAll<Sprite>("spriteFile") as Sprite[];
2.If you want to use the StreamingAssets folder, you must build the Sprite sheet as Assetbundle then use the AssetBundle API read it during run-time. The AssetBundle.LoadAssetWithSubAssets and AssetBundle.LoadAssetWithSubAssetsAsync (Recommended) functions are used to load Sprite atlas.
This post shows how to build AssetBundle. Ignore the loading part because loading sprite atlas is different from loading a normal Texture. Once you build it, see below for how to load it. The sprite atlas is stored in the loadedSprites variable:
public Image image;
string nameOfAssetBundle = "animals";
string nameOfObjectToLoad = "dog";
void Start()
{
StartCoroutine(LoadAsset(nameOfAssetBundle, nameOfObjectToLoad));
}
IEnumerator LoadAsset(string assetBundleName, string objectNameToLoad)
{
string filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "AssetBundles");
filePath = System.IO.Path.Combine(filePath, assetBundleName);
//Load "animals" AssetBundle
var assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(filePath);
yield return assetBundleCreateRequest;
AssetBundle asseBundle = assetBundleCreateRequest.assetBundle;
//Load the "dog" Asset (Use Sprite since it's a Sprite. Use GameObject if prefab)
AssetBundleRequest asset = asseBundle.LoadAssetWithSubAssetsAsync<Sprite>(objectNameToLoad);
yield return asset;
//Retrive all the Object atlas and store them in loadedSprites Sprite
UnityEngine.Object[] loadedAsset = asset.allAssets as UnityEngine.Object[];
Sprite[] loadedSprites = new Sprite[loadedAsset.Length];
for (int i = 0; i < loadedSprites.Length; i++)
loadedSprites[i] = (Sprite)loadedAsset[i];
Debug.Log("Atlas Count: " + loadedSprites.Length);
for (int i = 0; i < loadedSprites.Length; i++)
{
Debug.LogWarning(loadedSprites[i].name);
//Do something with the loaded loadedAsset object (Load to Image component for example)
image.sprite = loadedSprites[i];
}
}
Starting with the 1.17.x preview version, you can use the following syntax:
myAddressableSpriteSheet.LoadAssetAsync<Sprite[]>();
With myAddressableSpriteSheet being an AssetReferenceTexture2D.
This functionality is not documented anywhere yet but was mentioned in the Unity forum.

How To Dynamically Add a 3D Model to a Ground Plane

I was using Unity's Ground Plane feature to place a 3d animated model. However I am now downloading this 3d model using AssetBundle feature and need to place it under on the Ground Plane Stage using a script.
However it doesn't display, when I deploy it onto an android device...
I am using a Xiaomi Redmi 3s which has support for GroundPlane detection.
I have added the script to download the asset bundle onto the Plane Finder
AssetbundleDownloadingScript:
public class AssetLoader : MonoBehaviour
{
public static AssetLoader Instance;
public string url = "myurl";
public int version = 1;
public string AssetName;
//public Text infoText;
public string infoText = "";
AssetBundle bundle;
void Awake()
{
Instance = this;
DownloadAsset();
}
void OnDisable()
{
//AssetBundleManager.Unload(url, version);
}
public void DownloadAsset()
{
// bundle = AssetBundleManager.getAssetBundle (url, version);
// Debug.Log(bundle);
if (!bundle)
StartCoroutine(DownloadAssetBundle());
}
IEnumerator DownloadAssetBundle()
{
yield return StartCoroutine(AssetBundleManager.downloadAssetBundle(url, version));
bundle = AssetBundleManager.getAssetBundle(url, version);
if (bundle != null)
infoText = "Download Success.....";
else
infoText = "Download error please retry";
GameObject santaasset = bundle.LoadAsset("animation_keyframes_increase_v1", typeof(GameObject)) as GameObject;
//here script attached to plane finder,get 1st child of planefinder
var child = gameObject.transform.GetChild(0);
if (santaasset != null)
{
santaasset.transform.transform.Rotate(new Vector3(0, 180, 0));
santaasset.transform.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
santaasset.transform.transform.SetParent(child.transform, false);
}
bundle.Unload(false);
}
public void SetInfoText(string text)
{
//infoText.text = text;
}
void OnGUI()
{
GUILayout.Label("Dummy Label:" + infoText);
}
}
Here's a screenshot of my scene:
Any suggestions on what I am doing wrong ? Thanks.
I noticed in your AssetbundleDownloadingScript you are creating a GameObject santaasset, however you are never assigning the object to a new or an existing GameObject, or even Instantiating it. You are instead assigning the Asset that you are loading from your bundle. However that asset is also never assigned, since it was just loaded into memory, so that Unity could recognize it. This is what you are experiencing, and this is why you see the object in game, but its not active, even tough its not disabled.
To fix this issue, you must assign your GameObject or Instantiate it like so:GameObject santaasset = Instantiate(bundle.LoadAsset("animation_keyframes_increase_v1", typeof(GameObject)) as GameObject);

Replace material whith sprite

How to add sprite to player material with script. I have a shop menu of player and when a like to select one sprite i like to add to player material but I don't know how to do this. I have my code but just this I don't know how to add. This is my code I made like this but don't work can someone tell me how to add playersprite to playermaterial.
public Material playerMaterial; // --> (player Material)
public Sprite[] playerSprite; // --> (Sprite i wish to add to the player)
GameManager.Instance.playerMaterial = GameManager.Instance.playerSprite[index];
private void SetSprite(int index)
{
activeSpriteIndex = index;
GameManager.Instance.state.activeSprite = index;
GameManager.Instance.playerMaterial = GameManager.Instance.playerSprite[index];
spriteBuySetText.text = "Current";
GameManager.Instance.Save();
}
A Unity3d material is more than just a texture so you need to set the texture of material via the SetTexture() method or mainTexture property. I'm assuming it's the Main Texture you want to change and not the bump or lighting maps.
public Material playerMaterial; // --> (player Material)
public Sprite[] playerSprite; // --> (Sprite i wish to add to the player)
private void SetSprite(int index)
{
activeSpriteIndex = index;
GameManager.Instance.state.activeSprite = index;
GameManager.Instance.playerMaterial.mainTexture = GameManager.Instance.playerSprite[index].texture;
spriteBuySetText.text = "Current";
GameManager.Instance.Save();
}

Unity resources folder and loading next texture on 3D object

First of all I'm not a professional coder but have done some C# in the past. I have problem with my Unity project at the moment. I'm loading images from my resources folder and rendering them on a 3D object. Problem is with my nex texture "buttons" which are supposed to look at variable "n" and determinate which image is loaded as texture on a 3D object based on their order in the images folder. Currently when I click on a next image button it renders the next texture but only for the first 2 images in the folder. After that it does go in to the loop I've checked with debug.log but it does not render next image as a texture. Code is as following:
For the next button (that calls render texture and changes variable 'n' value):
void OnMouseDown() {
if (this.name == "NextTexture") {
LevelSelect.n=+2;
LevelSelect.RenderTextures();
}
if (this.name == "PreviousTexture") {
LevelSelect.n-=2;
LevelSelect.RenderTextures();
}
and for the image to texture rendering:
public static void RenderTextures(){
Mesh mesh = staticMeshFilter.mesh;
Vector3[] vertices = mesh.vertices;
Vector2[] uvs = new Vector2[vertices.Length];
for (int i=0; i < uvs.Length; i++) {
uvs[i] = new Vector2(vertices[i].x, vertices[i].y);
}
mesh.uv = uvs;
rend.material.mainTexture = loader.LoadImages ("images") [n];
transformTexture.renderer.material.mainTextureOffset = new Vector2(0.4f, 0.28f); // +0.1x
transformTexture.renderer.material.mainTextureScale = new Vector2(0.8f, 0.8f);
}
So at the moment I'm confused about how and why my code doesn't render the 3rd and 4th image from the images folder as textures.
Cheers!

Categories